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# Author: Alberto Solino (@agsolino)
8#
9# Description:
10#
11# Wrapper class for SMB1/2/3 so it's transparent for the client.
12# You can still play with the low level methods (version dependent)
13# by calling getSMBServer()
14#
15
16import ntpath
17import string
18import socket
19
20from impacket import smb, smb3, nmb, nt_errors, LOG
21from smb3structs import *
22
23
24# So the user doesn't need to import smb, the smb3 are already in here
25SMB_DIALECT = smb.SMB_DIALECT
26
27class SMBConnection:
28    """
29    SMBConnection class
30
31    :param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\*SMBSERVER*.  If the later,
32           and port is 139, the library will try to get the target's server name.
33    :param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN
34    :param string/optional myName: client's NETBIOS name
35    :param integer/optional sess_port: target port to connect
36    :param integer/optional timeout: timeout in seconds when receiving packets
37    :param optional preferredDialect: the dialect desired to talk with the target server. If not specified the highest
38           one available will be used
39    :param optional boolean manualNegotiate: the user manually performs SMB_COM_NEGOTIATE
40
41    :return: a SMBConnection instance, if not raises a SessionError exception
42    """
43
44    def __init__(self, remoteName='', remoteHost='', myName=None, sess_port=nmb.SMB_SESSION_PORT, timeout=60, preferredDialect=None,
45                 existingConnection=None, manualNegotiate=False):
46
47        self._SMBConnection = 0
48        self._dialect       = ''
49        self._nmbSession    = 0
50        self._sess_port     = sess_port
51        self._myName        = myName
52        self._remoteHost    = remoteHost
53        self._remoteName    = remoteName
54        self._timeout       = timeout
55        self._preferredDialect = preferredDialect
56        self._existingConnection = existingConnection
57        self._manualNegotiate = manualNegotiate
58        self._doKerberos = False
59        self._kdcHost = None
60        self._useCache = True
61        self._ntlmFallback = True
62
63        if existingConnection is not None:
64            # Existing Connection must be a smb or smb3 instance
65            assert ( isinstance(existingConnection,smb.SMB) or isinstance(existingConnection, smb3.SMB3))
66            self._SMBConnection = existingConnection
67            self._preferredDialect = self._SMBConnection.getDialect()
68            self._doKerberos = self._SMBConnection.getKerberos()
69            return
70
71        ##preferredDialect = smb.SMB_DIALECT
72
73        if manualNegotiate is False:
74            self.negotiateSession(preferredDialect)
75
76    def negotiateSession(self, preferredDialect=None,
77                         flags1=smb.SMB.FLAGS1_PATHCASELESS | smb.SMB.FLAGS1_CANONICALIZED_PATHS,
78                         flags2=smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES,
79                         negoData='\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'):
80        """
81        Perform protocol negotiation
82
83        :param string preferredDialect: the dialect desired to talk with the target server. If None is specified the highest one available will be used
84        :param string flags1: the SMB FLAGS capabilities
85        :param string flags2: the SMB FLAGS2 capabilities
86        :param string negoData: data to be sent as part of the nego handshake
87
88        :return: True, raises a Session Error if error.
89        """
90
91        # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. This is to help some old
92        # applications still believing
93        # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better know about i
94        # *SMBSERVER's limitations
95        if self._sess_port == nmb.SMB_SESSION_PORT and self._remoteName == '*SMBSERVER':
96            self._remoteName = self._remoteHost
97        elif self._sess_port == nmb.NETBIOS_SESSION_PORT and self._remoteName == '*SMBSERVER':
98            # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best
99            nb = nmb.NetBIOS()
100            try:
101                res = nb.getnetbiosname(self._remoteHost)
102            except:
103                pass
104            else:
105                self._remoteName = res
106
107        hostType = nmb.TYPE_SERVER
108        if preferredDialect is None:
109            # If no preferredDialect sent, we try the highest available one.
110            packet = self.negotiateSessionWildcard(self._myName, self._remoteName, self._remoteHost, self._sess_port,
111                                                   self._timeout, True, flags1=flags1, flags2=flags2, data=negoData)
112            if packet[0] == '\xfe':
113                # Answer is SMB2 packet
114                self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
115                                                self._sess_port, self._timeout, session=self._nmbSession,
116                                                negSessionResponse=SMB2Packet(packet))
117            else:
118                # Answer is SMB packet, sticking to SMBv1
119                self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
120                                              self._sess_port, self._timeout, session=self._nmbSession,
121                                              negPacket=packet)
122        else:
123            if preferredDialect == smb.SMB_DIALECT:
124                self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
125                                              self._sess_port, self._timeout)
126            elif preferredDialect in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
127                self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
128                                                self._sess_port, self._timeout, preferredDialect=preferredDialect)
129            else:
130                LOG.critical("Unknown dialect %s", preferredDialect)
131                raise
132
133        # propagate flags to the smb sub-object
134        # does not affect smb3 objects
135        if isinstance(self._SMBConnection, smb.SMB):
136            self._SMBConnection.set_flags(flags1=flags1, flags2=flags2)
137
138        return True
139
140    def negotiateSessionWildcard(self, myName, remoteName, remoteHost, sess_port, timeout, extended_security=True, flags1=0,
141                                 flags2=0, data=None):
142        # Here we follow [MS-SMB2] negotiation handshake trying to understand what dialects
143        # (including SMB1) is supported on the other end.
144
145        if not myName:
146            myName = socket.gethostname()
147            i = string.find(myName, '.')
148            if i > -1:
149                myName = myName[:i]
150
151        tries = 0
152        smbp = smb.NewSMBPacket()
153        smbp['Flags1'] = flags1
154        # FLAGS2_UNICODE is required by some stacks to continue, regardless of subsequent support
155        smbp['Flags2'] = flags2 | smb.SMB.FLAGS2_UNICODE
156        resp = None
157        while tries < 2:
158            self._nmbSession = nmb.NetBIOSTCPSession(myName, remoteName, remoteHost, nmb.TYPE_SERVER, sess_port,
159                                                     timeout)
160
161            negSession = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE)
162            if extended_security is True:
163                smbp['Flags2'] |= smb.SMB.FLAGS2_EXTENDED_SECURITY
164            negSession['Data'] = data
165            smbp.addCommand(negSession)
166            self._nmbSession.send_packet(str(smbp))
167
168            try:
169                resp = self._nmbSession.recv_packet(timeout)
170                break
171            except nmb.NetBIOSError:
172                # OSX Yosemite asks for more Flags. Let's give it a try and see what happens
173                smbp['Flags2'] |= smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | smb.SMB.FLAGS2_UNICODE
174                smbp['Data'] = []
175
176            tries += 1
177
178        if resp is None:
179            # No luck, quitting
180            raise
181
182        return resp.get_trailer()
183
184
185    def getNMBServer(self):
186        return self._nmbSession
187
188    def getSMBServer(self):
189        """
190        returns the SMB/SMB3 instance being used. Useful for calling low level methods
191        """
192        return self._SMBConnection
193
194    def getDialect(self):
195        return self._SMBConnection.getDialect()
196
197    def getServerName(self):
198        return self._SMBConnection.get_server_name()
199
200    def getClientName(self):
201        return self._SMBConnection.get_client_name()
202
203    def getRemoteHost(self):
204        return self._SMBConnection.get_remote_host()
205
206    def getRemoteName(self):
207        return self._SMBConnection.get_remote_name()
208
209    def setRemoteName(self, name):
210        return self._SMBConnection.set_remote_name(name)
211
212    def getServerDomain(self):
213        return self._SMBConnection.get_server_domain()
214
215    def getServerDNSDomainName(self):
216        return self._SMBConnection.get_server_dns_domain_name()
217
218    def getServerOS(self):
219        return self._SMBConnection.get_server_os()
220
221    def getServerOSMajor(self):
222        return self._SMBConnection.get_server_os_major()
223
224    def getServerOSMinor(self):
225        return self._SMBConnection.get_server_os_minor()
226
227    def getServerOSBuild(self):
228        return self._SMBConnection.get_server_os_build()
229
230    def doesSupportNTLMv2(self):
231        return self._SMBConnection.doesSupportNTLMv2()
232
233    def isLoginRequired(self):
234        return self._SMBConnection.is_login_required()
235
236    def isSigningRequired(self):
237        return self._SMBConnection.is_signing_required()
238
239    def getCredentials(self):
240        return self._SMBConnection.getCredentials()
241
242    def getIOCapabilities(self):
243        return self._SMBConnection.getIOCapabilities()
244
245    def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlmFallback = True):
246        """
247        logins into the target system
248
249        :param string user: username
250        :param string password: password for the user
251        :param string domain: domain where the account is valid for
252        :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
253        :param string nthash: NTHASH used to authenticate using hashes (password is not used)
254        :param bool ntlmFallback: If True it will try NTLMv1 authentication if NTLMv2 fails. Only available for SMBv1
255
256        :return: None, raises a Session Error if error.
257        """
258        self._ntlmFallback = ntlmFallback
259        try:
260            if self.getDialect() == smb.SMB_DIALECT:
261                return self._SMBConnection.login(user, password, domain, lmhash, nthash, ntlmFallback)
262            else:
263                return self._SMBConnection.login(user, password, domain, lmhash, nthash)
264        except (smb.SessionError, smb3.SessionError), e:
265            raise SessionError(e.get_error_code(), e.get_error_packet())
266
267    def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,
268                      TGS=None, useCache=True):
269        """
270        logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
271
272        :param string user: username
273        :param string password: password for the user
274        :param string domain: domain where the account is valid for (required)
275        :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
276        :param string nthash: NTHASH used to authenticate using hashes (password is not used)
277        :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
278        :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
279        :param struct TGT: If there's a TGT available, send the structure here and it will be used
280        :param struct TGS: same for TGS. See smb3.py for the format
281        :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
282
283        :return: None, raises a Session Error if error.
284        """
285        import os
286        from impacket.krb5.ccache import CCache
287        from impacket.krb5.kerberosv5 import KerberosError
288        from impacket.krb5 import constants
289        from impacket.ntlm import compute_lmhash, compute_nthash
290
291        self._kdcHost = kdcHost
292        self._useCache = useCache
293
294        if TGT is not None or TGS is not None:
295            useCache = False
296
297        if useCache is True:
298            try:
299                ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
300            except:
301                # No cache present
302                pass
303            else:
304                LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
305                # retrieve domain information from CCache file if needed
306                if domain == '':
307                    domain = ccache.principal.realm['data']
308                    LOG.debug('Domain retrieved from CCache: %s' % domain)
309
310                principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper())
311                creds = ccache.getCredential(principal)
312                if creds is None:
313                    # Let's try for the TGT and go from there
314                    principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper())
315                    creds =  ccache.getCredential(principal)
316                    if creds is not None:
317                        TGT = creds.toTGT()
318                        LOG.debug('Using TGT from cache')
319                    else:
320                        LOG.debug("No valid credentials found in cache. ")
321                else:
322                    TGS = creds.toTGS(principal)
323                    LOG.debug('Using TGS from cache')
324
325                # retrieve user information from CCache file if needed
326                if user == '' and creds is not None:
327                    user = creds['client'].prettyPrint().split('@')[0]
328                    LOG.debug('Username retrieved from CCache: %s' % user)
329                elif user == '' and len(ccache.principal.components) > 0:
330                    user = ccache.principal.components[0]['data']
331                    LOG.debug('Username retrieved from CCache: %s' % user)
332
333        while True:
334            try:
335                if self.getDialect() == smb.SMB_DIALECT:
336                    return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost,
337                                                              TGT, TGS)
338                return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT,
339                                                         TGS)
340            except (smb.SessionError, smb3.SessionError), e:
341                raise SessionError(e.get_error_code(), e.get_error_packet())
342            except KerberosError, e:
343                if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
344                    # We might face this if the target does not support AES
345                    # So, if that's the case we'll force using RC4 by converting
346                    # the password to lm/nt hashes and hope for the best. If that's already
347                    # done, byebye.
348                    if lmhash is '' and nthash is '' and (aesKey is '' or aesKey is None) and TGT is None and TGS is None:
349                        from impacket.ntlm import compute_lmhash, compute_nthash
350                        lmhash = compute_lmhash(password)
351                        nthash = compute_nthash(password)
352                    else:
353                        raise e
354                else:
355                    raise e
356
357    def isGuestSession(self):
358        try:
359            return self._SMBConnection.isGuestSession()
360        except (smb.SessionError, smb3.SessionError), e:
361            raise SessionError(e.get_error_code(), e.get_error_packet())
362
363    def logoff(self):
364        try:
365            return self._SMBConnection.logoff()
366        except (smb.SessionError, smb3.SessionError), e:
367            raise SessionError(e.get_error_code(), e.get_error_packet())
368
369
370    def connectTree(self,share):
371        if self.getDialect() == smb.SMB_DIALECT:
372            # If we already have a UNC we do nothing.
373            if ntpath.ismount(share) is False:
374                # Else we build it
375                share = ntpath.basename(share)
376                share = '\\\\' + self.getRemoteHost() + '\\' + share
377        try:
378            return self._SMBConnection.connect_tree(share)
379        except (smb.SessionError, smb3.SessionError), e:
380            raise SessionError(e.get_error_code(), e.get_error_packet())
381
382
383    def disconnectTree(self, treeId):
384        try:
385            return self._SMBConnection.disconnect_tree(treeId)
386        except (smb.SessionError, smb3.SessionError), e:
387            raise SessionError(e.get_error_code(), e.get_error_packet())
388
389
390    def listShares(self):
391        """
392        get a list of available shares at the connected target
393
394        :return: a list containing dict entries for each share, raises exception if error
395        """
396        # Get the shares through RPC
397        from impacket.dcerpc.v5 import transport, srvs
398        rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc',
399                                              smb_connection=self)
400        dce = rpctransport.get_dce_rpc()
401        dce.connect()
402        dce.bind(srvs.MSRPC_UUID_SRVS)
403        resp = srvs.hNetrShareEnum(dce, 1)
404        return resp['InfoStruct']['ShareInfo']['Level1']['Buffer']
405
406    def listPath(self, shareName, path, password = None):
407        """
408        list the files/directories under shareName/path
409
410        :param string shareName: a valid name for the share where the files/directories are going to be searched
411        :param string path: a base path relative to shareName
412        :password string: the password for the share
413
414        :return: a list containing smb.SharedFile items, raises a SessionError exception if error.
415        """
416
417        try:
418            return self._SMBConnection.list_path(shareName, path, password)
419        except (smb.SessionError, smb3.SessionError), e:
420            raise SessionError(e.get_error_code(), e.get_error_packet())
421
422    def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL,
423                   shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
424                   creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF,
425                   fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
426                   oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
427        """
428        creates a remote file
429
430        :param HANDLE treeId: a valid handle for the share where the file is to be created
431        :param string pathName: the path name of the file to create
432        :return: a valid file descriptor, if not raises a SessionError exception.
433        """
434
435        if self.getDialect() == smb.SMB_DIALECT:
436            _, flags2 = self._SMBConnection.get_flags()
437
438            pathName = pathName.replace('/', '\\')
439            pathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
440
441            ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
442            ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
443            ntCreate['Data']       = smb.SMBNtCreateAndX_Data(flags=flags2)
444            ntCreate['Parameters']['FileNameLength']= len(pathName)
445            ntCreate['Parameters']['AccessMask']    = desiredAccess
446            ntCreate['Parameters']['FileAttributes']= fileAttributes
447            ntCreate['Parameters']['ShareAccess']   = shareMode
448            ntCreate['Parameters']['Disposition']   = creationDisposition
449            ntCreate['Parameters']['CreateOptions'] = creationOption
450            ntCreate['Parameters']['Impersonation'] = impersonationLevel
451            ntCreate['Parameters']['SecurityFlags'] = securityFlags
452            ntCreate['Parameters']['CreateFlags']   = 0x16
453            ntCreate['Data']['FileName'] = pathName
454
455            if flags2 & smb.SMB.FLAGS2_UNICODE:
456                ntCreate['Data']['Pad'] = 0x0
457
458            if createContexts is not None:
459                LOG.error("CreateContexts not supported in SMB1")
460
461            try:
462                return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
463            except (smb.SessionError, smb3.SessionError), e:
464                raise SessionError(e.get_error_code(), e.get_error_packet())
465        else:
466            try:
467                return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
468                                                  creationDisposition, fileAttributes, impersonationLevel,
469                                                  securityFlags, oplockLevel, createContexts)
470            except (smb.SessionError, smb3.SessionError), e:
471                raise SessionError(e.get_error_code(), e.get_error_packet())
472
473    def openFile(self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ,
474                 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN,
475                 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
476                 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
477        """
478        opens a remote file
479
480        :param HANDLE treeId: a valid handle for the share where the file is to be opened
481        :param string pathName: the path name to open
482        :return: a valid file descriptor, if not raises a SessionError exception.
483        """
484
485        if self.getDialect() == smb.SMB_DIALECT:
486            _, flags2 = self._SMBConnection.get_flags()
487
488            pathName = pathName.replace('/', '\\')
489            pathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
490
491            ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
492            ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
493            ntCreate['Data']       = smb.SMBNtCreateAndX_Data(flags=flags2)
494            ntCreate['Parameters']['FileNameLength']= len(pathName)
495            ntCreate['Parameters']['AccessMask']    = desiredAccess
496            ntCreate['Parameters']['FileAttributes']= fileAttributes
497            ntCreate['Parameters']['ShareAccess']   = shareMode
498            ntCreate['Parameters']['Disposition']   = creationDisposition
499            ntCreate['Parameters']['CreateOptions'] = creationOption
500            ntCreate['Parameters']['Impersonation'] = impersonationLevel
501            ntCreate['Parameters']['SecurityFlags'] = securityFlags
502            ntCreate['Parameters']['CreateFlags']   = 0x16
503            ntCreate['Data']['FileName'] = pathName
504
505            if flags2 & smb.SMB.FLAGS2_UNICODE:
506                ntCreate['Data']['Pad'] = 0x0
507
508            if createContexts is not None:
509                LOG.error("CreateContexts not supported in SMB1")
510
511            try:
512                return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
513            except (smb.SessionError, smb3.SessionError), e:
514                raise SessionError(e.get_error_code(), e.get_error_packet())
515        else:
516            try:
517                return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
518                                                  creationDisposition, fileAttributes, impersonationLevel,
519                                                  securityFlags, oplockLevel, createContexts)
520            except (smb.SessionError, smb3.SessionError), e:
521                raise SessionError(e.get_error_code(), e.get_error_packet())
522
523    def writeFile(self, treeId, fileId, data, offset=0):
524        """
525        writes data to a file
526
527        :param HANDLE treeId: a valid handle for the share where the file is to be written
528        :param HANDLE fileId: a valid handle for the file
529        :param string data: buffer with the data to write
530        :param integer offset: offset where to start writing the data
531
532        :return: amount of bytes written, if not raises a SessionError exception.
533        """
534        try:
535            return self._SMBConnection.writeFile(treeId, fileId, data, offset)
536        except (smb.SessionError, smb3.SessionError), e:
537            raise SessionError(e.get_error_code(), e.get_error_packet())
538
539
540    def readFile(self, treeId, fileId, offset = 0, bytesToRead = None, singleCall = True):
541        """
542        reads data from a file
543
544        :param HANDLE treeId: a valid handle for the share where the file is to be read
545        :param HANDLE fileId: a valid handle for the file to be read
546        :param integer offset: offset where to start reading the data
547        :param integer bytesToRead: amount of bytes to attempt reading. If None, it will attempt to read Dialect['MaxBufferSize'] bytes.
548        :param boolean singleCall: If True it won't attempt to read all bytesToRead. It will only make a single read call
549
550        :return: the data read, if not raises a SessionError exception. Length of data read is not always bytesToRead
551        """
552        finished = False
553        data = ''
554        maxReadSize = self._SMBConnection.getIOCapabilities()['MaxReadSize']
555        remainingBytesToRead = bytesToRead
556        while not finished:
557            if remainingBytesToRead > maxReadSize:
558                toRead = maxReadSize
559            else:
560                toRead = remainingBytesToRead
561            try:
562                bytesRead = self._SMBConnection.read_andx(treeId, fileId, offset, toRead)
563            except (smb.SessionError, smb3.SessionError), e:
564                if e.get_error_code() == nt_errors.STATUS_END_OF_FILE:
565                    toRead = ''
566                    break
567                else:
568                    raise SessionError(e.get_error_code(), e.get_error_packet())
569
570            data += bytesRead
571            if len(data) >= bytesToRead:
572                finished = True
573            elif len(bytesRead) == 0:
574                # End of the file achieved.
575                finished = True
576            elif singleCall is True:
577                finished = True
578            else:
579                offset += len(bytesRead)
580                remainingBytesToRead -= len(bytesRead)
581
582        return data
583
584    def closeFile(self, treeId, fileId):
585        """
586        closes a file handle
587
588        :param HANDLE treeId: a valid handle for the share where the file is to be opened
589        :param HANDLE fileId: a valid handle for the file/directory to be closed
590
591        :return: None, raises a SessionError exception if error.
592
593        """
594        try:
595            return self._SMBConnection.close(treeId, fileId)
596        except (smb.SessionError, smb3.SessionError), e:
597            raise SessionError(e.get_error_code(), e.get_error_packet())
598
599    def deleteFile(self, shareName, pathName):
600        """
601        removes a file
602
603        :param string shareName: a valid name for the share where the file is to be deleted
604        :param string pathName: the path name to remove
605
606        :return: None, raises a SessionError exception if error.
607
608        """
609        try:
610            return self._SMBConnection.remove(shareName, pathName)
611        except (smb.SessionError, smb3.SessionError), e:
612            raise SessionError(e.get_error_code(), e.get_error_packet())
613
614    def queryInfo(self, treeId, fileId):
615        """
616        queries basic information about an opened file/directory
617
618        :param HANDLE treeId: a valid handle for the share where the file is to be opened
619        :param HANDLE fileId: a valid handle for the file/directory to be closed
620
621        :return: a smb.SMBQueryFileBasicInfo structure.  raises a SessionError exception if error.
622
623        """
624        try:
625            if self.getDialect() == smb.SMB_DIALECT:
626                res = self._SMBConnection.query_file_info(treeId, fileId)
627            else:
628                res = self._SMBConnection.queryInfo(treeId, fileId)
629            return smb.SMBQueryFileStandardInfo(res)
630        except (smb.SessionError, smb3.SessionError), e:
631            raise SessionError(e.get_error_code(), e.get_error_packet())
632
633    def createDirectory(self, shareName, pathName ):
634        """
635        creates a directory
636
637        :param string shareName: a valid name for the share where the directory is to be created
638        :param string pathName: the path name or the directory to create
639
640        :return: None, raises a SessionError exception if error.
641
642        """
643        try:
644            return self._SMBConnection.mkdir(shareName, pathName)
645        except (smb.SessionError, smb3.SessionError), e:
646            raise SessionError(e.get_error_code(), e.get_error_packet())
647
648    def deleteDirectory(self, shareName, pathName):
649        """
650        deletes a directory
651
652        :param string shareName: a valid name for the share where directory is to be deleted
653        :param string pathName: the path name or the directory to delete
654
655        :return: None, raises a SessionError exception if error.
656
657        """
658        try:
659            return self._SMBConnection.rmdir(shareName, pathName)
660        except (smb.SessionError, smb3.SessionError), e:
661            raise SessionError(e.get_error_code(), e.get_error_packet())
662
663    def waitNamedPipe(self, treeId, pipeName, timeout = 5):
664        """
665        waits for a named pipe
666
667        :param HANDLE treeId: a valid handle for the share where the pipe is
668        :param string pipeName: the pipe name to check
669        :param integer timeout: time to wait for an answer
670
671        :return: None, raises a SessionError exception if error.
672
673        """
674        try:
675            return self._SMBConnection.waitNamedPipe(treeId, pipeName, timeout = timeout)
676        except (smb.SessionError, smb3.SessionError), e:
677            raise SessionError(e.get_error_code(), e.get_error_packet())
678
679    def transactNamedPipe(self, treeId, fileId, data, waitAnswer = True):
680        """
681        writes to a named pipe using a transaction command
682
683        :param HANDLE treeId: a valid handle for the share where the pipe is
684        :param HANDLE fileId: a valid handle for the pipe
685        :param string data: buffer with the data to write
686        :param boolean waitAnswer: whether or not to wait for an answer
687
688        :return: None, raises a SessionError exception if error.
689
690        """
691        try:
692            return self._SMBConnection.TransactNamedPipe(treeId, fileId, data, waitAnswer = waitAnswer)
693        except (smb.SessionError, smb3.SessionError), e:
694            raise SessionError(e.get_error_code(), e.get_error_packet())
695
696
697    def transactNamedPipeRecv(self):
698        """
699        reads from a named pipe using a transaction command
700
701        :return: data read, raises a SessionError exception if error.
702
703        """
704        try:
705            return self._SMBConnection.TransactNamedPipeRecv()
706        except (smb.SessionError, smb3.SessionError), e:
707            raise SessionError(e.get_error_code(), e.get_error_packet())
708
709    def writeNamedPipe(self, treeId, fileId, data, waitAnswer = True):
710        """
711        writes to a named pipe
712
713        :param HANDLE treeId: a valid handle for the share where the pipe is
714        :param HANDLE fileId: a valid handle for the pipe
715        :param string data: buffer with the data to write
716        :param boolean waitAnswer: whether or not to wait for an answer
717
718        :return: None, raises a SessionError exception if error.
719
720        """
721        try:
722            if self.getDialect() == smb.SMB_DIALECT:
723                return self._SMBConnection.write_andx(treeId, fileId, data, wait_answer = waitAnswer, write_pipe_mode = True)
724            else:
725                return self.writeFile(treeId, fileId, data, 0)
726        except (smb.SessionError, smb3.SessionError), e:
727            raise SessionError(e.get_error_code(), e.get_error_packet())
728
729
730    def readNamedPipe(self,treeId, fileId, bytesToRead = None ):
731        """
732        read from a named pipe
733
734        :param HANDLE treeId: a valid handle for the share where the pipe resides
735        :param HANDLE fileId: a valid handle for the pipe
736        :param integer bytesToRead: amount of data to read
737
738        :return: None, raises a SessionError exception if error.
739
740        """
741
742        try:
743            return self.readFile(treeId, fileId, bytesToRead = bytesToRead, singleCall = True)
744        except (smb.SessionError, smb3.SessionError), e:
745            raise SessionError(e.get_error_code(), e.get_error_packet())
746
747
748    def getFile(self, shareName, pathName, callback, shareAccessMode = None):
749        """
750        downloads a file
751
752        :param string shareName: name for the share where the file is to be retrieved
753        :param string pathName: the path name to retrieve
754        :param callback callback:
755
756        :return: None, raises a SessionError exception if error.
757
758        """
759        try:
760            if shareAccessMode is None:
761                # if share access mode is none, let's the underlying API deals with it
762                return self._SMBConnection.retr_file(shareName, pathName, callback)
763            else:
764                return self._SMBConnection.retr_file(shareName, pathName, callback, shareAccessMode=shareAccessMode)
765        except (smb.SessionError, smb3.SessionError), e:
766            raise SessionError(e.get_error_code(), e.get_error_packet())
767
768    def putFile(self, shareName, pathName, callback, shareAccessMode = None):
769        """
770        uploads a file
771
772        :param string shareName: name for the share where the file is to be uploaded
773        :param string pathName: the path name to upload
774        :param callback callback:
775
776        :return: None, raises a SessionError exception if error.
777
778        """
779        try:
780            if shareAccessMode is None:
781                # if share access mode is none, let's the underlying API deals with it
782                return self._SMBConnection.stor_file(shareName, pathName, callback)
783            else:
784                return self._SMBConnection.stor_file(shareName, pathName, callback, shareAccessMode)
785        except (smb.SessionError, smb3.SessionError), e:
786            raise SessionError(e.get_error_code(), e.get_error_packet())
787
788    def rename(self, shareName, oldPath, newPath):
789        """
790        renames a file/directory
791
792        :param string shareName: name for the share where the files/directories are
793        :param string oldPath: the old path name or the directory/file to rename
794        :param string newPath: the new path name or the directory/file to rename
795
796        :return: True, raises a SessionError exception if error.
797
798        """
799
800        try:
801            return self._SMBConnection.rename(shareName, oldPath, newPath)
802        except (smb.SessionError, smb3.SessionError), e:
803            raise SessionError(e.get_error_code(), e.get_error_packet())
804
805    def reconnect(self):
806        """
807        reconnects the SMB object based on the original options and credentials used. Only exception is that
808        manualNegotiate will not be honored.
809        Not only the connection will be created but also a login attempt using the original credentials and
810        method (Kerberos, PtH, etc)
811
812        :return: True, raises a SessionError exception if error
813        """
814        userName, password, domain, lmhash, nthash, aesKey, TGT, TGS = self.getCredentials()
815        self.negotiateSession(self._preferredDialect)
816        if self._doKerberos is True:
817            self.kerberosLogin(userName, password, domain, lmhash, nthash, aesKey, self._kdcHost, TGT, TGS, self._useCache)
818        else:
819            self.login(userName, password, domain, lmhash, nthash, self._ntlmFallback)
820
821        return True
822
823    def setTimeout(self, timeout):
824        try:
825            return self._SMBConnection.set_timeout(timeout)
826        except (smb.SessionError, smb3.SessionError), e:
827            raise SessionError(e.get_error_code(), e.get_error_packet())
828
829    def getSessionKey(self):
830        if self.getDialect() == smb.SMB_DIALECT:
831            return self._SMBConnection.get_session_key()
832        else:
833            return self._SMBConnection.getSessionKey()
834
835    def setSessionKey(self, key):
836        if self.getDialect() == smb.SMB_DIALECT:
837            return self._SMBConnection.set_session_key(key)
838        else:
839            return self._SMBConnection.setSessionKey(key)
840
841    def close(self):
842        """
843        logs off and closes the underlying _NetBIOSSession()
844
845        :return: None
846        """
847        try:
848            self.logoff()
849        except:
850            pass
851        self._SMBConnection.close_session()
852
853class SessionError(Exception):
854    """
855    This is the exception every client should catch regardless of the underlying
856    SMB version used. We'll take care of that. NETBIOS exceptions are NOT included,
857    since all SMB versions share the same NETBIOS instances.
858    """
859    def __init__( self, error = 0, packet=0):
860        Exception.__init__(self)
861        self.error = error
862        self.packet = packet
863
864    def getErrorCode( self ):
865        return self.error
866
867    def getErrorPacket( self ):
868        return self.packet
869
870    def getErrorString( self ):
871        return nt_errors.ERROR_MESSAGES[self.error]
872
873    def __str__( self ):
874        if nt_errors.ERROR_MESSAGES.has_key(self.error):
875            return 'SMB SessionError: %s(%s)' % (nt_errors.ERROR_MESSAGES[self.error])
876        else:
877            return 'SMB SessionError: 0x%x' % self.error
878
879
880