• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2003-2016 CORE Security Technologies
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7
8
9# -*- mode: python; tab-width: 4 -*-
10#
11# Copyright (C) 2001 Michael Teo <michaelteo@bigfoot.com>
12# nmb.py - NetBIOS library
13#
14# This software is provided 'as-is', without any express or implied warranty.
15# In no event will the author be held liable for any damages arising from the
16# use of this software.
17#
18# Permission is granted to anyone to use this software for any purpose,
19# including commercial applications, and to alter it and redistribute it
20# freely, subject to the following restrictions:
21#
22# 1. The origin of this software must not be misrepresented; you must not
23#    claim that you wrote the original software. If you use this software
24#    in a product, an acknowledgment in the product documentation would be
25#    appreciated but is not required.
26#
27# 2. Altered source versions must be plainly marked as such, and must not be
28#    misrepresented as being the original software.
29#
30# 3. This notice cannot be removed or altered from any source distribution.
31#
32# Altered source done by Alberto Solino (@agsolino)
33
34import socket
35import string
36import re
37import select
38import errno
39from random import randint
40from struct import pack, unpack
41import time
42
43from structure import Structure
44
45CVS_REVISION = '$Revision: 526 $'
46
47# Taken from socket module reference
48INADDR_ANY = '0.0.0.0'
49BROADCAST_ADDR = '<broadcast>'
50
51# Default port for NetBIOS name service
52NETBIOS_NS_PORT = 137
53# Default port for NetBIOS session service
54NETBIOS_SESSION_PORT = 139
55
56# Default port for SMB session service
57SMB_SESSION_PORT = 445
58
59# Owner Node Type Constants
60NODE_B = 0x0000
61NODE_P = 0x2000
62NODE_M = 0x4000
63NODE_RESERVED = 0x6000
64NODE_GROUP = 0x8000
65NODE_UNIQUE = 0x0
66
67# Name Type Constants
68TYPE_UNKNOWN = 0x01
69TYPE_WORKSTATION = 0x00
70TYPE_CLIENT = 0x03
71TYPE_SERVER = 0x20
72TYPE_DOMAIN_MASTER = 0x1B
73TYPE_DOMAIN_CONTROLLER = 0x1C
74TYPE_MASTER_BROWSER = 0x1D
75TYPE_BROWSER = 0x1E
76TYPE_NETDDE  = 0x1F
77TYPE_STATUS = 0x21
78
79# Opcodes values
80OPCODE_QUERY = 0
81OPCODE_REGISTRATION = 0x5
82OPCODE_RELEASE = 0x6
83OPCODE_WACK = 0x7
84OPCODE_REFRESH = 0x8
85OPCODE_REQUEST = 0
86OPCODE_RESPONSE = 0x10
87
88# NM_FLAGS
89NM_FLAGS_BROADCAST = 0x1
90NM_FLAGS_UNICAST = 0
91NM_FLAGS_RA = 0x8
92NM_FLAGS_RD = 0x10
93NM_FLAGS_TC = 0x20
94NM_FLAGS_AA = 0x40
95
96# QUESTION_TYPE
97QUESTION_TYPE_NB = 0x20     # NetBIOS general Name Service Resource Record
98QUESTION_TYPE_NBSTAT = 0x21 # NetBIOS NODE STATUS Resource Record
99# QUESTION_CLASS
100QUESTION_CLASS_IN = 0x1     # Internet class
101
102# RR_TYPE Resource Record Type code
103RR_TYPE_A = 0x1               # IP address Resource Record
104RR_TYPE_NS = 0x2              # Name Server Resource Record
105RR_TYPE_NULL = 0xA          # NULL Resource Record
106RR_TYPE_NB = 0x20           # NetBIOS general Name Service Resource Record
107RR_TYPE_NBSTAT = 0x21       # NetBIOS NODE STATUS Resource Record
108
109# Resource Record Class
110RR_CLASS_IN = 1             # Internet class
111
112# RCODE values
113RCODE_FMT_ERR   = 0x1       # Format Error.  Request was invalidly formatted.
114RCODE_SRV_ERR   = 0x2       # Server failure.  Problem with NBNS, cannot process name.
115RCODE_IMP_ERR   = 0x4       # Unsupported request error.  Allowable only for challenging NBNS when gets an Update type
116                            # registration request.
117RCODE_RFS_ERR   = 0x5       # Refused error.  For policy reasons server will not register this name from this host.
118RCODE_ACT_ERR   = 0x6       # Active error.  Name is owned by another node.
119RCODE_CFT_ERR   = 0x7       # Name in conflict error.  A UNIQUE name is owned by more than one node.
120
121# NAME_FLAGS
122NAME_FLAGS_PRM = 0x0200       # Permanent Name Flag.  If one (1) then entry is for the permanent node name.  Flag is zero
123                            # (0) for all other names.
124NAME_FLAGS_ACT = 0x0400       # Active Name Flag.  All entries have this flag set to one (1).
125NAME_FLAG_CNF  = 0x0800       # Conflict Flag.  If one (1) then name on this node is in conflict.
126NAME_FLAG_DRG  = 0x1000       # Deregister Flag.  If one (1) then this name is in the process of being deleted.
127
128NAME_TYPES = { TYPE_UNKNOWN: 'Unknown', TYPE_WORKSTATION: 'Workstation', TYPE_CLIENT: 'Client',
129               TYPE_SERVER: 'Server', TYPE_MASTER_BROWSER: 'Master Browser', TYPE_BROWSER: 'Browser Server',
130               TYPE_DOMAIN_MASTER: 'Domain Master' , TYPE_NETDDE: 'NetDDE Server'}
131# NetBIOS Session Types
132NETBIOS_SESSION_MESSAGE = 0x0
133NETBIOS_SESSION_REQUEST = 0x81
134NETBIOS_SESSION_POSITIVE_RESPONSE = 0x82
135NETBIOS_SESSION_NEGATIVE_RESPONSE = 0x83
136NETBIOS_SESSION_RETARGET_RESPONSE = 0x84
137NETBIOS_SESSION_KEEP_ALIVE = 0x85
138
139
140def strerror(errclass, errcode):
141    if errclass == ERRCLASS_OS:
142        return 'OS Error', str(errcode)
143    elif errclass == ERRCLASS_QUERY:
144        return 'Query Error', QUERY_ERRORS.get(errcode, 'Unknown error')
145    elif errclass == ERRCLASS_SESSION:
146        return 'Session Error', SESSION_ERRORS.get(errcode, 'Unknown error')
147    else:
148        return 'Unknown Error Class', 'Unknown Error'
149
150
151
152class NetBIOSError(Exception): pass
153class NetBIOSTimeout(Exception):
154    def __init__(self, message = 'The NETBIOS connection with the remote host timed out.'):
155        Exception.__init__(self, message)
156
157class NBResourceRecord:
158    def __init__(self, data = 0):
159        self._data = data
160        try:
161            if self._data:
162                self.rr_name = (re.split('\x00',data))[0]
163                offset = len(self.rr_name)+1
164                self.rr_type  = unpack('>H', self._data[offset:offset+2])[0]
165                self.rr_class = unpack('>H', self._data[offset+2: offset+4])[0]
166                self.ttl = unpack('>L',self._data[offset+4:offset+8])[0]
167                self.rdlength = unpack('>H', self._data[offset+8:offset+10])[0]
168                self.rdata = self._data[offset+10:offset+10+self.rdlength]
169                offset = self.rdlength - 2
170                self.unit_id = data[offset:offset+6]
171            else:
172                self.rr_name = ''
173                self.rr_type = 0
174                self.rr_class = 0
175                self.ttl = 0
176                self.rdlength = 0
177                self.rdata = ''
178                self.unit_id = ''
179        except Exception:
180                raise NetBIOSError( 'Wrong packet format ' )
181
182    def set_rr_name(self, name):
183        self.rr_name = name
184    def set_rr_type(self, name):
185        self.rr_type = name
186    def set_rr_class(self,cl):
187        self.rr_class = cl
188    def set_ttl(self,ttl):
189        self.ttl = ttl
190    def set_rdata(self,rdata):
191        self.rdata = rdata
192        self.rdlength = len(rdata)
193    def get_unit_id(self):
194        return self.unit_id
195    def get_rr_name(self):
196        return self.rr_name
197    def get_rr_class(self):
198        return self.rr_class
199    def get_ttl(self):
200        return self.ttl
201    def get_rdlength(self):
202        return self.rdlength
203    def get_rdata(self):
204        return self.rdata
205    def rawData(self):
206        return self.rr_name + pack('!HHLH',self.rr_type, self.rr_class, self.ttl, self.rdlength) + self.rdata
207
208class NBNodeStatusResponse(NBResourceRecord):
209    def __init__(self, data = 0):
210        NBResourceRecord.__init__(self,data)
211        self.num_names = 0
212        self.node_names = [ ]
213        self.statstics = ''
214        self.mac = '00-00-00-00-00-00'
215        try:
216            if data:
217                self._data = self.get_rdata()
218                self.num_names = unpack('>B',self._data[:1])[0]
219                offset = 1
220                for i in range(0, self.num_names):
221                    name = self._data[offset:offset + 15]
222                    type,flags = unpack('>BH', self._data[offset + 15: offset + 18])
223                    offset += 18
224                    self.node_names.append(NBNodeEntry(name, type ,flags))
225                self.set_mac_in_hexa(self.get_unit_id())
226        except Exception:
227            raise NetBIOSError( 'Wrong packet format ' )
228
229    def set_mac_in_hexa(self, data):
230        data_aux = ''
231        for d in data:
232            if data_aux == '':
233                data_aux = '%02x' % ord(d)
234            else:
235                data_aux += '-%02x' % ord(d)
236        self.mac = string.upper(data_aux)
237
238    def get_num_names(self):
239        return self.num_names
240    def get_mac(self):
241        return self.mac
242    def set_num_names(self, num):
243        self.num_names = num
244    def get_node_names(self):
245        return self.node_names
246    def add_node_name(self,node_names):
247        self.node_names.append(node_names)
248        self.num_names += 1
249    def rawData(self):
250        res = pack('!B', self.num_names )
251        for i in range(0, self.num_names):
252            res += self.node_names[i].rawData()
253
254class NBPositiveNameQueryResponse(NBResourceRecord):
255    def __init__(self, data = 0):
256        NBResourceRecord.__init__(self, data)
257        self.addr_entries = [ ]
258        if data:
259                self._data = self.get_rdata()
260                _qn_length, qn_name, qn_scope = decode_name(data)
261                self._netbios_name = string.rstrip(qn_name[:-1]) + qn_scope
262                self._name_type = ord(qn_name[-1])
263                self._nb_flags = unpack('!H', self._data[:2])
264                offset = 2
265                while offset<len(self._data):
266                    self.addr_entries.append('%d.%d.%d.%d' % unpack('4B', (self._data[offset:offset+4])))
267                    offset += 4
268
269    def get_netbios_name(self):
270        return self._netbios_name
271
272    def get_name_type(self):
273        return self._name_type
274
275    def get_addr_entries(self):
276        return self.addr_entries
277
278class NetBIOSPacket:
279    """ This is a packet as defined in RFC 1002 """
280    def __init__(self, data = 0):
281        self.name_trn_id = 0x0  # Transaction ID for Name Service Transaction.
282                                #   Requestor places a unique value for each active
283                                #   transaction.  Responder puts NAME_TRN_ID value
284                                #   from request packet in response packet.
285        self.opcode = 0         # Packet type code
286        self.nm_flags = 0       # Flags for operation
287        self.rcode = 0          # Result codes of request.
288        self.qdcount = 0        # Unsigned 16 bit integer specifying the number of entries in the question section of a Name
289        self.ancount = 0        # Unsigned 16 bit integer specifying the number of
290                                # resource records in the answer section of a Name
291                                # Service packet.
292        self.nscount = 0        # Unsigned 16 bit integer specifying the number of
293                                # resource records in the authority section of a
294                                # Name Service packet.
295        self.arcount = 0        # Unsigned 16 bit integer specifying the number of
296                                # resource records in the additional records
297                                # section of a Name Service packeT.
298        self.questions = ''
299        self.answers = ''
300        if data == 0:
301            self._data = ''
302        else:
303            try:
304                self._data = data
305                self.opcode = ord(data[2]) >> 3
306                self.nm_flags = ((ord(data[2]) & 0x3) << 4) | ((ord(data[3]) & 0xf0) >> 4)
307                self.name_trn_id = unpack('>H', self._data[:2])[0]
308                self.rcode = ord(data[3]) & 0x0f
309                self.qdcount = unpack('>H', self._data[4:6])[0]
310                self.ancount = unpack('>H', self._data[6:8])[0]
311                self.nscount = unpack('>H', self._data[8:10])[0]
312                self.arcount = unpack('>H', self._data[10:12])[0]
313                self.answers = self._data[12:]
314            except Exception:
315                raise NetBIOSError( 'Wrong packet format ' )
316
317    def set_opcode(self, opcode):
318        self.opcode = opcode
319    def set_trn_id(self, trn):
320        self.name_trn_id = trn
321    def set_nm_flags(self, nm_flags):
322        self.nm_flags = nm_flags
323    def set_rcode(self, rcode):
324        self.rcode = rcode
325    def addQuestion(self, question, qtype, qclass):
326        self.qdcount += 1
327        self.questions += question + pack('!HH',qtype,qclass)
328    def get_trn_id(self):
329        return self.name_trn_id
330    def get_rcode(self):
331        return self.rcode
332    def get_nm_flags(self):
333        return self.nm_flags
334    def get_opcode(self):
335        return self.opcode
336    def get_qdcount(self):
337        return self.qdcount
338    def get_ancount(self):
339        return self.ancount
340    def get_nscount(self):
341        return self.nscount
342    def get_arcount(self):
343        return self.arcount
344    def rawData(self):
345        secondWord = self.opcode << 11
346        secondWord |= self.nm_flags << 4
347        secondWord |= self.rcode
348        data = pack('!HHHHHH', self.name_trn_id, secondWord , self.qdcount, self.ancount, self.nscount, self.arcount) + self.questions + self.answers
349        return data
350    def get_answers(self):
351        return self.answers
352
353class NBHostEntry:
354
355    def __init__(self, nbname, nametype, ip):
356        self.__nbname = nbname
357        self.__nametype = nametype
358        self.__ip = ip
359
360    def get_nbname(self):
361        return self.__nbname
362
363    def get_nametype(self):
364        return self.__nametype
365
366    def get_ip(self):
367        return self.__ip
368
369    def __repr__(self):
370        return '<NBHostEntry instance: NBname="' + self.__nbname + '", IP="' + self.__ip + '">'
371
372class NBNodeEntry:
373
374    def __init__(self, nbname, nametype, flags):
375        self.__nbname = string.ljust(nbname,17)
376        self.__nametype = nametype
377        self.__flags = flags
378        self.__isgroup = flags & 0x8000
379        self.__nodetype = flags & 0x6000
380        self.__deleting = flags & 0x1000
381        self.__isconflict = flags & 0x0800
382        self.__isactive = flags & 0x0400
383        self.__ispermanent = flags & 0x0200
384
385    def get_nbname(self):
386        return self.__nbname
387
388    def get_nametype(self):
389        return self.__nametype
390
391    def is_group(self):
392        return self.__isgroup
393
394    def get_nodetype(self):
395        return self.__nodetype
396
397    def is_deleting(self):
398        return self.__deleting
399
400    def is_conflict(self):
401        return self.__isconflict
402
403    def is_active(self):
404        return self.__isactive
405
406    def is_permanent(self):
407        return self.__ispermanent
408
409    def set_nbname(self, name):
410        self.__nbname = string.ljust(name,17)
411
412    def set_nametype(self, type):
413        self.__nametype = type
414
415    def set_flags(self,flags):
416        self.__flags = flags
417
418    def __repr__(self):
419        s = '<NBNodeEntry instance: NBname="' + self.__nbname + '" NameType="' + NAME_TYPES[self.__nametype] + '"'
420        if self.__isactive:
421            s += ' ACTIVE'
422        if self.__isgroup:
423            s += ' GROUP'
424        if self.__isconflict:
425            s += ' CONFLICT'
426        if self.__deleting:
427            s += ' DELETING'
428        return s
429    def rawData(self):
430        return self.__nbname + pack('!BH',self.__nametype, self.__flags)
431
432
433class NetBIOS:
434
435    # Creates a NetBIOS instance without specifying any default NetBIOS domain nameserver.
436    # All queries will be sent through the servport.
437    def __init__(self, servport = NETBIOS_NS_PORT):
438        self.__servport = NETBIOS_NS_PORT
439        self.__nameserver = None
440        self.__broadcastaddr = BROADCAST_ADDR
441        self.mac = '00-00-00-00-00-00'
442
443    def _setup_connection(self, dstaddr):
444        port = randint(10000, 60000)
445        af, socktype, proto, _canonname, _sa = socket.getaddrinfo(dstaddr, port, socket.AF_INET, socket.SOCK_DGRAM)[0]
446        s = socket.socket(af, socktype, proto)
447        has_bind = 1
448        for _i in range(0, 10):
449        # We try to bind to a port for 10 tries
450            try:
451                s.bind(( INADDR_ANY, randint(10000, 60000) ))
452                s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
453                has_bind = 1
454            except socket.error:
455                pass
456        if not has_bind:
457            raise NetBIOSError, ( 'Cannot bind to a good UDP port', ERRCLASS_OS, errno.EAGAIN )
458        self.__sock = s
459
460    # Set the default NetBIOS domain nameserver.
461    def set_nameserver(self, nameserver):
462        self.__nameserver = nameserver
463
464    # Return the default NetBIOS domain nameserver, or None if none is specified.
465    def get_nameserver(self):
466        return self.__nameserver
467
468    # Set the broadcast address to be used for query.
469    def set_broadcastaddr(self, broadcastaddr):
470        self.__broadcastaddr = broadcastaddr
471
472    # Return the broadcast address to be used, or BROADCAST_ADDR if default broadcast address is used.
473    def get_broadcastaddr(self):
474        return self.__broadcastaddr
475
476    # Returns a NBPositiveNameQueryResponse instance containing the host information for nbname.
477    # If a NetBIOS domain nameserver has been specified, it will be used for the query.
478    # Otherwise, the query is broadcasted on the broadcast address.
479    def gethostbyname(self, nbname, qtype = TYPE_WORKSTATION, scope = None, timeout = 1):
480        return self.__queryname(nbname, self.__nameserver, qtype, scope, timeout)
481
482    # Returns a list of NBNodeEntry instances containing node status information for nbname.
483    # If destaddr contains an IP address, then this will become an unicast query on the destaddr.
484    # Raises NetBIOSTimeout if timeout (in secs) is reached.
485    # Raises NetBIOSError for other errors
486    def getnodestatus(self, nbname, destaddr = None, type = TYPE_WORKSTATION, scope = None, timeout = 1):
487        if destaddr:
488            return self.__querynodestatus(nbname, destaddr, type, scope, timeout)
489        else:
490            return self.__querynodestatus(nbname, self.__nameserver, type, scope, timeout)
491
492    def getnetbiosname(self, ip):
493        entries = self.getnodestatus('*',ip)
494        entries = filter(lambda x:x.get_nametype() == TYPE_SERVER, entries)
495        return entries[0].get_nbname().strip()
496
497    def getmacaddress(self):
498        return self.mac
499
500    def __queryname(self, nbname, destaddr, qtype, scope, timeout, retries = 0):
501        self._setup_connection(destaddr)
502        trn_id = randint(1, 32000)
503        p = NetBIOSPacket()
504        p.set_trn_id(trn_id)
505        netbios_name = nbname.upper()
506        qn_label = encode_name(netbios_name, qtype, scope)
507        p.addQuestion(qn_label, QUESTION_TYPE_NB, QUESTION_CLASS_IN)
508        p.set_nm_flags(NM_FLAGS_RD)
509        if not destaddr:
510            p.set_nm_flags(p.get_nm_flags() | NM_FLAGS_BROADCAST)
511            destaddr = self.__broadcastaddr
512        req = p.rawData()
513
514        tries = retries
515        while 1:
516            self.__sock.sendto(req, ( destaddr, self.__servport ))
517            try:
518                ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
519                if not ready:
520                    if tries:
521                        # Retry again until tries == 0
522                        tries -= 1
523                    else:
524                        raise NetBIOSTimeout
525                else:
526                    data, _ = self.__sock.recvfrom(65536, 0)
527
528                    res = NetBIOSPacket(data)
529                    if res.get_trn_id() == p.get_trn_id():
530                        if res.get_rcode():
531                            if res.get_rcode() == 0x03:
532                                return None
533                            else:
534                                raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, res.get_rcode() )
535
536                        if res.get_ancount() != 1:
537                            raise NetBIOSError( 'Malformed response')
538
539                        return NBPositiveNameQueryResponse(res.get_answers())
540            except select.error, ex:
541                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
542                    raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
543                raise
544
545
546    def __querynodestatus(self, nbname, destaddr, type, scope, timeout):
547        self._setup_connection(destaddr)
548        trn_id = randint(1, 32000)
549        p = NetBIOSPacket()
550        p.set_trn_id(trn_id)
551        netbios_name = string.upper(nbname)
552        qn_label = encode_name(netbios_name, type, scope)
553        p.addQuestion(qn_label, QUESTION_TYPE_NBSTAT, QUESTION_CLASS_IN)
554
555        if not destaddr:
556            p.set_nm_flags(NM_FLAGS_BROADCAST)
557            destaddr = self.__broadcastaddr
558        req = p.rawData()
559        tries = 3
560        while 1:
561            try:
562                self.__sock.sendto(req, 0, ( destaddr, self.__servport ))
563                ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
564                if not ready:
565                    if tries:
566                        # Retry again until tries == 0
567                        tries -= 1
568                    else:
569                        raise NetBIOSTimeout
570                else:
571                    try:
572                        data, _ = self.__sock.recvfrom(65536, 0)
573                    except Exception, e:
574                        raise NetBIOSError, "recvfrom error: %s" % str(e)
575                    self.__sock.close()
576                    res = NetBIOSPacket(data)
577                    if res.get_trn_id() == p.get_trn_id():
578                        if res.get_rcode():
579                            if res.get_rcode() == 0x03:
580                                # I'm just guessing here
581                                raise NetBIOSError, "Cannot get data from server"
582                            else:
583                                raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, res.get_rcode() )
584                        answ = NBNodeStatusResponse(res.get_answers())
585                        self.mac = answ.get_mac()
586                        return answ.get_node_names()
587            except select.error, ex:
588                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
589                    raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
590            except socket.error, ex:
591                raise NetBIOSError, 'Connection error: %s' % str(ex)
592
593# Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
594def encode_name(name, type, scope):
595    if name == '*':
596        name += '\0' * 15
597    elif len(name) > 15:
598        name = name[:15] + chr(type)
599    else:
600        name = string.ljust(name, 15) + chr(type)
601
602    encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
603    if scope:
604        encoded_scope = ''
605        for s in string.split(scope, '.'):
606            encoded_scope = encoded_scope + chr(len(s)) + s
607        return encoded_name + encoded_scope + '\0'
608    else:
609        return encoded_name + '\0'
610
611# Internal method for use in encode_name()
612def _do_first_level_encoding(m):
613    s = ord(m.group(0))
614    return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
615
616def decode_name(name):
617    name_length = ord(name[0])
618    assert name_length == 32
619
620    decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
621    if name[33] == '\0':
622        return 34, decoded_name, ''
623    else:
624        decoded_domain = ''
625        offset = 34
626        while 1:
627            domain_length = ord(name[offset])
628            if domain_length == 0:
629                break
630            decoded_domain = '.' + name[offset:offset + domain_length]
631            offset += domain_length
632        return offset + 1, decoded_name, decoded_domain
633
634def _do_first_level_decoding(m):
635    s = m.group(0)
636    return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
637
638
639
640class NetBIOSSessionPacket:
641    def __init__(self, data = 0):
642        self.type = 0x0
643        self.flags = 0x0
644        self.length = 0x0
645        if data == 0:
646            self._trailer = ''
647        else:
648            try:
649                self.type = ord(data[0])
650                if self.type == NETBIOS_SESSION_MESSAGE:
651                    self.length = ord(data[1]) << 16 | (unpack('!H', data[2:4])[0])
652                else:
653                    self.flags = ord(data[1])
654                    self.length = unpack('!H', data[2:4])[0]
655
656                self._trailer = data[4:]
657            except:
658                raise NetBIOSError( 'Wrong packet format ' )
659
660    def set_type(self, type):
661        self.type = type
662    def get_type(self):
663        return self.type
664    def rawData(self):
665        if self.type == NETBIOS_SESSION_MESSAGE:
666            data = pack('!BBH',self.type,self.length >> 16,self.length & 0xFFFF) + self._trailer
667        else:
668            data = pack('!BBH',self.type,self.flags,self.length) + self._trailer
669        return data
670    def set_trailer(self,data):
671        self._trailer = data
672        self.length = len(data)
673    def get_length(self):
674        return self.length
675    def get_trailer(self):
676        return self._trailer
677
678class NetBIOSSession:
679    def __init__(self, myname, remote_name, remote_host, remote_type = TYPE_SERVER, sess_port = NETBIOS_SESSION_PORT, timeout = None, local_type = TYPE_WORKSTATION, sock = None):
680        if len(myname) > 15:
681            self.__myname = string.upper(myname[:15])
682        else:
683            self.__myname = string.upper(myname)
684        self.__local_type = local_type
685
686        assert remote_name
687        # if destination port SMB_SESSION_PORT and remote name *SMBSERVER, we're changing it to its IP address
688        # helping solving the client mistake ;)
689        if remote_name == '*SMBSERVER' and sess_port == SMB_SESSION_PORT:
690            remote_name = remote_host
691        # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best
692        if remote_name == '*SMBSERVER':
693            nb = NetBIOS()
694
695            try:
696                res = nb.getnetbiosname(remote_host)
697            except:
698                res = None
699                pass
700
701            if res is not None:
702                remote_name = res
703
704        if len(remote_name) > 15:
705            self.__remote_name = string.upper(remote_name[:15])
706        else:
707            self.__remote_name = string.upper(remote_name)
708        self.__remote_type = remote_type
709
710        self.__remote_host = remote_host
711
712        if sock is not None:
713            # We are acting as a server
714            self._sock = sock
715        else:
716            self._sock = self._setup_connection((remote_host, sess_port))
717
718        if sess_port == NETBIOS_SESSION_PORT:
719            self._request_session(remote_type, local_type, timeout)
720
721    def get_myname(self):
722        return self.__myname
723
724    def get_mytype(self):
725        return self.__local_type
726
727    def get_remote_host(self):
728        return self.__remote_host
729
730    def get_remote_name(self):
731        return self.__remote_name
732
733    def get_remote_type(self):
734        return self.__remote_type
735
736    def close(self):
737        self._sock.close()
738
739    def get_socket(self):
740        return self._sock
741
742class NetBIOSUDPSessionPacket(Structure):
743    TYPE_DIRECT_UNIQUE = 16
744    TYPE_DIRECT_GROUP  = 17
745
746    FLAGS_MORE_FRAGMENTS = 1
747    FLAGS_FIRST_FRAGMENT = 2
748    FLAGS_B_NODE         = 0
749
750    structure = (
751        ('Type','B=16'),    # Direct Unique Datagram
752        ('Flags','B=2'),    # FLAGS_FIRST_FRAGMENT
753        ('ID','<H'),
754        ('_SourceIP','>L'),
755        ('SourceIP','"'),
756        ('SourcePort','>H=138'),
757        ('DataLegth','>H-Data'),
758        ('Offset','>H=0'),
759        ('SourceName','z'),
760        ('DestinationName','z'),
761        ('Data',':'),
762    )
763
764    def getData(self):
765        addr = self['SourceIP'].split('.')
766        addr = [int(x) for x in addr]
767        addr = (((addr[0] << 8) + addr[1] << 8) + addr[2] << 8) + addr[3]
768        self['_SourceIP'] = addr
769        return Structure.getData(self)
770
771    def get_trailer(self):
772        return self['Data']
773
774class NetBIOSUDPSession(NetBIOSSession):
775    def _setup_connection(self, peer):
776        af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_DGRAM)[0]
777        sock = socket.socket(af, socktype, proto)
778        sock.connect(sa)
779
780        sock = socket.socket(af, socktype, proto)
781        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
782        sock.bind((INADDR_ANY, 138))
783        self.peer = peer
784        return sock
785
786    def _request_session(self, remote_type, local_type, timeout = None):
787        pass
788
789    def next_id(self):
790        if hasattr(self, '__dgram_id'):
791            answer = self.__dgram_id
792        else:
793            self.__dgram_id = randint(1,65535)
794            answer = self.__dgram_id
795        self.__dgram_id += 1
796        return answer
797
798    def send_packet(self, data):
799        # Yes... I know...
800        self._sock.connect(self.peer)
801
802        p = NetBIOSUDPSessionPacket()
803        p['ID'] = self.next_id()
804        p['SourceIP'] = self._sock.getsockname()[0]
805        p['SourceName'] = encode_name(self.get_myname(), self.get_mytype(), '')[:-1]
806        p['DestinationName'] = encode_name(self.get_remote_name(), self.get_remote_type(), '')[:-1]
807        p['Data'] = data
808
809        self._sock.sendto(str(p), self.peer)
810        self._sock.close()
811
812        self._sock = self._setup_connection(self.peer)
813
814    def recv_packet(self, timeout = None):
815        # The next loop is a workaround for a bigger problem:
816        # When data reaches higher layers, the lower headers are lost,
817        # and with them, for example, the source IP. Hence, SMB users
818        # can't know where packets are comming from... we need a better
819        # solution, right now, we will filter everything except packets
820        # coming from the remote_host specified in __init__()
821
822        while 1:
823            data, peer = self._sock.recvfrom(8192)
824#            print "peer: %r  self.peer: %r" % (peer, self.peer)
825            if peer == self.peer: break
826
827        return NetBIOSUDPSessionPacket(data)
828
829class NetBIOSTCPSession(NetBIOSSession):
830    def __init__(self, myname, remote_name, remote_host, remote_type = TYPE_SERVER, sess_port = NETBIOS_SESSION_PORT, timeout = None, local_type = TYPE_WORKSTATION, sock = None, select_poll = False):
831        self.__select_poll = select_poll
832        if self.__select_poll:
833            self.read_function = self.polling_read
834        else:
835            self.read_function = self.non_polling_read
836        NetBIOSSession.__init__(self, myname, remote_name, remote_host, remote_type = remote_type, sess_port = sess_port, timeout = timeout, local_type = local_type, sock=sock)
837
838
839    def _setup_connection(self, peer):
840        try:
841            af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_STREAM)[0]
842            sock = socket.socket(af, socktype, proto)
843            sock.connect(sa)
844        except socket.error, e:
845            raise socket.error("Connection error (%s:%s)" % (peer[0], peer[1]), e)
846        return sock
847
848    def send_packet(self, data):
849        p = NetBIOSSessionPacket()
850        p.set_type(NETBIOS_SESSION_MESSAGE)
851        p.set_trailer(data)
852        self._sock.send(p.rawData())
853
854    def recv_packet(self, timeout = None):
855        data = self.__read(timeout)
856        return NetBIOSSessionPacket(data)
857
858    def _request_session(self, remote_type, local_type, timeout = None):
859        p = NetBIOSSessionPacket()
860        remote_name = encode_name(self.get_remote_name(), remote_type, '')
861        myname = encode_name(self.get_myname(), local_type, '')
862        p.set_type(NETBIOS_SESSION_REQUEST)
863        p.set_trailer(remote_name + myname)
864
865        self._sock.send(p.rawData())
866        while 1:
867            p = self.recv_packet(timeout)
868            if p.get_type() == NETBIOS_SESSION_NEGATIVE_RESPONSE:
869                raise NetBIOSError, ( 'Cannot request session', ERRCLASS_SESSION, ord(p.get_trailer()[0]) )
870            elif p.get_type() == NETBIOS_SESSION_POSITIVE_RESPONSE:
871                break
872            else:
873                # Ignore all other messages, most probably keepalive messages
874                pass
875
876    def polling_read(self, read_length, timeout):
877        data = ''
878        if timeout is None:
879            timeout = 3600
880
881        time_left = timeout
882        CHUNK_TIME = 0.025
883        bytes_left = read_length
884
885        while bytes_left > 0:
886            try:
887                ready, _, _ = select.select([self._sock.fileno() ], [ ], [ ], 0)
888
889                if not ready:
890                    if time_left <= 0:
891                        raise NetBIOSTimeout
892                    else:
893                        time.sleep(CHUNK_TIME)
894                        time_left -= CHUNK_TIME
895                        continue
896
897                received = self._sock.recv(bytes_left)
898                if len(received) == 0:
899                    raise NetBIOSError, ( 'Error while reading from remote', ERRCLASS_OS, None)
900
901                data = data + received
902                bytes_left = read_length - len(data)
903            except select.error, ex:
904                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
905                    raise NetBIOSError, ( 'Error occurs while reading from remote', ERRCLASS_OS, ex[0] )
906
907        return data
908
909    def non_polling_read(self, read_length, timeout):
910        data = ''
911        bytes_left = read_length
912
913        while bytes_left > 0:
914            try:
915                ready, _, _ = select.select([self._sock.fileno() ], [ ], [ ], timeout)
916
917                if not ready:
918                        raise NetBIOSTimeout
919
920                received = self._sock.recv(bytes_left)
921                if len(received) == 0:
922                    raise NetBIOSError, ( 'Error while reading from remote', ERRCLASS_OS, None)
923
924                data = data + received
925                bytes_left = read_length - len(data)
926            except select.error, ex:
927                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
928                    raise NetBIOSError, ( 'Error occurs while reading from remote', ERRCLASS_OS, ex[0] )
929
930        return data
931
932    def __read(self, timeout = None):
933        data = self.read_function(4, timeout)
934        type, flags, length = unpack('>ccH', data)
935        if ord(type) == NETBIOS_SESSION_MESSAGE:
936            length |= ord(flags) << 16
937        else:
938            if ord(flags) & 0x01:
939                length |= 0x10000
940        data2 = self.read_function(length, timeout)
941
942        return data + data2
943
944ERRCLASS_QUERY = 0x00
945ERRCLASS_SESSION = 0xf0
946ERRCLASS_OS = 0xff
947
948QUERY_ERRORS = { 0x01: 'Request format error. Please file a bug report.',
949                 0x02: 'Internal server error',
950                 0x03: 'Name does not exist',
951                 0x04: 'Unsupported request',
952                 0x05: 'Request refused'
953                 }
954
955SESSION_ERRORS = { 0x80: 'Not listening on called name',
956                   0x81: 'Not listening for calling name',
957                   0x82: 'Called name not present',
958                   0x83: 'Sufficient resources',
959                   0x8f: 'Unspecified error'
960                   }
961
962def main():
963    def get_netbios_host_by_name(name):
964        n = NetBIOS()
965        n.set_broadcastaddr('255.255.255.255') # To avoid use "<broadcast>" in socket
966        for qtype in (TYPE_WORKSTATION, TYPE_CLIENT, TYPE_SERVER, TYPE_DOMAIN_MASTER, TYPE_DOMAIN_CONTROLLER):
967            try:
968                addrs = n.gethostbyname(name, qtype = qtype).get_addr_entries()
969            except NetBIOSTimeout:
970                continue
971            else:
972                return addrs
973        raise Exception("Host not found")
974
975
976    n = get_netbios_host_by_name("some-host")
977    print n
978
979if __name__ == '__main__':
980    main()
981