123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980 |
- # Copyright (c) 2003-2016 CORE Security Technologies
- #
- # This software is provided under under a slightly modified version
- # of the Apache Software License. See the accompanying LICENSE file
- # for more information.
- #
- # -*- mode: python; tab-width: 4 -*-
- #
- # Copyright (C) 2001 Michael Teo <michaelteo@bigfoot.com>
- # nmb.py - NetBIOS library
- #
- # This software is provided 'as-is', without any express or implied warranty.
- # In no event will the author be held liable for any damages arising from the
- # use of this software.
- #
- # Permission is granted to anyone to use this software for any purpose,
- # including commercial applications, and to alter it and redistribute it
- # freely, subject to the following restrictions:
- #
- # 1. The origin of this software must not be misrepresented; you must not
- # claim that you wrote the original software. If you use this software
- # in a product, an acknowledgment in the product documentation would be
- # appreciated but is not required.
- #
- # 2. Altered source versions must be plainly marked as such, and must not be
- # misrepresented as being the original software.
- #
- # 3. This notice cannot be removed or altered from any source distribution.
- #
- # Altered source done by Alberto Solino (@agsolino)
- import socket
- import string
- import re
- import select
- import errno
- from random import randint
- from struct import pack, unpack
- import time
- from structure import Structure
- CVS_REVISION = '$Revision: 526 $'
- # Taken from socket module reference
- INADDR_ANY = '0.0.0.0'
- BROADCAST_ADDR = '<broadcast>'
- # Default port for NetBIOS name service
- NETBIOS_NS_PORT = 137
- # Default port for NetBIOS session service
- NETBIOS_SESSION_PORT = 139
- # Default port for SMB session service
- SMB_SESSION_PORT = 445
- # Owner Node Type Constants
- NODE_B = 0x0000
- NODE_P = 0x2000
- NODE_M = 0x4000
- NODE_RESERVED = 0x6000
- NODE_GROUP = 0x8000
- NODE_UNIQUE = 0x0
- # Name Type Constants
- TYPE_UNKNOWN = 0x01
- TYPE_WORKSTATION = 0x00
- TYPE_CLIENT = 0x03
- TYPE_SERVER = 0x20
- TYPE_DOMAIN_MASTER = 0x1B
- TYPE_DOMAIN_CONTROLLER = 0x1C
- TYPE_MASTER_BROWSER = 0x1D
- TYPE_BROWSER = 0x1E
- TYPE_NETDDE = 0x1F
- TYPE_STATUS = 0x21
- # Opcodes values
- OPCODE_QUERY = 0
- OPCODE_REGISTRATION = 0x5
- OPCODE_RELEASE = 0x6
- OPCODE_WACK = 0x7
- OPCODE_REFRESH = 0x8
- OPCODE_REQUEST = 0
- OPCODE_RESPONSE = 0x10
- # NM_FLAGS
- NM_FLAGS_BROADCAST = 0x1
- NM_FLAGS_UNICAST = 0
- NM_FLAGS_RA = 0x8
- NM_FLAGS_RD = 0x10
- NM_FLAGS_TC = 0x20
- NM_FLAGS_AA = 0x40
- # QUESTION_TYPE
- QUESTION_TYPE_NB = 0x20 # NetBIOS general Name Service Resource Record
- QUESTION_TYPE_NBSTAT = 0x21 # NetBIOS NODE STATUS Resource Record
- # QUESTION_CLASS
- QUESTION_CLASS_IN = 0x1 # Internet class
- # RR_TYPE Resource Record Type code
- RR_TYPE_A = 0x1 # IP address Resource Record
- RR_TYPE_NS = 0x2 # Name Server Resource Record
- RR_TYPE_NULL = 0xA # NULL Resource Record
- RR_TYPE_NB = 0x20 # NetBIOS general Name Service Resource Record
- RR_TYPE_NBSTAT = 0x21 # NetBIOS NODE STATUS Resource Record
- # Resource Record Class
- RR_CLASS_IN = 1 # Internet class
- # RCODE values
- RCODE_FMT_ERR = 0x1 # Format Error. Request was invalidly formatted.
- RCODE_SRV_ERR = 0x2 # Server failure. Problem with NBNS, cannot process name.
- RCODE_IMP_ERR = 0x4 # Unsupported request error. Allowable only for challenging NBNS when gets an Update type
- # registration request.
- RCODE_RFS_ERR = 0x5 # Refused error. For policy reasons server will not register this name from this host.
- RCODE_ACT_ERR = 0x6 # Active error. Name is owned by another node.
- RCODE_CFT_ERR = 0x7 # Name in conflict error. A UNIQUE name is owned by more than one node.
- # NAME_FLAGS
- NAME_FLAGS_PRM = 0x0200 # Permanent Name Flag. If one (1) then entry is for the permanent node name. Flag is zero
- # (0) for all other names.
- NAME_FLAGS_ACT = 0x0400 # Active Name Flag. All entries have this flag set to one (1).
- NAME_FLAG_CNF = 0x0800 # Conflict Flag. If one (1) then name on this node is in conflict.
- NAME_FLAG_DRG = 0x1000 # Deregister Flag. If one (1) then this name is in the process of being deleted.
- NAME_TYPES = { TYPE_UNKNOWN: 'Unknown', TYPE_WORKSTATION: 'Workstation', TYPE_CLIENT: 'Client',
- TYPE_SERVER: 'Server', TYPE_MASTER_BROWSER: 'Master Browser', TYPE_BROWSER: 'Browser Server',
- TYPE_DOMAIN_MASTER: 'Domain Master' , TYPE_NETDDE: 'NetDDE Server'}
- # NetBIOS Session Types
- NETBIOS_SESSION_MESSAGE = 0x0
- NETBIOS_SESSION_REQUEST = 0x81
- NETBIOS_SESSION_POSITIVE_RESPONSE = 0x82
- NETBIOS_SESSION_NEGATIVE_RESPONSE = 0x83
- NETBIOS_SESSION_RETARGET_RESPONSE = 0x84
- NETBIOS_SESSION_KEEP_ALIVE = 0x85
- def strerror(errclass, errcode):
- if errclass == ERRCLASS_OS:
- return 'OS Error', str(errcode)
- elif errclass == ERRCLASS_QUERY:
- return 'Query Error', QUERY_ERRORS.get(errcode, 'Unknown error')
- elif errclass == ERRCLASS_SESSION:
- return 'Session Error', SESSION_ERRORS.get(errcode, 'Unknown error')
- else:
- return 'Unknown Error Class', 'Unknown Error'
-
-
- class NetBIOSError(Exception): pass
- class NetBIOSTimeout(Exception):
- def __init__(self, message = 'The NETBIOS connection with the remote host timed out.'):
- Exception.__init__(self, message)
- class NBResourceRecord:
- def __init__(self, data = 0):
- self._data = data
- try:
- if self._data:
- self.rr_name = (re.split('\x00',data))[0]
- offset = len(self.rr_name)+1
- self.rr_type = unpack('>H', self._data[offset:offset+2])[0]
- self.rr_class = unpack('>H', self._data[offset+2: offset+4])[0]
- self.ttl = unpack('>L',self._data[offset+4:offset+8])[0]
- self.rdlength = unpack('>H', self._data[offset+8:offset+10])[0]
- self.rdata = self._data[offset+10:offset+10+self.rdlength]
- offset = self.rdlength - 2
- self.unit_id = data[offset:offset+6]
- else:
- self.rr_name = ''
- self.rr_type = 0
- self.rr_class = 0
- self.ttl = 0
- self.rdlength = 0
- self.rdata = ''
- self.unit_id = ''
- except Exception:
- raise NetBIOSError( 'Wrong packet format ' )
- def set_rr_name(self, name):
- self.rr_name = name
- def set_rr_type(self, name):
- self.rr_type = name
- def set_rr_class(self,cl):
- self.rr_class = cl
- def set_ttl(self,ttl):
- self.ttl = ttl
- def set_rdata(self,rdata):
- self.rdata = rdata
- self.rdlength = len(rdata)
- def get_unit_id(self):
- return self.unit_id
- def get_rr_name(self):
- return self.rr_name
- def get_rr_class(self):
- return self.rr_class
- def get_ttl(self):
- return self.ttl
- def get_rdlength(self):
- return self.rdlength
- def get_rdata(self):
- return self.rdata
- def rawData(self):
- return self.rr_name + pack('!HHLH',self.rr_type, self.rr_class, self.ttl, self.rdlength) + self.rdata
- class NBNodeStatusResponse(NBResourceRecord):
- def __init__(self, data = 0):
- NBResourceRecord.__init__(self,data)
- self.num_names = 0
- self.node_names = [ ]
- self.statstics = ''
- self.mac = '00-00-00-00-00-00'
- try:
- if data:
- self._data = self.get_rdata()
- self.num_names = unpack('>B',self._data[:1])[0]
- offset = 1
- for i in range(0, self.num_names):
- name = self._data[offset:offset + 15]
- type,flags = unpack('>BH', self._data[offset + 15: offset + 18])
- offset += 18
- self.node_names.append(NBNodeEntry(name, type ,flags))
- self.set_mac_in_hexa(self.get_unit_id())
- except Exception:
- raise NetBIOSError( 'Wrong packet format ' )
- def set_mac_in_hexa(self, data):
- data_aux = ''
- for d in data:
- if data_aux == '':
- data_aux = '%02x' % ord(d)
- else:
- data_aux += '-%02x' % ord(d)
- self.mac = string.upper(data_aux)
- def get_num_names(self):
- return self.num_names
- def get_mac(self):
- return self.mac
- def set_num_names(self, num):
- self.num_names = num
- def get_node_names(self):
- return self.node_names
- def add_node_name(self,node_names):
- self.node_names.append(node_names)
- self.num_names += 1
- def rawData(self):
- res = pack('!B', self.num_names )
- for i in range(0, self.num_names):
- res += self.node_names[i].rawData()
- class NBPositiveNameQueryResponse(NBResourceRecord):
- def __init__(self, data = 0):
- NBResourceRecord.__init__(self, data)
- self.addr_entries = [ ]
- if data:
- self._data = self.get_rdata()
- _qn_length, qn_name, qn_scope = decode_name(data)
- self._netbios_name = string.rstrip(qn_name[:-1]) + qn_scope
- self._name_type = ord(qn_name[-1])
- self._nb_flags = unpack('!H', self._data[:2])
- offset = 2
- while offset<len(self._data):
- self.addr_entries.append('%d.%d.%d.%d' % unpack('4B', (self._data[offset:offset+4])))
- offset += 4
-
- def get_netbios_name(self):
- return self._netbios_name
-
- def get_name_type(self):
- return self._name_type
-
- def get_addr_entries(self):
- return self.addr_entries
-
- class NetBIOSPacket:
- """ This is a packet as defined in RFC 1002 """
- def __init__(self, data = 0):
- self.name_trn_id = 0x0 # Transaction ID for Name Service Transaction.
- # Requestor places a unique value for each active
- # transaction. Responder puts NAME_TRN_ID value
- # from request packet in response packet.
- self.opcode = 0 # Packet type code
- self.nm_flags = 0 # Flags for operation
- self.rcode = 0 # Result codes of request.
- self.qdcount = 0 # Unsigned 16 bit integer specifying the number of entries in the question section of a Name
- self.ancount = 0 # Unsigned 16 bit integer specifying the number of
- # resource records in the answer section of a Name
- # Service packet.
- self.nscount = 0 # Unsigned 16 bit integer specifying the number of
- # resource records in the authority section of a
- # Name Service packet.
- self.arcount = 0 # Unsigned 16 bit integer specifying the number of
- # resource records in the additional records
- # section of a Name Service packeT.
- self.questions = ''
- self.answers = ''
- if data == 0:
- self._data = ''
- else:
- try:
- self._data = data
- self.opcode = ord(data[2]) >> 3
- self.nm_flags = ((ord(data[2]) & 0x3) << 4) | ((ord(data[3]) & 0xf0) >> 4)
- self.name_trn_id = unpack('>H', self._data[:2])[0]
- self.rcode = ord(data[3]) & 0x0f
- self.qdcount = unpack('>H', self._data[4:6])[0]
- self.ancount = unpack('>H', self._data[6:8])[0]
- self.nscount = unpack('>H', self._data[8:10])[0]
- self.arcount = unpack('>H', self._data[10:12])[0]
- self.answers = self._data[12:]
- except Exception:
- raise NetBIOSError( 'Wrong packet format ' )
-
- def set_opcode(self, opcode):
- self.opcode = opcode
- def set_trn_id(self, trn):
- self.name_trn_id = trn
- def set_nm_flags(self, nm_flags):
- self.nm_flags = nm_flags
- def set_rcode(self, rcode):
- self.rcode = rcode
- def addQuestion(self, question, qtype, qclass):
- self.qdcount += 1
- self.questions += question + pack('!HH',qtype,qclass)
- def get_trn_id(self):
- return self.name_trn_id
- def get_rcode(self):
- return self.rcode
- def get_nm_flags(self):
- return self.nm_flags
- def get_opcode(self):
- return self.opcode
- def get_qdcount(self):
- return self.qdcount
- def get_ancount(self):
- return self.ancount
- def get_nscount(self):
- return self.nscount
- def get_arcount(self):
- return self.arcount
- def rawData(self):
- secondWord = self.opcode << 11
- secondWord |= self.nm_flags << 4
- secondWord |= self.rcode
- data = pack('!HHHHHH', self.name_trn_id, secondWord , self.qdcount, self.ancount, self.nscount, self.arcount) + self.questions + self.answers
- return data
- def get_answers(self):
- return self.answers
- class NBHostEntry:
- def __init__(self, nbname, nametype, ip):
- self.__nbname = nbname
- self.__nametype = nametype
- self.__ip = ip
- def get_nbname(self):
- return self.__nbname
- def get_nametype(self):
- return self.__nametype
- def get_ip(self):
- return self.__ip
- def __repr__(self):
- return '<NBHostEntry instance: NBname="' + self.__nbname + '", IP="' + self.__ip + '">'
- class NBNodeEntry:
-
- def __init__(self, nbname, nametype, flags):
- self.__nbname = string.ljust(nbname,17)
- self.__nametype = nametype
- self.__flags = flags
- self.__isgroup = flags & 0x8000
- self.__nodetype = flags & 0x6000
- self.__deleting = flags & 0x1000
- self.__isconflict = flags & 0x0800
- self.__isactive = flags & 0x0400
- self.__ispermanent = flags & 0x0200
- def get_nbname(self):
- return self.__nbname
- def get_nametype(self):
- return self.__nametype
- def is_group(self):
- return self.__isgroup
- def get_nodetype(self):
- return self.__nodetype
- def is_deleting(self):
- return self.__deleting
- def is_conflict(self):
- return self.__isconflict
- def is_active(self):
- return self.__isactive
- def is_permanent(self):
- return self.__ispermanent
- def set_nbname(self, name):
- self.__nbname = string.ljust(name,17)
- def set_nametype(self, type):
- self.__nametype = type
- def set_flags(self,flags):
- self.__flags = flags
-
- def __repr__(self):
- s = '<NBNodeEntry instance: NBname="' + self.__nbname + '" NameType="' + NAME_TYPES[self.__nametype] + '"'
- if self.__isactive:
- s += ' ACTIVE'
- if self.__isgroup:
- s += ' GROUP'
- if self.__isconflict:
- s += ' CONFLICT'
- if self.__deleting:
- s += ' DELETING'
- return s
- def rawData(self):
- return self.__nbname + pack('!BH',self.__nametype, self.__flags)
- class NetBIOS:
- # Creates a NetBIOS instance without specifying any default NetBIOS domain nameserver.
- # All queries will be sent through the servport.
- def __init__(self, servport = NETBIOS_NS_PORT):
- self.__servport = NETBIOS_NS_PORT
- self.__nameserver = None
- self.__broadcastaddr = BROADCAST_ADDR
- self.mac = '00-00-00-00-00-00'
- def _setup_connection(self, dstaddr):
- port = randint(10000, 60000)
- af, socktype, proto, _canonname, _sa = socket.getaddrinfo(dstaddr, port, socket.AF_INET, socket.SOCK_DGRAM)[0]
- s = socket.socket(af, socktype, proto)
- has_bind = 1
- for _i in range(0, 10):
- # We try to bind to a port for 10 tries
- try:
- s.bind(( INADDR_ANY, randint(10000, 60000) ))
- s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
- has_bind = 1
- except socket.error:
- pass
- if not has_bind:
- raise NetBIOSError, ( 'Cannot bind to a good UDP port', ERRCLASS_OS, errno.EAGAIN )
- self.__sock = s
- # Set the default NetBIOS domain nameserver.
- def set_nameserver(self, nameserver):
- self.__nameserver = nameserver
- # Return the default NetBIOS domain nameserver, or None if none is specified.
- def get_nameserver(self):
- return self.__nameserver
- # Set the broadcast address to be used for query.
- def set_broadcastaddr(self, broadcastaddr):
- self.__broadcastaddr = broadcastaddr
- # Return the broadcast address to be used, or BROADCAST_ADDR if default broadcast address is used.
- def get_broadcastaddr(self):
- return self.__broadcastaddr
- # Returns a NBPositiveNameQueryResponse instance containing the host information for nbname.
- # If a NetBIOS domain nameserver has been specified, it will be used for the query.
- # Otherwise, the query is broadcasted on the broadcast address.
- def gethostbyname(self, nbname, qtype = TYPE_WORKSTATION, scope = None, timeout = 1):
- return self.__queryname(nbname, self.__nameserver, qtype, scope, timeout)
- # Returns a list of NBNodeEntry instances containing node status information for nbname.
- # If destaddr contains an IP address, then this will become an unicast query on the destaddr.
- # Raises NetBIOSTimeout if timeout (in secs) is reached.
- # Raises NetBIOSError for other errors
- def getnodestatus(self, nbname, destaddr = None, type = TYPE_WORKSTATION, scope = None, timeout = 1):
- if destaddr:
- return self.__querynodestatus(nbname, destaddr, type, scope, timeout)
- else:
- return self.__querynodestatus(nbname, self.__nameserver, type, scope, timeout)
- def getnetbiosname(self, ip):
- entries = self.getnodestatus('*',ip)
- entries = filter(lambda x:x.get_nametype() == TYPE_SERVER, entries)
- return entries[0].get_nbname().strip()
- def getmacaddress(self):
- return self.mac
- def __queryname(self, nbname, destaddr, qtype, scope, timeout, retries = 0):
- self._setup_connection(destaddr)
- trn_id = randint(1, 32000)
- p = NetBIOSPacket()
- p.set_trn_id(trn_id)
- netbios_name = nbname.upper()
- qn_label = encode_name(netbios_name, qtype, scope)
- p.addQuestion(qn_label, QUESTION_TYPE_NB, QUESTION_CLASS_IN)
- p.set_nm_flags(NM_FLAGS_RD)
- if not destaddr:
- p.set_nm_flags(p.get_nm_flags() | NM_FLAGS_BROADCAST)
- destaddr = self.__broadcastaddr
- req = p.rawData()
-
- tries = retries
- while 1:
- self.__sock.sendto(req, ( destaddr, self.__servport ))
- try:
- ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
- if not ready:
- if tries:
- # Retry again until tries == 0
- tries -= 1
- else:
- raise NetBIOSTimeout
- else:
- data, _ = self.__sock.recvfrom(65536, 0)
-
- res = NetBIOSPacket(data)
- if res.get_trn_id() == p.get_trn_id():
- if res.get_rcode():
- if res.get_rcode() == 0x03:
- return None
- else:
- raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, res.get_rcode() )
-
- if res.get_ancount() != 1:
- raise NetBIOSError( 'Malformed response')
-
- return NBPositiveNameQueryResponse(res.get_answers())
- except select.error, ex:
- if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
- raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
- raise
- def __querynodestatus(self, nbname, destaddr, type, scope, timeout):
- self._setup_connection(destaddr)
- trn_id = randint(1, 32000)
- p = NetBIOSPacket()
- p.set_trn_id(trn_id)
- netbios_name = string.upper(nbname)
- qn_label = encode_name(netbios_name, type, scope)
- p.addQuestion(qn_label, QUESTION_TYPE_NBSTAT, QUESTION_CLASS_IN)
- if not destaddr:
- p.set_nm_flags(NM_FLAGS_BROADCAST)
- destaddr = self.__broadcastaddr
- req = p.rawData()
- tries = 3
- while 1:
- try:
- self.__sock.sendto(req, 0, ( destaddr, self.__servport ))
- ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
- if not ready:
- if tries:
- # Retry again until tries == 0
- tries -= 1
- else:
- raise NetBIOSTimeout
- else:
- try:
- data, _ = self.__sock.recvfrom(65536, 0)
- except Exception, e:
- raise NetBIOSError, "recvfrom error: %s" % str(e)
- self.__sock.close()
- res = NetBIOSPacket(data)
- if res.get_trn_id() == p.get_trn_id():
- if res.get_rcode():
- if res.get_rcode() == 0x03:
- # I'm just guessing here
- raise NetBIOSError, "Cannot get data from server"
- else:
- raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, res.get_rcode() )
- answ = NBNodeStatusResponse(res.get_answers())
- self.mac = answ.get_mac()
- return answ.get_node_names()
- except select.error, ex:
- if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
- raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
- except socket.error, ex:
- raise NetBIOSError, 'Connection error: %s' % str(ex)
- # Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
- def encode_name(name, type, scope):
- if name == '*':
- name += '\0' * 15
- elif len(name) > 15:
- name = name[:15] + chr(type)
- else:
- name = string.ljust(name, 15) + chr(type)
-
- encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
- if scope:
- encoded_scope = ''
- for s in string.split(scope, '.'):
- encoded_scope = encoded_scope + chr(len(s)) + s
- return encoded_name + encoded_scope + '\0'
- else:
- return encoded_name + '\0'
- # Internal method for use in encode_name()
- def _do_first_level_encoding(m):
- s = ord(m.group(0))
- return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
- def decode_name(name):
- name_length = ord(name[0])
- assert name_length == 32
- decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
- if name[33] == '\0':
- return 34, decoded_name, ''
- else:
- decoded_domain = ''
- offset = 34
- while 1:
- domain_length = ord(name[offset])
- if domain_length == 0:
- break
- decoded_domain = '.' + name[offset:offset + domain_length]
- offset += domain_length
- return offset + 1, decoded_name, decoded_domain
- def _do_first_level_decoding(m):
- s = m.group(0)
- return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
- class NetBIOSSessionPacket:
- def __init__(self, data = 0):
- self.type = 0x0
- self.flags = 0x0
- self.length = 0x0
- if data == 0:
- self._trailer = ''
- else:
- try:
- self.type = ord(data[0])
- if self.type == NETBIOS_SESSION_MESSAGE:
- self.length = ord(data[1]) << 16 | (unpack('!H', data[2:4])[0])
- else:
- self.flags = ord(data[1])
- self.length = unpack('!H', data[2:4])[0]
- self._trailer = data[4:]
- except:
- raise NetBIOSError( 'Wrong packet format ' )
- def set_type(self, type):
- self.type = type
- def get_type(self):
- return self.type
- def rawData(self):
- if self.type == NETBIOS_SESSION_MESSAGE:
- data = pack('!BBH',self.type,self.length >> 16,self.length & 0xFFFF) + self._trailer
- else:
- data = pack('!BBH',self.type,self.flags,self.length) + self._trailer
- return data
- def set_trailer(self,data):
- self._trailer = data
- self.length = len(data)
- def get_length(self):
- return self.length
- def get_trailer(self):
- return self._trailer
-
- class NetBIOSSession:
- 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):
- if len(myname) > 15:
- self.__myname = string.upper(myname[:15])
- else:
- self.__myname = string.upper(myname)
- self.__local_type = local_type
- assert remote_name
- # if destination port SMB_SESSION_PORT and remote name *SMBSERVER, we're changing it to its IP address
- # helping solving the client mistake ;)
- if remote_name == '*SMBSERVER' and sess_port == SMB_SESSION_PORT:
- remote_name = remote_host
- # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best
- if remote_name == '*SMBSERVER':
- nb = NetBIOS()
-
- try:
- res = nb.getnetbiosname(remote_host)
- except:
- res = None
- pass
-
- if res is not None:
- remote_name = res
- if len(remote_name) > 15:
- self.__remote_name = string.upper(remote_name[:15])
- else:
- self.__remote_name = string.upper(remote_name)
- self.__remote_type = remote_type
- self.__remote_host = remote_host
- if sock is not None:
- # We are acting as a server
- self._sock = sock
- else:
- self._sock = self._setup_connection((remote_host, sess_port))
- if sess_port == NETBIOS_SESSION_PORT:
- self._request_session(remote_type, local_type, timeout)
- def get_myname(self):
- return self.__myname
- def get_mytype(self):
- return self.__local_type
- def get_remote_host(self):
- return self.__remote_host
- def get_remote_name(self):
- return self.__remote_name
- def get_remote_type(self):
- return self.__remote_type
- def close(self):
- self._sock.close()
- def get_socket(self):
- return self._sock
- class NetBIOSUDPSessionPacket(Structure):
- TYPE_DIRECT_UNIQUE = 16
- TYPE_DIRECT_GROUP = 17
- FLAGS_MORE_FRAGMENTS = 1
- FLAGS_FIRST_FRAGMENT = 2
- FLAGS_B_NODE = 0
- structure = (
- ('Type','B=16'), # Direct Unique Datagram
- ('Flags','B=2'), # FLAGS_FIRST_FRAGMENT
- ('ID','<H'),
- ('_SourceIP','>L'),
- ('SourceIP','"'),
- ('SourcePort','>H=138'),
- ('DataLegth','>H-Data'),
- ('Offset','>H=0'),
- ('SourceName','z'),
- ('DestinationName','z'),
- ('Data',':'),
- )
- def getData(self):
- addr = self['SourceIP'].split('.')
- addr = [int(x) for x in addr]
- addr = (((addr[0] << 8) + addr[1] << 8) + addr[2] << 8) + addr[3]
- self['_SourceIP'] = addr
- return Structure.getData(self)
- def get_trailer(self):
- return self['Data']
- class NetBIOSUDPSession(NetBIOSSession):
- def _setup_connection(self, peer):
- af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_DGRAM)[0]
- sock = socket.socket(af, socktype, proto)
- sock.connect(sa)
- sock = socket.socket(af, socktype, proto)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind((INADDR_ANY, 138))
- self.peer = peer
- return sock
- def _request_session(self, remote_type, local_type, timeout = None):
- pass
- def next_id(self):
- if hasattr(self, '__dgram_id'):
- answer = self.__dgram_id
- else:
- self.__dgram_id = randint(1,65535)
- answer = self.__dgram_id
- self.__dgram_id += 1
- return answer
- def send_packet(self, data):
- # Yes... I know...
- self._sock.connect(self.peer)
- p = NetBIOSUDPSessionPacket()
- p['ID'] = self.next_id()
- p['SourceIP'] = self._sock.getsockname()[0]
- p['SourceName'] = encode_name(self.get_myname(), self.get_mytype(), '')[:-1]
- p['DestinationName'] = encode_name(self.get_remote_name(), self.get_remote_type(), '')[:-1]
- p['Data'] = data
- self._sock.sendto(str(p), self.peer)
- self._sock.close()
- self._sock = self._setup_connection(self.peer)
- def recv_packet(self, timeout = None):
- # The next loop is a workaround for a bigger problem:
- # When data reaches higher layers, the lower headers are lost,
- # and with them, for example, the source IP. Hence, SMB users
- # can't know where packets are comming from... we need a better
- # solution, right now, we will filter everything except packets
- # coming from the remote_host specified in __init__()
- while 1:
- data, peer = self._sock.recvfrom(8192)
- # print "peer: %r self.peer: %r" % (peer, self.peer)
- if peer == self.peer: break
- return NetBIOSUDPSessionPacket(data)
- class NetBIOSTCPSession(NetBIOSSession):
- 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):
- self.__select_poll = select_poll
- if self.__select_poll:
- self.read_function = self.polling_read
- else:
- self.read_function = self.non_polling_read
- NetBIOSSession.__init__(self, myname, remote_name, remote_host, remote_type = remote_type, sess_port = sess_port, timeout = timeout, local_type = local_type, sock=sock)
- def _setup_connection(self, peer):
- try:
- af, socktype, proto, canonname, sa = socket.getaddrinfo(peer[0], peer[1], 0, socket.SOCK_STREAM)[0]
- sock = socket.socket(af, socktype, proto)
- sock.connect(sa)
- except socket.error, e:
- raise socket.error("Connection error (%s:%s)" % (peer[0], peer[1]), e)
- return sock
- def send_packet(self, data):
- p = NetBIOSSessionPacket()
- p.set_type(NETBIOS_SESSION_MESSAGE)
- p.set_trailer(data)
- self._sock.send(p.rawData())
- def recv_packet(self, timeout = None):
- data = self.__read(timeout)
- return NetBIOSSessionPacket(data)
- def _request_session(self, remote_type, local_type, timeout = None):
- p = NetBIOSSessionPacket()
- remote_name = encode_name(self.get_remote_name(), remote_type, '')
- myname = encode_name(self.get_myname(), local_type, '')
- p.set_type(NETBIOS_SESSION_REQUEST)
- p.set_trailer(remote_name + myname)
- self._sock.send(p.rawData())
- while 1:
- p = self.recv_packet(timeout)
- if p.get_type() == NETBIOS_SESSION_NEGATIVE_RESPONSE:
- raise NetBIOSError, ( 'Cannot request session', ERRCLASS_SESSION, ord(p.get_trailer()[0]) )
- elif p.get_type() == NETBIOS_SESSION_POSITIVE_RESPONSE:
- break
- else:
- # Ignore all other messages, most probably keepalive messages
- pass
- def polling_read(self, read_length, timeout):
- data = ''
- if timeout is None:
- timeout = 3600
- time_left = timeout
- CHUNK_TIME = 0.025
- bytes_left = read_length
- while bytes_left > 0:
- try:
- ready, _, _ = select.select([self._sock.fileno() ], [ ], [ ], 0)
-
- if not ready:
- if time_left <= 0:
- raise NetBIOSTimeout
- else:
- time.sleep(CHUNK_TIME)
- time_left -= CHUNK_TIME
- continue
- received = self._sock.recv(bytes_left)
- if len(received) == 0:
- raise NetBIOSError, ( 'Error while reading from remote', ERRCLASS_OS, None)
- data = data + received
- bytes_left = read_length - len(data)
- except select.error, ex:
- if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
- raise NetBIOSError, ( 'Error occurs while reading from remote', ERRCLASS_OS, ex[0] )
- return data
- def non_polling_read(self, read_length, timeout):
- data = ''
- bytes_left = read_length
- while bytes_left > 0:
- try:
- ready, _, _ = select.select([self._sock.fileno() ], [ ], [ ], timeout)
- if not ready:
- raise NetBIOSTimeout
- received = self._sock.recv(bytes_left)
- if len(received) == 0:
- raise NetBIOSError, ( 'Error while reading from remote', ERRCLASS_OS, None)
- data = data + received
- bytes_left = read_length - len(data)
- except select.error, ex:
- if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
- raise NetBIOSError, ( 'Error occurs while reading from remote', ERRCLASS_OS, ex[0] )
- return data
- def __read(self, timeout = None):
- data = self.read_function(4, timeout)
- type, flags, length = unpack('>ccH', data)
- if ord(type) == NETBIOS_SESSION_MESSAGE:
- length |= ord(flags) << 16
- else:
- if ord(flags) & 0x01:
- length |= 0x10000
- data2 = self.read_function(length, timeout)
- return data + data2
- ERRCLASS_QUERY = 0x00
- ERRCLASS_SESSION = 0xf0
- ERRCLASS_OS = 0xff
- QUERY_ERRORS = { 0x01: 'Request format error. Please file a bug report.',
- 0x02: 'Internal server error',
- 0x03: 'Name does not exist',
- 0x04: 'Unsupported request',
- 0x05: 'Request refused'
- }
- SESSION_ERRORS = { 0x80: 'Not listening on called name',
- 0x81: 'Not listening for calling name',
- 0x82: 'Called name not present',
- 0x83: 'Sufficient resources',
- 0x8f: 'Unspecified error'
- }
- def main():
- def get_netbios_host_by_name(name):
- n = NetBIOS()
- n.set_broadcastaddr('255.255.255.255') # To avoid use "<broadcast>" in socket
- for qtype in (TYPE_WORKSTATION, TYPE_CLIENT, TYPE_SERVER, TYPE_DOMAIN_MASTER, TYPE_DOMAIN_CONTROLLER):
- try:
- addrs = n.gethostbyname(name, qtype = qtype).get_addr_entries()
- except NetBIOSTimeout:
- continue
- else:
- return addrs
- raise Exception("Host not found")
-
-
- n = get_netbios_host_by_name("some-host")
- print n
- if __name__ == '__main__':
- main()
|