1
2import logging, binascii, time, hmac
3from datetime import datetime
4from smb_constants import *
5from smb2_constants import *
6from smb_structs import *
7from smb2_structs import *
8from .security_descriptors import SecurityDescriptor
9from nmb.base import NMBSession
10from utils import convertFILETIMEtoEpoch
11import ntlm, securityblob
12
13try:
14    import hashlib
15    sha256 = hashlib.sha256
16except ImportError:
17    from utils import sha256
18
19
20class NotReadyError(Exception):
21    """Raised when SMB connection is not ready (i.e. not authenticated or authentication failed)"""
22    pass
23
24class NotConnectedError(Exception):
25    """Raised when underlying SMB connection has been disconnected or not connected yet"""
26    pass
27
28class SMBTimeout(Exception):
29    """Raised when a timeout has occurred while waiting for a response or for a SMB/CIFS operation to complete."""
30    pass
31
32
33def _convert_to_unicode(string):
34    if not isinstance(string, unicode):
35        string = unicode(string, "utf-8")
36    return string
37
38
39class SMB(NMBSession):
40    """
41    This class represents a "connection" to the remote SMB/CIFS server.
42    It is not meant to be used directly in an application as it does not have any network transport implementations.
43
44    For application use, please refer to
45      - L{SMBProtocol.SMBProtocolFactory<smb.SMBProtocol>} if you are using Twisted framework
46
47    In [MS-CIFS], this class will contain attributes of Client, Client.Connection and Client.Session abstract data models.
48
49    References:
50    ===========
51      - [MS-CIFS]: 3.2.1
52    """
53
54    log = logging.getLogger('SMB.SMB')
55
56    SIGN_NEVER = 0
57    SIGN_WHEN_SUPPORTED = 1
58    SIGN_WHEN_REQUIRED = 2
59
60    def __init__(self, username, password, my_name, remote_name, domain = '', use_ntlm_v2 = True, sign_options = SIGN_WHEN_REQUIRED, is_direct_tcp = False):
61        NMBSession.__init__(self, my_name, remote_name, is_direct_tcp = is_direct_tcp)
62        self.username = _convert_to_unicode(username)
63        self.password = _convert_to_unicode(password)
64        self.domain = _convert_to_unicode(domain)
65        self.sign_options = sign_options
66        self.is_direct_tcp = is_direct_tcp
67        self.use_ntlm_v2 = use_ntlm_v2 #: Similar to LMAuthenticationPolicy and NTAuthenticationPolicy as described in [MS-CIFS] 3.2.1.1
68        self.smb_message = SMBMessage()
69        self.is_using_smb2 = False   #: Are we communicating using SMB2 protocol? self.smb_message will be a SMB2Message instance if this flag is True
70        self.async_requests = { }    #: AsyncID mapped to _PendingRequest instance
71        self.pending_requests = { }  #: MID mapped to _PendingRequest instance
72        self.connected_trees = { }   #: Share name mapped to TID
73        self.next_rpc_call_id = 1    #: Next RPC callID value. Not used directly in SMB message. Usually encapsulated in sub-commands under SMB_COM_TRANSACTION or SMB_COM_TRANSACTION2 messages
74
75        self.has_negotiated = False
76        self.has_authenticated = False
77        self.is_signing_active = False           #: True if the remote server accepts message signing. All outgoing messages will be signed. Simiar to IsSigningActive as described in [MS-CIFS] 3.2.1.2
78        self.signing_session_key = None          #: Session key for signing packets, if signing is active. Similar to SigningSessionKey as described in [MS-CIFS] 3.2.1.2
79        self.signing_challenge_response = None   #: Contains the challenge response for signing, if signing is active. Similar to SigningChallengeResponse as described in [MS-CIFS] 3.2.1.2
80        self.mid = 0
81        self.uid = 0
82        self.next_signing_id = 2     #: Similar to ClientNextSendSequenceNumber as described in [MS-CIFS] 3.2.1.2
83
84        # SMB1 and SMB2 attributes
85        # Note that the interpretations of the values may differ between SMB1 and SMB2 protocols
86        self.capabilities = 0
87        self.security_mode = 0     #: Initialized from the SecurityMode field of the SMB_COM_NEGOTIATE message
88
89        # SMB1 attributes
90        # Most of the following attributes will be initialized upon receipt of SMB_COM_NEGOTIATE message from server (via self._updateServerInfo_SMB1 method)
91        self.use_plaintext_authentication = False  #: Similar to PlaintextAuthenticationPolicy in in [MS-CIFS] 3.2.1.1
92        self.max_raw_size = 0
93        self.max_buffer_size = 0   #: Similar to MaxBufferSize as described in [MS-CIFS] 3.2.1.1
94        self.max_mpx_count = 0     #: Similar to MaxMpxCount as described in [MS-CIFS] 3.2.1.1
95
96        # SMB2 attributes
97        self.max_read_size = 0      #: Similar to MaxReadSize as described in [MS-SMB2] 2.2.4
98        self.max_write_size = 0     #: Similar to MaxWriteSize as described in [MS-SMB2] 2.2.4
99        self.max_transact_size = 0  #: Similar to MaxTransactSize as described in [MS-SMB2] 2.2.4
100        self.session_id = 0         #: Similar to SessionID as described in [MS-SMB2] 2.2.4. This will be set in _updateState_SMB2 method
101
102        self._setupSMB1Methods()
103
104        self.log.info('Authentication with remote machine "%s" for user "%s" will be using NTLM %s authentication (%s extended security)',
105                      self.remote_name, self.username,
106                      (self.use_ntlm_v2 and 'v2') or 'v1',
107                      (SUPPORT_EXTENDED_SECURITY and 'with') or 'without')
108
109
110    #
111    # NMBSession Methods
112    #
113
114    def onNMBSessionOK(self):
115        self._sendSMBMessage(SMBMessage(ComNegotiateRequest()))
116
117    def onNMBSessionFailed(self):
118        pass
119
120    def onNMBSessionMessage(self, flags, data):
121        while True:
122            try:
123                i = self.smb_message.decode(data)
124            except SMB2ProtocolHeaderError:
125                self.log.info('Now switching over to SMB2 protocol communication')
126                self.is_using_smb2 = True
127                self.mid = 0  # Must reset messageID counter, or else remote SMB2 server will disconnect
128                self._setupSMB2Methods()
129                self.smb_message = self._klassSMBMessage()
130                i = self.smb_message.decode(data)
131
132            next_message_offset = 0
133            if self.is_using_smb2:
134                next_message_offset = self.smb_message.next_command_offset
135
136            if i > 0:
137                if not self.is_using_smb2:
138                    self.log.debug('Received SMB message "%s" (command:0x%2X flags:0x%02X flags2:0x%04X TID:%d UID:%d)',
139                                   SMB_COMMAND_NAMES.get(self.smb_message.command, '<unknown>'),
140                                   self.smb_message.command, self.smb_message.flags, self.smb_message.flags2, self.smb_message.tid, self.smb_message.uid)
141                else:
142                    self.log.debug('Received SMB2 message "%s" (command:0x%04X flags:0x%04x)',
143                                   SMB2_COMMAND_NAMES.get(self.smb_message.command, '<unknown>'),
144                                   self.smb_message.command, self.smb_message.flags)
145                if self._updateState(self.smb_message):
146                    # We need to create a new instance instead of calling reset() because the instance could be captured in the message history.
147                    self.smb_message = self._klassSMBMessage()
148
149            if next_message_offset > 0:
150                data = data[next_message_offset:]
151            else:
152                break
153
154    #
155    # Public Methods for Overriding in Subclasses
156    #
157
158    def onAuthOK(self):
159        pass
160
161    def onAuthFailed(self):
162        pass
163
164    #
165    # Protected Methods
166    #
167
168    def _setupSMB1Methods(self):
169        self._klassSMBMessage = SMBMessage
170        self._updateState = self._updateState_SMB1
171        self._updateServerInfo = self._updateServerInfo_SMB1
172        self._handleNegotiateResponse = self._handleNegotiateResponse_SMB1
173        self._sendSMBMessage = self._sendSMBMessage_SMB1
174        self._handleSessionChallenge = self._handleSessionChallenge_SMB1
175        self._listShares = self._listShares_SMB1
176        self._listPath = self._listPath_SMB1
177        self._listSnapshots = self._listSnapshots_SMB1
178        self._getSecurity = self._getSecurity_SMB1
179        self._getAttributes = self._getAttributes_SMB1
180        self._retrieveFile = self._retrieveFile_SMB1
181        self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB1
182        self._storeFile = self._storeFile_SMB1
183        self._storeFileFromOffset = self._storeFileFromOffset_SMB1
184        self._deleteFiles = self._deleteFiles_SMB1
185        self._resetFileAttributes = self._resetFileAttributes_SMB1
186        self._createDirectory = self._createDirectory_SMB1
187        self._deleteDirectory = self._deleteDirectory_SMB1
188        self._rename = self._rename_SMB1
189        self._echo = self._echo_SMB1
190
191    def _setupSMB2Methods(self):
192        self._klassSMBMessage = SMB2Message
193        self._updateState = self._updateState_SMB2
194        self._updateServerInfo = self._updateServerInfo_SMB2
195        self._handleNegotiateResponse = self._handleNegotiateResponse_SMB2
196        self._sendSMBMessage = self._sendSMBMessage_SMB2
197        self._handleSessionChallenge = self._handleSessionChallenge_SMB2
198        self._listShares = self._listShares_SMB2
199        self._listPath = self._listPath_SMB2
200        self._listSnapshots = self._listSnapshots_SMB2
201        self._getAttributes = self._getAttributes_SMB2
202        self._getSecurity = self._getSecurity_SMB2
203        self._retrieveFile = self._retrieveFile_SMB2
204        self._retrieveFileFromOffset = self._retrieveFileFromOffset_SMB2
205        self._storeFile = self._storeFile_SMB2
206        self._storeFileFromOffset = self._storeFileFromOffset_SMB2
207        self._deleteFiles = self._deleteFiles_SMB2
208        self._resetFileAttributes = self._resetFileAttributes_SMB2
209        self._createDirectory = self._createDirectory_SMB2
210        self._deleteDirectory = self._deleteDirectory_SMB2
211        self._rename = self._rename_SMB2
212        self._echo = self._echo_SMB2
213
214    def _getNextRPCCallID(self):
215        self.next_rpc_call_id += 1
216        return self.next_rpc_call_id
217
218    #
219    # SMB2 Methods Family
220    #
221
222    def _sendSMBMessage_SMB2(self, smb_message):
223        if smb_message.mid == 0:
224            smb_message.mid = self._getNextMID_SMB2()
225
226        if smb_message.command != SMB2_COM_NEGOTIATE:
227            smb_message.session_id = self.session_id
228
229        if self.is_signing_active:
230            smb_message.flags |= SMB2_FLAGS_SIGNED
231            raw_data = smb_message.encode()
232            smb_message.signature = hmac.new(self.signing_session_key, raw_data, sha256).digest()[:16]
233
234            smb_message.raw_data = smb_message.encode()
235            self.log.debug('MID is %d. Signature is %s. Total raw message is %d bytes', smb_message.mid, binascii.hexlify(smb_message.signature), len(smb_message.raw_data))
236        else:
237            smb_message.raw_data = smb_message.encode()
238        self.sendNMBMessage(smb_message.raw_data)
239
240    def _getNextMID_SMB2(self):
241        self.mid += 1
242        return self.mid
243
244    def _updateState_SMB2(self, message):
245        if message.isReply:
246            if message.command == SMB2_COM_NEGOTIATE:
247                if message.status == 0:
248                    self.has_negotiated = True
249                    self.log.info('SMB2 dialect negotiation successful')
250                    self._updateServerInfo(message.payload)
251                    self._handleNegotiateResponse(message)
252                else:
253                    raise ProtocolError('Unknown status value (0x%08X) in SMB2_COM_NEGOTIATE' % message.status,
254                                        message.raw_data, message)
255            elif message.command == SMB2_COM_SESSION_SETUP:
256                if message.status == 0:
257                    self.session_id = message.session_id
258                    try:
259                        result = securityblob.decodeAuthResponseSecurityBlob(message.payload.security_blob)
260                        if result == securityblob.RESULT_ACCEPT_COMPLETED:
261                            self.has_authenticated = True
262                            self.log.info('Authentication (on SMB2) successful!')
263
264                            # [MS-SMB2]: 3.2.5.3.1
265                            # If the security subsystem indicates that the session was established by an anonymous user,
266                            # Session.SigningRequired MUST be set to FALSE.
267                            # If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the
268                            # SMB2 SESSION_SETUP Response and if Session.SigningRequired is TRUE, this indicates a SESSION_SETUP
269                            # failure and the connection MUST be terminated. If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags
270                            # field of the SMB2 SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired
271                            # MUST be set to FALSE.
272                            if message.payload.isGuestSession or message.payload.isAnonymousSession:
273                                self.is_signing_active = False
274                                self.log.info('Signing disabled because session is guest/anonymous')
275
276                            self.onAuthOK()
277                        else:
278                            raise ProtocolError('SMB2_COM_SESSION_SETUP status is 0 but security blob negResult value is %d' % result, message.raw_data, message)
279                    except securityblob.BadSecurityBlobError, ex:
280                        raise ProtocolError(str(ex), message.raw_data, message)
281                elif message.status == 0xc0000016:  # STATUS_MORE_PROCESSING_REQUIRED
282                    self.session_id = message.session_id
283                    try:
284                        result, ntlm_token = securityblob.decodeChallengeSecurityBlob(message.payload.security_blob)
285                        if result == securityblob.RESULT_ACCEPT_INCOMPLETE:
286                            self._handleSessionChallenge(message, ntlm_token)
287                    except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ), ex:
288                        raise ProtocolError(str(ex), message.raw_data, message)
289                elif (message.status == 0xc000006d   # STATUS_LOGON_FAILURE
290                    or message.status == 0xc0000064  # STATUS_NO_SUCH_USER
291                    or message.status == 0xc000006a):# STATUS_WRONG_PASSWORD
292                    self.has_authenticated = False
293                    self.log.info('Authentication (on SMB2) failed. Please check username and password.')
294                    self.onAuthFailed()
295                elif (message.status == 0xc0000193    # STATUS_ACCOUNT_EXPIRED
296                    or message.status == 0xC0000071): # STATUS_PASSWORD_EXPIRED
297                    self.has_authenticated = False
298                    self.log.info('Authentication (on SMB2) failed. Account or password has expired.')
299                    self.onAuthFailed()
300                elif message.status == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT
301                    self.has_authenticated = False
302                    self.log.info('Authentication (on SMB2) failed. Account has been locked due to too many invalid logon attempts.')
303                    self.onAuthFailed()
304                elif message.status == 0xc0000072: # STATUS_ACCOUNT_DISABLED
305                    self.has_authenticated = False
306                    self.log.info('Authentication (on SMB2) failed. Account has been disabled.')
307                    self.onAuthFailed()
308                elif (message.status == 0xc000006f    # STATUS_INVALID_LOGON_HOURS
309                    or message.status == 0xc000015b   # STATUS_LOGON_TYPE_NOT_GRANTED
310                    or message.status == 0xc0000070): # STATUS_INVALID_WORKSTATION
311                    self.has_authenticated = False
312                    self.log.info('Authentication (on SMB2) failed. Not allowed.')
313                    self.onAuthFailed()
314                elif message.status == 0xc000018c:  # STATUS_TRUSTED_DOMAIN_FAILURE
315                    self.has_authenticated = False
316                    self.log.info('Authentication (on SMB2) failed. Domain not trusted.')
317                    self.onAuthFailed()
318                elif message.status == 0xc000018d:  # STATUS_TRUSTED_RELATIONSHIP_FAILURE
319                    self.has_authenticated = False
320                    self.log.info('Authentication (on SMB2) failed. Workstation not trusted.')
321                    self.onAuthFailed()
322                else:
323                    raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status,
324                                        message.raw_data, message)
325
326            if message.isAsync:
327                if message.status == 0x00000103:  # STATUS_PENDING
328                    req = self.pending_requests.pop(message.mid, None)
329                    if req:
330                        self.async_requests[message.async_id] = req
331                else: # All other status including SUCCESS
332                    req = self.async_requests.pop(message.async_id, None)
333                    if req:
334                        req.callback(message, **req.kwargs)
335                        return True
336            else:
337                req = self.pending_requests.pop(message.mid, None)
338                if req:
339                    req.callback(message, **req.kwargs)
340                    return True
341
342
343    def _updateServerInfo_SMB2(self, payload):
344        self.capabilities = payload.capabilities
345        self.security_mode = payload.security_mode
346        self.max_transact_size = payload.max_transact_size
347        self.max_read_size = payload.max_read_size
348        self.max_write_size = payload.max_write_size
349        self.use_plaintext_authentication = False   # SMB2 never allows plaintext authentication
350
351
352    def _handleNegotiateResponse_SMB2(self, message):
353        ntlm_data = ntlm.generateNegotiateMessage()
354        blob = securityblob.generateNegotiateSecurityBlob(ntlm_data)
355        self._sendSMBMessage(SMB2Message(SMB2SessionSetupRequest(blob)))
356
357
358    def _handleSessionChallenge_SMB2(self, message, ntlm_token):
359        server_challenge, server_flags, server_info = ntlm.decodeChallengeMessage(ntlm_token)
360
361        self.log.info('Performing NTLMv2 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))
362
363        if self.use_ntlm_v2:
364            self.log.info('Performing NTLMv2 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))
365            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV2(self.password,
366                                                                                                         self.username,
367                                                                                                         server_challenge,
368                                                                                                         server_info,
369                                                                                                         self.domain)
370
371        else:
372            self.log.info('Performing NTLMv1 authentication (on SMB2) with server challenge "%s"', binascii.hexlify(server_challenge))
373            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV1(self.password, server_challenge, True)
374
375        ntlm_data = ntlm.generateAuthenticateMessage(server_flags,
376                                                     nt_challenge_response,
377                                                     lm_challenge_response,
378                                                     session_key,
379                                                     self.username,
380                                                     self.domain,
381                                                     self.my_name)
382
383        if self.log.isEnabledFor(logging.DEBUG):
384            self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response))
385            self.log.debug('LM challenge response is "%s" (%d bytes)', binascii.hexlify(lm_challenge_response), len(lm_challenge_response))
386
387        blob = securityblob.generateAuthSecurityBlob(ntlm_data)
388        self._sendSMBMessage(SMB2Message(SMB2SessionSetupRequest(blob)))
389
390        if self.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED:
391            self.log.info('Server requires all SMB messages to be signed')
392            self.is_signing_active = (self.sign_options != SMB.SIGN_NEVER)
393        elif self.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED:
394            self.log.info('Server supports SMB signing')
395            self.is_signing_active = (self.sign_options == SMB.SIGN_WHEN_SUPPORTED)
396        else:
397            self.is_signing_active = False
398
399        if self.is_signing_active:
400            self.log.info("SMB signing activated. All SMB messages will be signed.")
401            self.signing_session_key = (session_key + '\0'*16)[:16]
402            if self.capabilities & CAP_EXTENDED_SECURITY:
403                self.signing_challenge_response = None
404            else:
405                self.signing_challenge_response = blob
406        else:
407            self.log.info("SMB signing deactivated. SMB messages will NOT be signed.")
408
409
410    def _listShares_SMB2(self, callback, errback, timeout = 30):
411        if not self.has_authenticated:
412            raise NotReadyError('SMB connection not authenticated')
413
414        expiry_time = time.time() + timeout
415        path = 'IPC$'
416        messages_history = [ ]
417
418        def connectSrvSvc(tid):
419            m = SMB2Message(SMB2CreateRequest('srvsvc',
420                                              file_attributes = 0,
421                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
422                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
423                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
424                                              impersonation = SEC_IMPERSONATE,
425                                              create_options = FILE_NON_DIRECTORY_FILE | FILE_OPEN_NO_RECALL,
426                                              create_disp = FILE_OPEN))
427
428            m.tid = tid
429            self._sendSMBMessage(m)
430            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback, tid = tid)
431            messages_history.append(m)
432
433        def connectSrvSvcCB(create_message, **kwargs):
434            messages_history.append(create_message)
435            if create_message.status == 0:
436                call_id = self._getNextRPCCallID()
437                # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706]
438                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
439                data_bytes = \
440                    binascii.unhexlify("""05 00 0b 03 10 00 00 00 74 00 00 00""".replace(' ', '')) + \
441                    struct.pack('<I', call_id) + \
442                    binascii.unhexlify("""
443b8 10 b8 10 00 00 00 00 02 00 00 00 00 00 01 00
444c8 4f 32 4b 70 16 d3 01 12 78 5a 47 bf 6e e1 88
44503 00 00 00 04 5d 88 8a eb 1c c9 11 9f e8 08 00
4462b 10 48 60 02 00 00 00 01 00 01 00 c8 4f 32 4b
44770 16 d3 01 12 78 5a 47 bf 6e e1 88 03 00 00 00
4482c 1c b7 6c 12 98 40 45 03 00 00 00 00 00 00 00
44901 00 00 00
450""".replace(' ', '').replace('\n', ''))
451                m = SMB2Message(SMB2WriteRequest(create_message.payload.fid, data_bytes, 0))
452                m.tid = kwargs['tid']
453                self._sendSMBMessage(m)
454                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid)
455                messages_history.append(m)
456            else:
457                errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history))
458
459        def rpcBindCB(trans_message, **kwargs):
460            messages_history.append(trans_message)
461            if trans_message.status == 0:
462                m = SMB2Message(SMB2ReadRequest(kwargs['fid'], read_len = 1024, read_offset = 0))
463                m.tid = kwargs['tid']
464                self._sendSMBMessage(m)
465                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcReadCB, errback, tid = kwargs['tid'], fid = kwargs['fid'])
466                messages_history.append(m)
467            else:
468                closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to read from Server Service RPC endpoint')
469
470        def rpcReadCB(read_message, **kwargs):
471            messages_history.append(read_message)
472            if read_message.status == 0:
473                call_id = self._getNextRPCCallID()
474
475                padding = ''
476                remote_name = '\\\\' + self.remote_name
477                server_len = len(remote_name) + 1
478                server_bytes_len = server_len * 2
479                if server_len % 2 != 0:
480                    padding = '\0\0'
481                    server_bytes_len += 2
482
483                # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
484                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
485                data_bytes = \
486                    binascii.unhexlify("""05 00 00 03 10 00 00 00""".replace(' ', '')) + \
487                    struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \
488                    binascii.unhexlify("""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(' ', '')) + \
489                    struct.pack('<III', server_len, 0, server_len) + \
490                    (remote_name + '\0').encode('UTF-16LE') + padding + \
491                    binascii.unhexlify("""
49201 00 00 00 01 00 00 00 04 00 02 00 00 00 00 00
49300 00 00 00 ff ff ff ff 08 00 02 00 00 00 00 00
494""".replace(' ', '').replace('\n', ''))
495                m = SMB2Message(SMB2IoctlRequest(kwargs['fid'], 0x0011C017, flags = 0x01, max_out_size = 8196, in_data = data_bytes))
496                m.tid = kwargs['tid']
497                self._sendSMBMessage(m)
498                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, tid = kwargs['tid'], fid = kwargs['fid'])
499                messages_history.append(m)
500            else:
501                closeFid(kwargs['tid'], kwargs['fid'], error = 'Failed to list shares: Unable to bind to Server Service RPC endpoint')
502
503        def listShareResultsCB(result_message, **kwargs):
504            messages_history.append(result_message)
505            if result_message.status == 0:
506                # The payload.data_bytes will contain the results of the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
507                data_bytes = result_message.payload.out_data
508
509                if ord(data_bytes[3]) & 0x02 == 0:
510                    sendReadRequest(kwargs['tid'], kwargs['fid'], data_bytes)
511                else:
512                    decodeResults(kwargs['tid'], kwargs['fid'], data_bytes)
513            else:
514                closeFid(kwargs['tid'], kwargs['fid'])
515                errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
516
517        def decodeResults(tid, fid, data_bytes):
518            shares_count = struct.unpack('<I', data_bytes[36:40])[0]
519            results = [ ]     # A list of SharedDevice instances
520            offset = 36 + 12  # You need to study the byte stream to understand the meaning of these constants
521            for i in range(0, shares_count):
522                results.append(SharedDevice(struct.unpack('<I', data_bytes[offset+4:offset+8])[0], None, None))
523                offset += 12
524
525            for i in range(0, shares_count):
526                max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
527                offset += 12
528                results[i].name = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE')
529
530                if length % 2 != 0:
531                    offset += (length * 2 + 2)
532                else:
533                    offset += (length * 2)
534
535                max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
536                offset += 12
537                results[i].comments = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE')
538
539                if length % 2 != 0:
540                    offset += (length * 2 + 2)
541                else:
542                    offset += (length * 2)
543
544            closeFid(tid, fid)
545            callback(results)
546
547        def sendReadRequest(tid, fid, data_bytes):
548            read_count = min(4280, self.max_read_size)
549            m = SMB2Message(SMB2ReadRequest(fid, 0, read_count))
550            m.tid = tid
551            self._sendSMBMessage(m)
552            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback,
553                                                           tid = tid, fid = fid, data_bytes = data_bytes)
554
555        def readCB(read_message, **kwargs):
556            messages_history.append(read_message)
557            if read_message.status == 0:
558                data_bytes = read_message.payload.data
559
560                if ord(data_bytes[3]) & 0x02 == 0:
561                    sendReadRequest(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
562                else:
563                    decodeResults(kwargs['tid'], kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
564            else:
565                closeFid(kwargs['tid'], kwargs['fid'])
566                errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
567
568        def closeFid(tid, fid, results = None, error = None):
569            m = SMB2Message(SMB2CloseRequest(fid))
570            m.tid = tid
571            self._sendSMBMessage(m)
572            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error)
573            messages_history.append(m)
574
575        def closeCB(close_message, **kwargs):
576            if kwargs['results'] is not None:
577                callback(kwargs['results'])
578            elif kwargs['error'] is not None:
579                errback(OperationFailure(kwargs['error'], messages_history))
580
581        if not self.connected_trees.has_key(path):
582            def connectCB(connect_message, **kwargs):
583                messages_history.append(connect_message)
584                if connect_message.status == 0:
585                    self.connected_trees[path] = connect_message.tid
586                    connectSrvSvc(connect_message.tid)
587                else:
588                    errback(OperationFailure('Failed to list shares: Unable to connect to IPC$', messages_history))
589
590            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), path )))
591            self._sendSMBMessage(m)
592            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)
593            messages_history.append(m)
594        else:
595            connectSrvSvc(self.connected_trees[path])
596
597    def _listPath_SMB2(self, service_name, path, callback, errback, search, pattern, timeout = 30):
598        if not self.has_authenticated:
599            raise NotReadyError('SMB connection not authenticated')
600
601        expiry_time = time.time() + timeout
602        path = path.replace('/', '\\')
603        if path.startswith('\\'):
604            path = path[1:]
605        if path.endswith('\\'):
606            path = path[:-1]
607        messages_history = [ ]
608        results = [ ]
609
610        def sendCreate(tid):
611            create_context_data = binascii.unhexlify("""
61228 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
61344 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
61400 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
61500 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
61600 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
61751 46 69 64 00 00 00 00
618""".replace(' ', '').replace('\n', ''))
619            m = SMB2Message(SMB2CreateRequest(path,
620                                              file_attributes = 0,
621                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
622                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
623                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
624                                              impersonation = SEC_IMPERSONATE,
625                                              create_options = FILE_DIRECTORY_FILE,
626                                              create_disp = FILE_OPEN,
627                                              create_context_data = create_context_data))
628            m.tid = tid
629            self._sendSMBMessage(m)
630            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid)
631            messages_history.append(m)
632
633        def createCB(create_message, **kwargs):
634            messages_history.append(create_message)
635            if create_message.status == 0:
636                sendQuery(kwargs['tid'], create_message.payload.fid, '')
637            else:
638                errback(OperationFailure('Failed to list %s on %s: Unable to open directory' % ( path, service_name ), messages_history))
639
640        def sendQuery(tid, fid, data_buf):
641            m = SMB2Message(SMB2QueryDirectoryRequest(fid, pattern,
642                                                      info_class = 0x03,   # FileBothDirectoryInformation
643                                                      flags = 0,
644                                                      output_buf_len = self.max_transact_size))
645            m.tid = tid
646            self._sendSMBMessage(m)
647            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = tid, fid = fid, data_buf = data_buf)
648            messages_history.append(m)
649
650        def queryCB(query_message, **kwargs):
651            messages_history.append(query_message)
652            if query_message.status == 0:
653                data_buf = decodeQueryStruct(kwargs['data_buf'] + query_message.payload.data)
654                sendQuery(kwargs['tid'], kwargs['fid'], data_buf)
655            elif query_message.status == 0x80000006L:  # STATUS_NO_MORE_FILES
656                closeFid(kwargs['tid'], kwargs['fid'], results = results)
657            else:
658                closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status)
659
660        def decodeQueryStruct(data_bytes):
661            # SMB_FIND_FILE_BOTH_DIRECTORY_INFO structure. See [MS-CIFS]: 2.2.8.1.7 and [MS-SMB]: 2.2.8.1.1
662            info_format = '<IIQQQQQQIIIBB24s'
663            info_size = struct.calcsize(info_format)
664
665            data_length = len(data_bytes)
666            offset = 0
667            while offset < data_length:
668                if offset + info_size > data_length:
669                    return data_bytes[offset:]
670
671                next_offset, _, \
672                create_time, last_access_time, last_write_time, last_attr_change_time, \
673                file_size, alloc_size, file_attributes, filename_length, ea_size, \
674                short_name_length, _, short_name = struct.unpack(info_format, data_bytes[offset:offset+info_size])
675
676                offset2 = offset + info_size
677                if offset2 + filename_length > data_length:
678                    return data_bytes[offset:]
679
680                filename = data_bytes[offset2:offset2+filename_length].decode('UTF-16LE')
681                short_name = short_name[:short_name_length].decode('UTF-16LE')
682
683                accept_result = False
684                if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc
685                    accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL)
686                else:
687                    accept_result = (file_attributes & search) > 0
688                if accept_result:
689                    results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time),
690                                              convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time),
691                                              file_size, alloc_size, file_attributes, short_name, filename))
692
693                if next_offset:
694                    offset += next_offset
695                else:
696                    break
697            return ''
698
699        def closeFid(tid, fid, results = None, error = None):
700            m = SMB2Message(SMB2CloseRequest(fid))
701            m.tid = tid
702            self._sendSMBMessage(m)
703            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, results = results, error = error)
704            messages_history.append(m)
705
706        def closeCB(close_message, **kwargs):
707            if kwargs['results'] is not None:
708                callback(kwargs['results'])
709            elif kwargs['error'] is not None:
710                if kwargs['error'] == 0xC000000F:  # [MS-ERREF]: STATUS_NO_SUCH_FILE
711                    # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files
712                    callback([ ])
713                else:
714                    errback(OperationFailure('Failed to list %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))
715
716        if not self.connected_trees.has_key(service_name):
717            def connectCB(connect_message, **kwargs):
718                messages_history.append(connect_message)
719                if connect_message.status == 0:
720                    self.connected_trees[service_name] = connect_message.tid
721                    sendCreate(connect_message.tid)
722                else:
723                    errback(OperationFailure('Failed to list %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
724
725            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
726            self._sendSMBMessage(m)
727            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
728            messages_history.append(m)
729        else:
730            sendCreate(self.connected_trees[service_name])
731
732    def _getAttributes_SMB2(self, service_name, path, callback, errback, timeout = 30):
733        if not self.has_authenticated:
734            raise NotReadyError('SMB connection not authenticated')
735
736        expiry_time = time.time() + timeout
737        path = path.replace('/', '\\')
738        if path.startswith('\\'):
739            path = path[1:]
740        if path.endswith('\\'):
741            path = path[:-1]
742        messages_history = [ ]
743
744        def sendCreate(tid):
745            create_context_data = binascii.unhexlify("""
74628 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
74744 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
74800 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
74900 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
75000 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
75151 46 69 64 00 00 00 00
752""".replace(' ', '').replace('\n', ''))
753            m = SMB2Message(SMB2CreateRequest(path,
754                                              file_attributes = 0,
755                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
756                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
757                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
758                                              impersonation = SEC_IMPERSONATE,
759                                              create_options = 0,
760                                              create_disp = FILE_OPEN,
761                                              create_context_data = create_context_data))
762            m.tid = tid
763            self._sendSMBMessage(m)
764            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid)
765            messages_history.append(m)
766
767        def createCB(create_message, **kwargs):
768            messages_history.append(create_message)
769            if create_message.status == 0:
770                p = create_message.payload
771                filename = self._extractLastPathComponent(unicode(path))
772                info = SharedFile(p.create_time, p.lastaccess_time, p.lastwrite_time, p.change_time,
773                                  p.file_size, p.allocation_size, p.file_attributes,
774                                  filename, filename)
775                closeFid(kwargs['tid'], p.fid, info = info)
776            else:
777                errback(OperationFailure('Failed to get attributes for %s on %s: Unable to open remote file object' % ( path, service_name ), messages_history))
778
779        def closeFid(tid, fid, info = None, error = None):
780            m = SMB2Message(SMB2CloseRequest(fid))
781            m.tid = tid
782            self._sendSMBMessage(m)
783            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, info = info, error = error)
784            messages_history.append(m)
785
786        def closeCB(close_message, **kwargs):
787            if kwargs['info'] is not None:
788                callback(kwargs['info'])
789            elif kwargs['error'] is not None:
790                errback(OperationFailure('Failed to get attributes for %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))
791
792        if not self.connected_trees.has_key(service_name):
793            def connectCB(connect_message, **kwargs):
794                messages_history.append(connect_message)
795                if connect_message.status == 0:
796                    self.connected_trees[service_name] = connect_message.tid
797                    sendCreate(connect_message.tid)
798                else:
799                    errback(OperationFailure('Failed to get attributes for %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
800
801            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
802            self._sendSMBMessage(m)
803            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
804            messages_history.append(m)
805        else:
806            sendCreate(self.connected_trees[service_name])
807
808    def _getSecurity_SMB2(self, service_name, path, callback, errback, timeout = 30):
809        if not self.has_authenticated:
810            raise NotReadyError('SMB connection not authenticated')
811
812        expiry_time = time.time() + timeout
813        path = path.replace('/', '\\')
814        if path.startswith('\\'):
815            path = path[1:]
816        if path.endswith('\\'):
817            path = path[:-1]
818        messages_history = [ ]
819        results = [ ]
820
821        def sendCreate(tid):
822            m = SMB2Message(SMB2CreateRequest(path,
823                                              file_attributes = 0,
824                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,
825                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
826                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
827                                              impersonation = SEC_IMPERSONATE,
828                                              create_options = 0,
829                                              create_disp = FILE_OPEN))
830            m.tid = tid
831            self._sendSMBMessage(m)
832            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid)
833            messages_history.append(m)
834
835        def createCB(create_message, **kwargs):
836            messages_history.append(create_message)
837            if create_message.status == 0:
838                m = SMB2Message(SMB2QueryInfoRequest(create_message.payload.fid,
839                                                     flags = 0,
840                                                     additional_info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
841                                                     info_type = SMB2_INFO_SECURITY,
842                                                     file_info_class = 0, # [MS-SMB2] 2.2.37, 3.2.4.12
843                                                     input_buf = '',
844                                                     output_buf_len = self.max_transact_size))
845                m.tid = kwargs['tid']
846                self._sendSMBMessage(m)
847                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback, tid = kwargs['tid'], fid = create_message.payload.fid)
848                messages_history.append(m)
849            else:
850                errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to open file or directory' % ( path, service_name ), messages_history))
851
852        def queryCB(query_message, **kwargs):
853            messages_history.append(query_message)
854            if query_message.status == 0:
855                security = SecurityDescriptor.from_bytes(query_message.payload.data)
856                closeFid(kwargs['tid'], kwargs['fid'], result = security)
857            else:
858                closeFid(kwargs['tid'], kwargs['fid'], error = query_message.status)
859
860        def closeFid(tid, fid, result = None, error = None):
861            m = SMB2Message(SMB2CloseRequest(fid))
862            m.tid = tid
863            self._sendSMBMessage(m)
864            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, result = result, error = error)
865            messages_history.append(m)
866
867        def closeCB(close_message, **kwargs):
868            if kwargs['result'] is not None:
869                callback(kwargs['result'])
870            elif kwargs['error'] is not None:
871                errback(OperationFailure('Failed to get the security descriptor of %s on %s: Query failed with errorcode 0x%08x' % ( path, service_name, kwargs['error'] ), messages_history))
872
873        if not self.connected_trees.has_key(service_name):
874            def connectCB(connect_message, **kwargs):
875                messages_history.append(connect_message)
876                if connect_message.status == 0:
877                    self.connected_trees[service_name] = connect_message.tid
878                    sendCreate(connect_message.tid)
879                else:
880                    errback(OperationFailure('Failed to get the security descriptor of %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
881
882            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
883            self._sendSMBMessage(m)
884            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
885            messages_history.append(m)
886        else:
887            sendCreate(self.connected_trees[service_name])
888
889    def _retrieveFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30):
890        return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0L, -1L, timeout)
891
892    def _retrieveFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):
893        if not self.has_authenticated:
894            raise NotReadyError('SMB connection not authenticated')
895
896        expiry_time = time.time() + timeout
897        path = path.replace('/', '\\')
898        if path.startswith('\\'):
899            path = path[1:]
900        if path.endswith('\\'):
901            path = path[:-1]
902        messages_history = [ ]
903        results = [ ]
904
905        def sendCreate(tid):
906            create_context_data = binascii.unhexlify("""
90728 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
90844 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
90900 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
91000 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
91100 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
91251 46 69 64 00 00 00 00
913""".replace(' ', '').replace('\n', ''))
914            m = SMB2Message(SMB2CreateRequest(path,
915                                              file_attributes = 0,
916                                              access_mask = FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,
917                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
918                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
919                                              impersonation = SEC_IMPERSONATE,
920                                              create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE,
921                                              create_disp = FILE_OPEN,
922                                              create_context_data = create_context_data))
923            m.tid = tid
924            self._sendSMBMessage(m)
925            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, createCB, errback, tid = tid)
926            messages_history.append(m)
927
928        def createCB(create_message, **kwargs):
929            messages_history.append(create_message)
930            if create_message.status == 0:
931                m = SMB2Message(SMB2QueryInfoRequest(create_message.payload.fid,
932                                                     flags = 0,
933                                                     additional_info = 0,
934                                                     info_type = SMB2_INFO_FILE,
935                                                     file_info_class = 0x16,  # FileStreamInformation [MS-FSCC] 2.4
936                                                     input_buf = '',
937                                                     output_buf_len = 4096))
938                m.tid = kwargs['tid']
939                self._sendSMBMessage(m)
940                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, infoCB, errback,
941                                                               tid = kwargs['tid'],
942                                                               fid = create_message.payload.fid,
943                                                               file_attributes = create_message.payload.file_attributes)
944                messages_history.append(m)
945            else:
946                errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history))
947
948        def infoCB(info_message, **kwargs):
949            messages_history.append(info_message)
950            if info_message.status == 0:
951                file_len = struct.unpack('<Q', info_message.payload.data[8:16])[0]
952                if max_length == 0 or starting_offset > file_len:
953                    closeFid(info_message.tid, kwargs['fid'])
954                    callback(( file_obj, kwargs['file_attributes'], 0 ))  # Note that this is a tuple of 3-elements
955                else:
956                    remaining_len = max_length
957                    if remaining_len < 0:
958                        remaining_len = file_len
959                    if starting_offset + remaining_len > file_len:
960                        remaining_len = file_len - starting_offset
961                    sendRead(kwargs['tid'], kwargs['fid'], starting_offset, remaining_len, 0, kwargs['file_attributes'])
962            else:
963                errback(OperationFailure('Failed to retrieve %s on %s: Unable to retrieve information on file' % ( path, service_name ), messages_history))
964
965        def sendRead(tid, fid, offset, remaining_len, read_len, file_attributes):
966            read_count = min(self.max_read_size, remaining_len)
967            m = SMB2Message(SMB2ReadRequest(fid, offset, read_count))
968            m.tid = tid
969            self._sendSMBMessage(m)
970            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback,
971                                                           tid = tid, fid = fid, offset = offset,
972                                                           remaining_len = remaining_len,
973                                                           read_len = read_len,
974                                                           file_attributes = file_attributes)
975
976        def readCB(read_message, **kwargs):
977            # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history.
978            if read_message.status == 0:
979                data_len = read_message.payload.data_length
980                file_obj.write(read_message.payload.data)
981
982                remaining_len = kwargs['remaining_len'] - data_len
983
984                if remaining_len > 0:
985                    sendRead(kwargs['tid'], kwargs['fid'], kwargs['offset'] + data_len, remaining_len, kwargs['read_len'] + data_len, kwargs['file_attributes'])
986                else:
987                    closeFid(kwargs['tid'], kwargs['fid'], ret = ( file_obj, kwargs['file_attributes'], kwargs['read_len'] + data_len ))
988            else:
989                messages_history.append(read_message)
990                closeFid(kwargs['tid'], kwargs['fid'], error = read_message.status)
991
992        def closeFid(tid, fid, ret = None, error = None):
993            m = SMB2Message(SMB2CloseRequest(fid))
994            m.tid = tid
995            self._sendSMBMessage(m)
996            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, ret = ret, error = error)
997            messages_history.append(m)
998
999        def closeCB(close_message, **kwargs):
1000            if kwargs['ret'] is not None:
1001                callback(kwargs['ret'])
1002            elif kwargs['error'] is not None:
1003                errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history))
1004
1005        if not self.connected_trees.has_key(service_name):
1006            def connectCB(connect_message, **kwargs):
1007                messages_history.append(connect_message)
1008                if connect_message.status == 0:
1009                    self.connected_trees[service_name] = connect_message.tid
1010                    sendCreate(connect_message.tid)
1011                else:
1012                    errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1013
1014            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1015            self._sendSMBMessage(m)
1016            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1017            messages_history.append(m)
1018        else:
1019            sendCreate(self.connected_trees[service_name])
1020
1021    def _storeFile_SMB2(self, service_name, path, file_obj, callback, errback, timeout = 30):
1022        self._storeFileFromOffset_SMB2(service_name, path, file_obj, callback, errback, 0L, True, timeout)
1023
1024    def _storeFileFromOffset_SMB2(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30):
1025        if not self.has_authenticated:
1026            raise NotReadyError('SMB connection not authenticated')
1027
1028        expiry_time = time.time() + timeout
1029        path = path.replace('/', '\\')
1030        if path.startswith('\\'):
1031            path = path[1:]
1032        if path.endswith('\\'):
1033            path = path[:-1]
1034        messages_history = [ ]
1035
1036        def sendCreate(tid):
1037            create_context_data = binascii.unhexlify("""
103828 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
103944 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
104000 00 00 00 00 00 00 00 20 00 00 00 10 00 04 00
104100 00 18 00 08 00 00 00 41 6c 53 69 00 00 00 00
104285 62 00 00 00 00 00 00 18 00 00 00 10 00 04 00
104300 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
104400 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
104551 46 69 64 00 00 00 00
1046""".replace(' ', '').replace('\n', ''))
1047            m = SMB2Message(SMB2CreateRequest(path,
1048                                              file_attributes = ATTR_ARCHIVE,
1049                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_READ_EA | FILE_WRITE_EA | READ_CONTROL | SYNCHRONIZE,
1050                                              share_access = 0,
1051                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1052                                              impersonation = SEC_IMPERSONATE,
1053                                              create_options = FILE_SEQUENTIAL_ONLY | FILE_NON_DIRECTORY_FILE,
1054                                              create_disp = FILE_OVERWRITE_IF if truncate else FILE_OPEN_IF,
1055                                              create_context_data = create_context_data))
1056            m.tid = tid
1057            self._sendSMBMessage(m)
1058            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1059            messages_history.append(m)
1060
1061        def createCB(create_message, **kwargs):
1062            create_message.tid = kwargs['tid']
1063            messages_history.append(create_message)
1064            if create_message.status == 0:
1065                sendWrite(create_message.tid, create_message.payload.fid, starting_offset)
1066            else:
1067                errback(OperationFailure('Failed to store %s on %s: Unable to open file' % ( path, service_name ), messages_history))
1068
1069        def sendWrite(tid, fid, offset):
1070            write_count = self.max_write_size
1071            data = file_obj.read(write_count)
1072            data_len = len(data)
1073            if data_len > 0:
1074                m = SMB2Message(SMB2WriteRequest(fid, data, offset))
1075                m.tid = tid
1076                self._sendSMBMessage(m)
1077                self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, tid = tid, fid = fid, offset = offset+data_len)
1078            else:
1079                closeFid(tid, fid, offset = offset)
1080
1081        def writeCB(write_message, **kwargs):
1082            # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history.
1083            if write_message.status == 0:
1084                sendWrite(kwargs['tid'], kwargs['fid'], kwargs['offset'])
1085            else:
1086                messages_history.append(write_message)
1087                closeFid(kwargs['tid'], kwargs['fid'])
1088                errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history))
1089
1090        def closeFid(tid, fid, error = None, offset = None):
1091            m = SMB2Message(SMB2CloseRequest(fid))
1092            m.tid = tid
1093            self._sendSMBMessage(m)
1094            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, fid = fid, offset = offset, error = error)
1095            messages_history.append(m)
1096
1097        def closeCB(close_message, **kwargs):
1098            if kwargs['offset'] is not None:
1099                callback(( file_obj, kwargs['offset'] ))  # Note that this is a tuple of 2-elements
1100            elif kwargs['error'] is not None:
1101                errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history))
1102
1103        if not self.connected_trees.has_key(service_name):
1104            def connectCB(connect_message, **kwargs):
1105                messages_history.append(connect_message)
1106                if connect_message.status == 0:
1107                    self.connected_trees[service_name] = connect_message.tid
1108                    sendCreate(connect_message.tid)
1109                else:
1110                    errback(OperationFailure('Failed to store %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1111
1112            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1113            self._sendSMBMessage(m)
1114            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
1115            messages_history.append(m)
1116        else:
1117            sendCreate(self.connected_trees[service_name])
1118
1119
1120    def _deleteFiles_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30):
1121        if not self.has_authenticated:
1122            raise NotReadyError('SMB connection not authenticated')
1123
1124        expiry_time = time.time() + timeout
1125        path = path_file_pattern.replace('/', '\\')
1126        if path.startswith('\\'):
1127            path = path[1:]
1128        if path.endswith('\\'):
1129            path = path[:-1]
1130        messages_history = [ ]
1131
1132        def sendCreate(tid):
1133            create_context_data = binascii.unhexlify("""
113428 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
113544 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
113600 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
113700 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
113800 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
113951 46 69 64 00 00 00 00
1140""".replace(' ', '').replace('\n', ''))
1141            m = SMB2Message(SMB2CreateRequest(path,
1142                                              file_attributes = 0,
1143                                              access_mask = DELETE | FILE_READ_ATTRIBUTES,
1144                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1145                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1146                                              impersonation = SEC_IMPERSONATE,
1147                                              create_options = FILE_NON_DIRECTORY_FILE,
1148                                              create_disp = FILE_OPEN,
1149                                              create_context_data = create_context_data))
1150            m.tid = tid
1151            self._sendSMBMessage(m)
1152            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1153            messages_history.append(m)
1154
1155        def createCB(open_message, **kwargs):
1156            open_message.tid = kwargs['tid']
1157            messages_history.append(open_message)
1158            if open_message.status == 0:
1159                sendDelete(open_message.tid, open_message.payload.fid)
1160            else:
1161                errback(OperationFailure('Failed to delete %s on %s: Unable to open file' % ( path, service_name ), messages_history))
1162
1163        def sendDelete(tid, fid):
1164            m = SMB2Message(SMB2SetInfoRequest(fid,
1165                                               additional_info = 0,
1166                                               info_type = SMB2_INFO_FILE,
1167                                               file_info_class = 0x0d,  # SMB2_FILE_DISPOSITION_INFO
1168                                               data = '\x01'))
1169            # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4.11
1170            m.tid = tid
1171            self._sendSMBMessage(m)
1172            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, fid = fid)
1173            messages_history.append(m)
1174
1175        def deleteCB(delete_message, **kwargs):
1176            messages_history.append(delete_message)
1177            if delete_message.status == 0:
1178                closeFid(kwargs['tid'], kwargs['fid'], status = 0)
1179            else:
1180                closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status)
1181
1182        def closeFid(tid, fid, status = None):
1183            m = SMB2Message(SMB2CloseRequest(fid))
1184            m.tid = tid
1185            self._sendSMBMessage(m)
1186            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, status = status)
1187            messages_history.append(m)
1188
1189        def closeCB(close_message, **kwargs):
1190            if kwargs['status'] == 0:
1191                callback(path_file_pattern)
1192            else:
1193                errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history))
1194
1195        if not self.connected_trees.has_key(service_name):
1196            def connectCB(connect_message, **kwargs):
1197                messages_history.append(connect_message)
1198                if connect_message.status == 0:
1199                    self.connected_trees[service_name] = connect_message.tid
1200                    sendCreate(connect_message.tid)
1201                else:
1202                    errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1203
1204            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1205            self._sendSMBMessage(m)
1206            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1207            messages_history.append(m)
1208        else:
1209            sendCreate(self.connected_trees[service_name])
1210
1211    def _resetFileAttributes_SMB2(self, service_name, path_file_pattern, callback, errback, timeout = 30):
1212        if not self.has_authenticated:
1213            raise NotReadyError('SMB connection not authenticated')
1214
1215        expiry_time = time.time() + timeout
1216        path = path_file_pattern.replace('/', '\\')
1217        if path.startswith('\\'):
1218            path = path[1:]
1219        if path.endswith('\\'):
1220            path = path[:-1]
1221        messages_history = [ ]
1222
1223        def sendCreate(tid):
1224            create_context_data = binascii.unhexlify("""
122528 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
122644 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
122700 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
122800 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
122900 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
123051 46 69 64 00 00 00 00
1231""".replace(' ', '').replace('\n', ''))
1232
1233            m = SMB2Message(SMB2CreateRequest(path,
1234                                              file_attributes = 0,
1235                                              access_mask = FILE_WRITE_ATTRIBUTES,
1236                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
1237                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1238                                              impersonation = SEC_IMPERSONATE,
1239                                              create_options = 0,
1240                                              create_disp = FILE_OPEN,
1241                                              create_context_data = create_context_data))
1242            m.tid = tid
1243            self._sendSMBMessage(m)
1244            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1245            messages_history.append(m)
1246
1247        def createCB(open_message, **kwargs):
1248            messages_history.append(open_message)
1249            if open_message.status == 0:
1250                sendReset(kwargs['tid'], open_message.payload.fid)
1251            else:
1252                errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to open file' % ( path, service_name ), messages_history))
1253
1254        def sendReset(tid, fid):
1255            m = SMB2Message(SMB2SetInfoRequest(fid,
1256                                               additional_info = 0,
1257                                               info_type = SMB2_INFO_FILE,
1258                                               file_info_class = 4,  # FileBasicInformation
1259                                               data = struct.pack('qqqqii',0,0,0,0,0x80,0))) # FILE_ATTRIBUTE_NORMAL
1260            # [MS-SMB2]: 2.2.39, [MS-FSCC]: 2.4, [MS-FSCC]: 2.4.7, [MS-FSCC]: 2.6
1261            m.tid = tid
1262            self._sendSMBMessage(m)
1263            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, resetCB, errback, tid = tid, fid = fid)
1264            messages_history.append(m)
1265
1266        def resetCB(reset_message, **kwargs):
1267            messages_history.append(reset_message)
1268            if reset_message.status == 0:
1269                closeFid(kwargs['tid'], kwargs['fid'], status = 0)
1270            else:
1271                closeFid(kwargs['tid'], kwargs['fid'], status = reset_message.status)
1272
1273        def closeFid(tid, fid, status = None):
1274            m = SMB2Message(SMB2CloseRequest(fid))
1275            m.tid = tid
1276            self._sendSMBMessage(m)
1277            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback, status = status)
1278            messages_history.append(m)
1279
1280        def closeCB(close_message, **kwargs):
1281            if kwargs['status'] == 0:
1282                callback(path_file_pattern)
1283            else:
1284                errback(OperationFailure('Failed to reset attributes of %s on %s: Reset failed' % ( path, service_name ), messages_history))
1285
1286        if not self.connected_trees.has_key(service_name):
1287            def connectCB(connect_message, **kwargs):
1288                messages_history.append(connect_message)
1289                if connect_message.status == 0:
1290                    self.connected_trees[service_name] = connect_message.tid
1291                    sendCreate(connect_message.tid)
1292                else:
1293                    errback(OperationFailure('Failed to reset attributes of %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1294
1295            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1296            self._sendSMBMessage(m)
1297            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1298            messages_history.append(m)
1299        else:
1300            sendCreate(self.connected_trees[service_name])
1301
1302    def _createDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30):
1303        if not self.has_authenticated:
1304            raise NotReadyError('SMB connection not authenticated')
1305
1306        expiry_time = time.time() + timeout
1307        path = path.replace('/', '\\')
1308        if path.startswith('\\'):
1309            path = path[1:]
1310        if path.endswith('\\'):
1311            path = path[:-1]
1312        messages_history = [ ]
1313
1314        def sendCreate(tid):
1315            create_context_data = binascii.unhexlify("""
131628 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
131744 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
131800 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
131900 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
132000 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
132151 46 69 64 00 00 00 00
1322""".replace(' ', '').replace('\n', ''))
1323            m = SMB2Message(SMB2CreateRequest(path,
1324                                              file_attributes = 0,
1325                                              access_mask = FILE_READ_DATA | FILE_WRITE_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | READ_CONTROL | DELETE | SYNCHRONIZE,
1326                                              share_access = 0,
1327                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1328                                              impersonation = SEC_IMPERSONATE,
1329                                              create_options = FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1330                                              create_disp = FILE_CREATE,
1331                                              create_context_data = create_context_data))
1332            m.tid = tid
1333            self._sendSMBMessage(m)
1334            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1335            messages_history.append(m)
1336
1337        def createCB(create_message, **kwargs):
1338            messages_history.append(create_message)
1339            if create_message.status == 0:
1340                closeFid(kwargs['tid'], create_message.payload.fid)
1341            else:
1342                errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history))
1343
1344        def closeFid(tid, fid):
1345            m = SMB2Message(SMB2CloseRequest(fid))
1346            m.tid = tid
1347            self._sendSMBMessage(m)
1348            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, closeCB, errback)
1349            messages_history.append(m)
1350
1351        def closeCB(close_message, **kwargs):
1352            callback(path)
1353
1354        if not self.connected_trees.has_key(service_name):
1355            def connectCB(connect_message, **kwargs):
1356                messages_history.append(connect_message)
1357                if connect_message.status == 0:
1358                    self.connected_trees[service_name] = connect_message.tid
1359                    sendCreate(connect_message.tid)
1360                else:
1361                    errback(OperationFailure('Failed to create directory %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1362
1363            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1364            self._sendSMBMessage(m)
1365            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1366            messages_history.append(m)
1367        else:
1368            sendCreate(self.connected_trees[service_name])
1369
1370    def _deleteDirectory_SMB2(self, service_name, path, callback, errback, timeout = 30):
1371        if not self.has_authenticated:
1372            raise NotReadyError('SMB connection not authenticated')
1373
1374        expiry_time = time.time() + timeout
1375        path = path.replace('/', '\\')
1376        if path.startswith('\\'):
1377            path = path[1:]
1378        if path.endswith('\\'):
1379            path = path[:-1]
1380        messages_history = [ ]
1381
1382        def sendCreate(tid):
1383            create_context_data = binascii.unhexlify("""
138428 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
138544 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
138600 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
138700 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
138800 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
138951 46 69 64 00 00 00 00
1390""".replace(' ', '').replace('\n', ''))
1391            m = SMB2Message(SMB2CreateRequest(path,
1392                                              file_attributes = 0,
1393                                              access_mask = DELETE | FILE_READ_ATTRIBUTES,
1394                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1395                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1396                                              impersonation = SEC_IMPERSONATE,
1397                                              create_options = FILE_DIRECTORY_FILE,
1398                                              create_disp = FILE_OPEN,
1399                                              create_context_data = create_context_data))
1400            m.tid = tid
1401            self._sendSMBMessage(m)
1402            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1403            messages_history.append(m)
1404
1405        def createCB(open_message, **kwargs):
1406            messages_history.append(open_message)
1407            if open_message.status == 0:
1408                sendDelete(kwargs['tid'], open_message.payload.fid)
1409            else:
1410                errback(OperationFailure('Failed to delete %s on %s: Unable to open directory' % ( path, service_name ), messages_history))
1411
1412        def sendDelete(tid, fid):
1413            m = SMB2Message(SMB2SetInfoRequest(fid,
1414                                               additional_info = 0,
1415                                               info_type = SMB2_INFO_FILE,
1416                                               file_info_class = 0x0d,  # SMB2_FILE_DISPOSITION_INFO
1417                                               data = '\x01'))
1418            m.tid = tid
1419            self._sendSMBMessage(m)
1420            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback, tid = tid, fid = fid)
1421            messages_history.append(m)
1422
1423        def deleteCB(delete_message, **kwargs):
1424            messages_history.append(delete_message)
1425            if delete_message.status == 0:
1426                closeFid(kwargs['tid'], kwargs['fid'], status = 0)
1427            else:
1428                closeFid(kwargs['tid'], kwargs['fid'], status = delete_message.status)
1429
1430        def closeFid(tid, fid, status = None):
1431            m = SMB2Message(SMB2CloseRequest(fid))
1432            m.tid = tid
1433            self._sendSMBMessage(m)
1434            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status)
1435            messages_history.append(m)
1436
1437        def closeCB(close_message, **kwargs):
1438            if kwargs['status'] == 0:
1439                callback(path)
1440            else:
1441                errback(OperationFailure('Failed to delete %s on %s: Delete failed' % ( path, service_name ), messages_history))
1442
1443        if not self.connected_trees.has_key(service_name):
1444            def connectCB(connect_message, **kwargs):
1445                messages_history.append(connect_message)
1446                if connect_message.status == 0:
1447                    self.connected_trees[service_name] = connect_message.tid
1448                    sendCreate(connect_message.tid)
1449                else:
1450                    errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1451
1452            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1453            self._sendSMBMessage(m)
1454            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1455            messages_history.append(m)
1456        else:
1457            sendCreate(self.connected_trees[service_name])
1458
1459    def _rename_SMB2(self, service_name, old_path, new_path, callback, errback, timeout = 30):
1460        if not self.has_authenticated:
1461            raise NotReadyError('SMB connection not authenticated')
1462
1463        expiry_time = time.time() + timeout
1464        messages_history = [ ]
1465
1466        new_path = new_path.replace('/', '\\')
1467        if new_path.startswith('\\'):
1468            new_path = new_path[1:]
1469        if new_path.endswith('\\'):
1470            new_path = new_path[:-1]
1471
1472        old_path = old_path.replace('/', '\\')
1473        if old_path.startswith('\\'):
1474            old_path = old_path[1:]
1475        if old_path.endswith('\\'):
1476            old_path = old_path[:-1]
1477
1478        def sendCreate(tid):
1479            create_context_data = binascii.unhexlify("""
148028 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
148144 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
148200 00 00 00 00 00 00 00 18 00 00 00 10 00 04 00
148300 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
148400 00 00 00 10 00 04 00 00 00 18 00 00 00 00 00
148551 46 69 64 00 00 00 00
1486""".replace(' ', '').replace('\n', ''))
1487            m = SMB2Message(SMB2CreateRequest(old_path,
1488                                              file_attributes = 0,
1489                                              access_mask = DELETE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
1490                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1491                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1492                                              impersonation = SEC_IMPERSONATE,
1493                                              create_options = FILE_SYNCHRONOUS_IO_NONALERT,
1494                                              create_disp = FILE_OPEN,
1495                                              create_context_data = create_context_data))
1496            m.tid = tid
1497            self._sendSMBMessage(m)
1498            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1499            messages_history.append(m)
1500
1501        def createCB(create_message, **kwargs):
1502            messages_history.append(create_message)
1503            if create_message.status == 0:
1504                sendRename(kwargs['tid'], create_message.payload.fid)
1505            else:
1506                errback(OperationFailure('Failed to rename %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history))
1507
1508        def sendRename(tid, fid):
1509            data = '\x00'*16 + struct.pack('<I', len(new_path)*2) + new_path.encode('UTF-16LE')
1510            m = SMB2Message(SMB2SetInfoRequest(fid,
1511                                               additional_info = 0,
1512                                               info_type = SMB2_INFO_FILE,
1513                                               file_info_class = 0x0a,  # SMB2_FILE_RENAME_INFO
1514                                               data = data))
1515            m.tid = tid
1516            self._sendSMBMessage(m)
1517            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback, tid = tid, fid = fid)
1518            messages_history.append(m)
1519
1520        def renameCB(rename_message, **kwargs):
1521            messages_history.append(rename_message)
1522            if rename_message.status == 0:
1523                closeFid(kwargs['tid'], kwargs['fid'], status = 0)
1524            else:
1525                closeFid(kwargs['tid'], kwargs['fid'], status = rename_message.status)
1526
1527        def closeFid(tid, fid, status = None):
1528            m = SMB2Message(SMB2CloseRequest(fid))
1529            m.tid = tid
1530            self._sendSMBMessage(m)
1531            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status)
1532            messages_history.append(m)
1533
1534        def closeCB(close_message, **kwargs):
1535            if kwargs['status'] == 0:
1536                callback(( old_path, new_path ))
1537            else:
1538                errback(OperationFailure('Failed to rename %s on %s: Rename failed' % ( old_path, service_name ), messages_history))
1539
1540        if not self.connected_trees.has_key(service_name):
1541            def connectCB(connect_message, **kwargs):
1542                messages_history.append(connect_message)
1543                if connect_message.status == 0:
1544                    self.connected_trees[service_name] = connect_message.tid
1545                    sendCreate(connect_message.tid)
1546                else:
1547                    errback(OperationFailure('Failed to rename %s on %s: Unable to connect to shared device' % ( old_path, service_name ), messages_history))
1548
1549            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1550            self._sendSMBMessage(m)
1551            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1552            messages_history.append(m)
1553        else:
1554            sendCreate(self.connected_trees[service_name])
1555
1556    def _listSnapshots_SMB2(self, service_name, path, callback, errback, timeout = 30):
1557        if not self.has_authenticated:
1558            raise NotReadyError('SMB connection not authenticated')
1559
1560        expiry_time = time.time() + timeout
1561        path = path.replace('/', '\\')
1562        if path.startswith('\\'):
1563            path = path[1:]
1564        if path.endswith('\\'):
1565            path = path[:-1]
1566        messages_history = [ ]
1567
1568        def sendCreate(tid):
1569            create_context_data = binascii.unhexlify("""
157028 00 00 00 10 00 04 00 00 00 18 00 10 00 00 00
157144 48 6e 51 00 00 00 00 00 00 00 00 00 00 00 00
157200 00 00 00 00 00 00 00 00 00 00 00 10 00 04 00
157300 00 18 00 00 00 00 00 4d 78 41 63 00 00 00 00
1574""".replace(' ', '').replace('\n', ''))
1575            m = SMB2Message(SMB2CreateRequest(path,
1576                                              file_attributes = 0,
1577                                              access_mask = FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
1578                                              share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
1579                                              oplock = SMB2_OPLOCK_LEVEL_NONE,
1580                                              impersonation = SEC_IMPERSONATE,
1581                                              create_options = FILE_SYNCHRONOUS_IO_NONALERT,
1582                                              create_disp = FILE_OPEN,
1583                                              create_context_data = create_context_data))
1584            m.tid = tid
1585            self._sendSMBMessage(m)
1586            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback, tid = tid)
1587            messages_history.append(m)
1588
1589        def createCB(create_message, **kwargs):
1590            messages_history.append(create_message)
1591            if create_message.status == 0:
1592                sendEnumSnapshots(kwargs['tid'], create_message.payload.fid)
1593            else:
1594                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open file/directory' % ( old_path, service_name ), messages_history))
1595
1596        def sendEnumSnapshots(tid, fid):
1597            m = SMB2Message(SMB2IoctlRequest(fid,
1598                                             ctlcode = 0x00144064,  # FSCTL_SRV_ENUMERATE_SNAPSHOTS
1599                                             flags = 0x0001,
1600                                             in_data = ''))
1601            m.tid = tid
1602            self._sendSMBMessage(m)
1603            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, enumSnapshotsCB, errback, tid = tid, fid = fid)
1604            messages_history.append(m)
1605
1606        def enumSnapshotsCB(enum_message, **kwargs):
1607            messages_history.append(enum_message)
1608            if enum_message.status == 0:
1609                results = [ ]
1610                snapshots_count = struct.unpack('<I', enum_message.payload.out_data[4:8])[0]
1611                for i in range(0, snapshots_count):
1612                    s = enum_message.payload.out_data[12+i*50:12+48+i*50].decode('UTF-16LE')
1613                    results.append(datetime(*map(int, ( s[5:9], s[10:12], s[13:15], s[16:18], s[19:21], s[22:24] ))))
1614                closeFid(kwargs['tid'], kwargs['fid'], results = results)
1615            else:
1616                closeFid(kwargs['tid'], kwargs['fid'], status = enum_message.status)
1617
1618        def closeFid(tid, fid, status = None, results = None):
1619            m = SMB2Message(SMB2CloseRequest(fid))
1620            m.tid = tid
1621            self._sendSMBMessage(m)
1622            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, closeCB, errback, status = status, results = results)
1623            messages_history.append(m)
1624
1625        def closeCB(close_message, **kwargs):
1626            if kwargs['results'] is not None:
1627                callback(kwargs['results'])
1628            else:
1629                errback(OperationFailure('Failed to list snapshots %s on %s: List failed' % ( path, service_name ), messages_history))
1630
1631        if not self.connected_trees.has_key(service_name):
1632            def connectCB(connect_message, **kwargs):
1633                messages_history.append(connect_message)
1634                if connect_message.status == 0:
1635                    self.connected_trees[service_name] = connect_message.tid
1636                    sendCreate(connect_message.tid)
1637                else:
1638                    errback(OperationFailure('Failed to list snapshots %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
1639
1640            m = SMB2Message(SMB2TreeConnectRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name )))
1641            self._sendSMBMessage(m)
1642            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
1643            messages_history.append(m)
1644        else:
1645            sendCreate(self.connected_trees[service_name])
1646
1647    def _echo_SMB2(self, data, callback, errback, timeout = 30):
1648        messages_history = [ ]
1649
1650        def echoCB(echo_message, **kwargs):
1651            messages_history.append(echo_message)
1652            if echo_message.status == 0:
1653                callback(data)
1654            else:
1655                errback(OperationFailure('Echo failed', messages_history))
1656
1657        m = SMB2Message(SMB2EchoRequest())
1658        self._sendSMBMessage(m)
1659        self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback)
1660        messages_history.append(m)
1661
1662
1663    #
1664    # SMB1 Methods Family
1665    #
1666
1667    def _sendSMBMessage_SMB1(self, smb_message):
1668        if smb_message.mid == 0:
1669            smb_message.mid = self._getNextMID_SMB1()
1670        if not smb_message.uid:
1671            smb_message.uid = self.uid
1672        if self.is_signing_active:
1673            smb_message.flags2 |= SMB_FLAGS2_SMB_SECURITY_SIGNATURE
1674
1675            # Increment the next_signing_id as described in [MS-CIFS] 3.2.4.1.3
1676            smb_message.security = self.next_signing_id
1677            self.next_signing_id += 2  # All our defined messages currently have responses, so always increment by 2
1678            raw_data = smb_message.encode()
1679
1680            md = ntlm.MD5(self.signing_session_key)
1681            if self.signing_challenge_response:
1682                md.update(self.signing_challenge_response)
1683            md.update(raw_data)
1684            signature = md.digest()[:8]
1685
1686            self.log.debug('MID is %d. Signing ID is %d. Signature is %s. Total raw message is %d bytes', smb_message.mid, smb_message.security, binascii.hexlify(signature), len(raw_data))
1687            smb_message.raw_data = raw_data[:14] + signature + raw_data[22:]
1688        else:
1689            smb_message.raw_data = smb_message.encode()
1690        self.sendNMBMessage(smb_message.raw_data)
1691
1692    def _getNextMID_SMB1(self):
1693        self.mid += 1
1694        if self.mid >= 0xFFFF: # MID cannot be 0xFFFF. [MS-CIFS]: 2.2.1.6.2
1695            # We don't use MID of 0 as MID can be reused for SMB_COM_TRANSACTION2_SECONDARY messages
1696            # where if mid=0, _sendSMBMessage will re-assign new MID values again
1697            self.mid = 1
1698        return self.mid
1699
1700    def _updateState_SMB1(self, message):
1701        if message.isReply:
1702            if message.command == SMB_COM_NEGOTIATE:
1703                if not message.status.hasError:
1704                    self.has_negotiated = True
1705                    self.log.info('SMB dialect negotiation successful (ExtendedSecurity:%s)', message.hasExtendedSecurity)
1706                    self._updateServerInfo(message.payload)
1707                    self._handleNegotiateResponse(message)
1708                else:
1709                    raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_NEGOTIATE' % message.status.internal_value,
1710                                        message.raw_data, message)
1711            elif message.command == SMB_COM_SESSION_SETUP_ANDX:
1712                if message.hasExtendedSecurity:
1713                    if not message.status.hasError:
1714                        try:
1715                            result = securityblob.decodeAuthResponseSecurityBlob(message.payload.security_blob)
1716                            if result == securityblob.RESULT_ACCEPT_COMPLETED:
1717                                self.log.debug('SMB uid is now %d', message.uid)
1718                                self.uid = message.uid
1719                                self.has_authenticated = True
1720                                self.log.info('Authentication (with extended security) successful!')
1721                                self.onAuthOK()
1722                            else:
1723                                raise ProtocolError('SMB_COM_SESSION_SETUP_ANDX status is 0 but security blob negResult value is %d' % result, message.raw_data, message)
1724                        except securityblob.BadSecurityBlobError, ex:
1725                            raise ProtocolError(str(ex), message.raw_data, message)
1726                    elif message.status.internal_value == 0xc0000016:  # STATUS_MORE_PROCESSING_REQUIRED
1727                        try:
1728                            result, ntlm_token = securityblob.decodeChallengeSecurityBlob(message.payload.security_blob)
1729                            if result == securityblob.RESULT_ACCEPT_INCOMPLETE:
1730                                self._handleSessionChallenge(message, ntlm_token)
1731                        except ( securityblob.BadSecurityBlobError, securityblob.UnsupportedSecurityProvider ), ex:
1732                            raise ProtocolError(str(ex), message.raw_data, message)
1733                    elif (message.status.internal_value == 0xc000006d    # STATUS_LOGON_FAILURE
1734                        or message.status.internal_value == 0xc0000064   # STATUS_NO_SUCH_USER
1735                        or message.status.internal_value == 0xc000006a): # STATUS_WRONG_PASSWORD
1736                        self.has_authenticated = False
1737                        self.log.info('Authentication (with extended security) failed. Please check username and password.')
1738                        self.onAuthFailed()
1739                    elif (message.status.internal_value == 0xc0000193    # STATUS_ACCOUNT_EXPIRED
1740                        or message.status.internal_value == 0xC0000071): # STATUS_PASSWORD_EXPIRED
1741                        self.has_authenticated = False
1742                        self.log.info('Authentication (with extended security) failed. Account or password has expired.')
1743                        self.onAuthFailed()
1744                    elif message.status.internal_value == 0xc0000234: # STATUS_ACCOUNT_LOCKED_OUT
1745                        self.has_authenticated = False
1746                        self.log.info('Authentication (with extended security) failed. Account has been locked due to too many invalid logon attempts.')
1747                        self.onAuthFailed()
1748                    elif message.status.internal_value == 0xc0000072: # STATUS_ACCOUNT_DISABLED
1749                        self.has_authenticated = False
1750                        self.log.info('Authentication (with extended security) failed. Account has been disabled.')
1751                        self.onAuthFailed()
1752                    elif (message.status.internal_value == 0xc000006f    # STATUS_INVALID_LOGON_HOURS
1753                        or message.status.internal_value == 0xc000015b   # STATUS_LOGON_TYPE_NOT_GRANTED
1754                        or message.status.internal_value == 0xc0000070): # STATUS_INVALID_WORKSTATION
1755                        self.has_authenticated = False
1756                        self.log.info('Authentication (with extended security) failed. Not allowed.')
1757                        self.onAuthFailed()
1758                    elif message.status.internal_value == 0xc000018c:  # STATUS_TRUSTED_DOMAIN_FAILURE
1759                        self.has_authenticated = False
1760                        self.log.info('Authentication (with extended security) failed. Domain not trusted.')
1761                        self.onAuthFailed()
1762                    elif message.status.internal_value == 0xc000018d:  # STATUS_TRUSTED_RELATIONSHIP_FAILURE
1763                        self.has_authenticated = False
1764                        self.log.info('Authentication (with extended security) failed. Workstation not trusted.')
1765                        self.onAuthFailed()
1766                    else:
1767                        raise ProtocolError('Unknown status value (0x%08X) in SMB_COM_SESSION_SETUP_ANDX (with extended security)' % message.status.internal_value,
1768                                            message.raw_data, message)
1769                else:
1770                    if message.status.internal_value == 0:
1771                        self.log.debug('SMB uid is now %d', message.uid)
1772                        self.uid = message.uid
1773                        self.has_authenticated = True
1774                        self.log.info('Authentication (without extended security) successful!')
1775                        self.onAuthOK()
1776                    else:
1777                        self.has_authenticated = False
1778                        self.log.info('Authentication (without extended security) failed. Please check username and password')
1779                        self.onAuthFailed()
1780            elif message.command == SMB_COM_TREE_CONNECT_ANDX:
1781                try:
1782                    req = self.pending_requests[message.mid]
1783                except KeyError:
1784                    pass
1785                else:
1786                    if not message.status.hasError:
1787                        self.connected_trees[req.kwargs['path']] = message.tid
1788
1789            req = self.pending_requests.pop(message.mid, None)
1790            if req:
1791                req.callback(message, **req.kwargs)
1792                return True
1793
1794
1795    def _updateServerInfo_SMB1(self, payload):
1796        self.capabilities = payload.capabilities
1797        self.security_mode = payload.security_mode
1798        self.max_raw_size = payload.max_raw_size
1799        self.max_buffer_size = payload.max_buffer_size
1800        self.max_mpx_count = payload.max_mpx_count
1801        self.use_plaintext_authentication = not bool(payload.security_mode & NEGOTIATE_ENCRYPT_PASSWORDS)
1802
1803        if self.use_plaintext_authentication:
1804            self.log.warning('Remote server only supports plaintext authentication. Your password can be stolen easily over the network.')
1805
1806
1807    def _handleSessionChallenge_SMB1(self, message, ntlm_token):
1808        assert message.hasExtendedSecurity
1809
1810        if message.uid and not self.uid:
1811            self.uid = message.uid
1812
1813        server_challenge, server_flags, server_info = ntlm.decodeChallengeMessage(ntlm_token)
1814        if self.use_ntlm_v2:
1815            self.log.info('Performing NTLMv2 authentication (with extended security) with server challenge "%s"', binascii.hexlify(server_challenge))
1816            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV2(self.password,
1817                                                                                                         self.username,
1818                                                                                                         server_challenge,
1819                                                                                                         server_info,
1820                                                                                                         self.domain)
1821
1822        else:
1823            self.log.info('Performing NTLMv1 authentication (with extended security) with server challenge "%s"', binascii.hexlify(server_challenge))
1824            nt_challenge_response, lm_challenge_response, session_key = ntlm.generateChallengeResponseV1(self.password, server_challenge, True)
1825
1826        ntlm_data = ntlm.generateAuthenticateMessage(server_flags,
1827                                                     nt_challenge_response,
1828                                                     lm_challenge_response,
1829                                                     session_key,
1830                                                     self.username,
1831                                                     self.domain,
1832                                                     self.my_name)
1833
1834        if self.log.isEnabledFor(logging.DEBUG):
1835            self.log.debug('NT challenge response is "%s" (%d bytes)', binascii.hexlify(nt_challenge_response), len(nt_challenge_response))
1836            self.log.debug('LM challenge response is "%s" (%d bytes)', binascii.hexlify(lm_challenge_response), len(lm_challenge_response))
1837
1838        blob = securityblob.generateAuthSecurityBlob(ntlm_data)
1839        self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__WithSecurityExtension(0, blob)))
1840
1841        if self.security_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRE:
1842            self.log.info('Server requires all SMB messages to be signed')
1843            self.is_signing_active = (self.sign_options != SMB.SIGN_NEVER)
1844        elif self.security_mode & NEGOTIATE_SECURITY_SIGNATURES_ENABLE:
1845            self.log.info('Server supports SMB signing')
1846            self.is_signing_active = (self.sign_options == SMB.SIGN_WHEN_SUPPORTED)
1847        else:
1848            self.is_signing_active = False
1849
1850        if self.is_signing_active:
1851            self.log.info("SMB signing activated. All SMB messages will be signed.")
1852            self.signing_session_key = session_key
1853            if self.capabilities & CAP_EXTENDED_SECURITY:
1854                self.signing_challenge_response = None
1855            else:
1856                self.signing_challenge_response = blob
1857        else:
1858            self.log.info("SMB signing deactivated. SMB messages will NOT be signed.")
1859
1860
1861    def _handleNegotiateResponse_SMB1(self, message):
1862        if message.uid and not self.uid:
1863            self.uid = message.uid
1864
1865        if message.hasExtendedSecurity or message.payload.supportsExtendedSecurity:
1866            ntlm_data = ntlm.generateNegotiateMessage()
1867            blob = securityblob.generateNegotiateSecurityBlob(ntlm_data)
1868            self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__WithSecurityExtension(message.payload.session_key, blob)))
1869        else:
1870            nt_password, _, _ = ntlm.generateChallengeResponseV1(self.password, message.payload.challenge, False)
1871            self.log.info('Performing NTLMv1 authentication (without extended security) with challenge "%s" and hashed password of "%s"',
1872                          binascii.hexlify(message.payload.challenge),
1873                          binascii.hexlify(nt_password))
1874            self._sendSMBMessage(SMBMessage(ComSessionSetupAndxRequest__NoSecurityExtension(message.payload.session_key,
1875                                                                                           self.username,
1876                                                                                           nt_password,
1877                                                                                           True,
1878                                                                                           self.domain)))
1879
1880    def _listShares_SMB1(self, callback, errback, timeout = 30):
1881        if not self.has_authenticated:
1882            raise NotReadyError('SMB connection not authenticated')
1883
1884        expiry_time = time.time() + timeout
1885        path = 'IPC$'
1886        messages_history = [ ]
1887
1888        def connectSrvSvc(tid):
1889            m = SMBMessage(ComNTCreateAndxRequest('\\srvsvc',
1890                                                  flags = NT_CREATE_REQUEST_EXTENDED_RESPONSE,
1891                                                  access_mask = READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA,
1892                                                  share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
1893                                                  create_disp = FILE_OPEN,
1894                                                  create_options = FILE_OPEN_NO_RECALL | FILE_NON_DIRECTORY_FILE,
1895                                                  impersonation = SEC_IMPERSONATE,
1896                                                  security_flags = 0))
1897            m.tid = tid
1898            self._sendSMBMessage(m)
1899            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectSrvSvcCB, errback)
1900            messages_history.append(m)
1901
1902        def connectSrvSvcCB(create_message, **kwargs):
1903            messages_history.append(create_message)
1904            if not create_message.status.hasError:
1905                call_id = self._getNextRPCCallID()
1906                # See [MS-CIFS]: 2.2.5.6.1 for more information on TRANS_TRANSACT_NMPIPE (0x0026) parameters
1907                setup_bytes = struct.pack('<HH', 0x0026, create_message.payload.fid)
1908                # The data_bytes are binding call to Server Service RPC using DCE v1.1 RPC over SMB. See [MS-SRVS] and [C706]
1909                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
1910                data_bytes = \
1911                    binascii.unhexlify("""05 00 0b 03 10 00 00 00 48 00 00 00""".replace(' ', '')) + \
1912                    struct.pack('<I', call_id) + \
1913                    binascii.unhexlify("""
1914b8 10 b8 10 00 00 00 00 01 00 00 00 00 00 01 00
1915c8 4f 32 4b 70 16 d3 01 12 78 5a 47 bf 6e e1 88
191603 00 00 00 04 5d 88 8a eb 1c c9 11 9f e8 08 00
19172b 10 48 60 02 00 00 00""".replace(' ', '').replace('\n', ''))
1918                m = SMBMessage(ComTransactionRequest(max_params_count = 0,
1919                                                     max_data_count = 4280,
1920                                                     max_setup_count = 0,
1921                                                     data_bytes = data_bytes,
1922                                                     setup_bytes = setup_bytes))
1923                m.tid = create_message.tid
1924                self._sendSMBMessage(m)
1925                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, rpcBindCB, errback, fid = create_message.payload.fid)
1926                messages_history.append(m)
1927            else:
1928                errback(OperationFailure('Failed to list shares: Unable to locate Server Service RPC endpoint', messages_history))
1929
1930        def rpcBindCB(trans_message, **kwargs):
1931            messages_history.append(trans_message)
1932            if not trans_message.status.hasError:
1933                call_id = self._getNextRPCCallID()
1934
1935                padding = ''
1936                server_len = len(self.remote_name) + 1
1937                server_bytes_len = server_len * 2
1938                if server_len % 2 != 0:
1939                    padding = '\0\0'
1940                    server_bytes_len += 2
1941
1942                # See [MS-CIFS]: 2.2.5.6.1 for more information on TRANS_TRANSACT_NMPIPE (0x0026) parameters
1943                setup_bytes = struct.pack('<HH', 0x0026, kwargs['fid'])
1944                # The data bytes are the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
1945                # If you wish to understand the meanings of the byte stream, I would suggest you use a recent version of WireShark to packet capture the stream
1946                data_bytes = \
1947                    binascii.unhexlify("""05 00 00 03 10 00 00 00""".replace(' ', '')) + \
1948                    struct.pack('<HHI', 72+server_bytes_len, 0, call_id) + \
1949                    binascii.unhexlify("""4c 00 00 00 00 00 0f 00 00 00 02 00""".replace(' ', '')) + \
1950                    struct.pack('<III', server_len, 0, server_len) + \
1951                    (self.remote_name + '\0').encode('UTF-16LE') + padding + \
1952                    binascii.unhexlify("""
195301 00 00 00 01 00 00 00 04 00 02 00 00 00 00 00
195400 00 00 00 ff ff ff ff 08 00 02 00 00 00 00 00
1955""".replace(' ', '').replace('\n', ''))
1956                m = SMBMessage(ComTransactionRequest(max_params_count = 0,
1957                                                     max_data_count = 4280,
1958                                                     max_setup_count = 0,
1959                                                     data_bytes = data_bytes,
1960                                                     setup_bytes = setup_bytes))
1961                m.tid = trans_message.tid
1962                self._sendSMBMessage(m)
1963                self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, listShareResultsCB, errback, fid = kwargs['fid'])
1964                messages_history.append(m)
1965            else:
1966                closeFid(trans_message.tid, kwargs['fid'])
1967                errback(OperationFailure('Failed to list shares: Unable to bind to Server Service RPC endpoint', messages_history))
1968
1969        def listShareResultsCB(result_message, **kwargs):
1970            messages_history.append(result_message)
1971            if not result_message.status.hasError:
1972                # The payload.data_bytes will contain the results of the RPC call to NetrShareEnum (Opnum 15) at Server Service RPC.
1973                data_bytes = result_message.payload.data_bytes
1974
1975                if ord(data_bytes[3]) & 0x02 == 0:
1976                    sendReadRequest(result_message.tid, kwargs['fid'], data_bytes)
1977                else:
1978                    decodeResults(result_message.tid, kwargs['fid'], data_bytes)
1979            else:
1980                closeFid(result_message.tid, kwargs['fid'])
1981                errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
1982
1983        def decodeResults(tid, fid, data_bytes):
1984            shares_count = struct.unpack('<I', data_bytes[36:40])[0]
1985            results = [ ]     # A list of SharedDevice instances
1986            offset = 36 + 12  # You need to study the byte stream to understand the meaning of these constants
1987            for i in range(0, shares_count):
1988                results.append(SharedDevice(struct.unpack('<I', data_bytes[offset+4:offset+8])[0], None, None))
1989                offset += 12
1990
1991            for i in range(0, shares_count):
1992                max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
1993                offset += 12
1994                results[i].name = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE')
1995
1996                if length % 2 != 0:
1997                    offset += (length * 2 + 2)
1998                else:
1999                    offset += (length * 2)
2000
2001                max_length, _, length = struct.unpack('<III', data_bytes[offset:offset+12])
2002                offset += 12
2003                results[i].comments = unicode(data_bytes[offset:offset+length*2-2], 'UTF-16LE')
2004
2005                if length % 2 != 0:
2006                    offset += (length * 2 + 2)
2007                else:
2008                    offset += (length * 2)
2009
2010            closeFid(tid, fid)
2011            callback(results)
2012
2013        def sendReadRequest(tid, fid, data_bytes):
2014            read_count = min(4280, self.max_raw_size - 2)
2015            m = SMBMessage(ComReadAndxRequest(fid = fid,
2016                                              offset = 0,
2017                                              max_return_bytes_count = read_count,
2018                                              min_return_bytes_count = read_count))
2019            m.tid = tid
2020            self._sendSMBMessage(m)
2021            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, data_bytes = data_bytes)
2022
2023        def readCB(read_message, **kwargs):
2024            messages_history.append(read_message)
2025            if not read_message.status.hasError:
2026                data_bytes = read_message.payload.data
2027
2028                if ord(data_bytes[3]) & 0x02 == 0:
2029                    sendReadRequest(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
2030                else:
2031                    decodeResults(read_message.tid, kwargs['fid'], kwargs['data_bytes'] + data_bytes[24:])
2032            else:
2033                closeFid(read_message.tid, kwargs['fid'])
2034                errback(OperationFailure('Failed to list shares: Unable to retrieve shared device list', messages_history))
2035
2036        def closeFid(tid, fid):
2037            m = SMBMessage(ComCloseRequest(fid))
2038            m.tid = tid
2039            self._sendSMBMessage(m)
2040            messages_history.append(m)
2041
2042        def connectCB(connect_message, **kwargs):
2043            messages_history.append(connect_message)
2044            if not connect_message.status.hasError:
2045                self.connected_trees[path] = connect_message.tid
2046                connectSrvSvc(connect_message.tid)
2047            else:
2048                errback(OperationFailure('Failed to list shares: Unable to connect to IPC$', messages_history))
2049
2050        m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), path ), SERVICE_ANY, ''))
2051        self._sendSMBMessage(m)
2052        self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = path)
2053        messages_history.append(m)
2054
2055    def _listPath_SMB1(self, service_name, path, callback, errback, search, pattern, timeout = 30):
2056        if not self.has_authenticated:
2057            raise NotReadyError('SMB connection not authenticated')
2058
2059        expiry_time = time.time() + timeout
2060        path = path.replace('/', '\\')
2061        if not path.endswith('\\'):
2062            path += '\\'
2063        messages_history = [ ]
2064        results = [ ]
2065
2066        def sendFindFirst(tid, support_dfs=False):
2067            setup_bytes = struct.pack('<H', 0x0001)  # TRANS2_FIND_FIRST2 sub-command. See [MS-CIFS]: 2.2.6.2.1
2068            params_bytes = \
2069                struct.pack('<HHHHI',
2070                            search & 0xFFFF, # SearchAttributes (need to restrict the values due to introduction of SMB_FILE_ATTRIBUTE_INCL_NORMAL)
2071                            100,    # SearchCount
2072                            0x0006, # Flags: SMB_FIND_CLOSE_AT_EOS | SMB_FIND_RETURN_RESUME_KEYS
2073                            0x0104, # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO
2074                            0x0000) # SearchStorageType (seems to be ignored by Windows)
2075            if support_dfs:
2076                params_bytes += ("\\" + self.remote_name + "\\" + service_name + path + pattern + '\0').encode('UTF-16LE')
2077            else:
2078                params_bytes += (path + pattern + '\0').encode('UTF-16LE')
2079
2080            m = SMBMessage(ComTransaction2Request(max_params_count = 10,
2081                                                  max_data_count = 16644,
2082                                                  max_setup_count = 0,
2083                                                  params_bytes = params_bytes,
2084                                                  setup_bytes = setup_bytes))
2085            m.tid = tid
2086            if support_dfs:
2087                m.flags2 |= SMB_FLAGS2_DFS
2088            self._sendSMBMessage(m)
2089            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findFirstCB, errback, support_dfs=support_dfs)
2090            messages_history.append(m)
2091
2092        def decodeFindStruct(data_bytes):
2093            # SMB_FIND_FILE_BOTH_DIRECTORY_INFO structure. See [MS-CIFS]: 2.2.8.1.7 and [MS-SMB]: 2.2.8.1.1
2094            info_format = '<IIQQQQQQIIIBB24s'
2095            info_size = struct.calcsize(info_format)
2096
2097            data_length = len(data_bytes)
2098            offset = 0
2099            while offset < data_length:
2100                if offset + info_size > data_length:
2101                    return data_bytes[offset:]
2102
2103                next_offset, _, \
2104                create_time, last_access_time, last_write_time, last_attr_change_time, \
2105                file_size, alloc_size, file_attributes, filename_length, ea_size, \
2106                short_name_length, _, short_name = struct.unpack(info_format, data_bytes[offset:offset+info_size])
2107
2108                offset2 = offset + info_size
2109                if offset2 + filename_length > data_length:
2110                    return data_bytes[offset:]
2111
2112                filename = data_bytes[offset2:offset2+filename_length].decode('UTF-16LE')
2113                short_name = short_name.decode('UTF-16LE')
2114
2115                accept_result = False
2116                if (file_attributes & 0xff) in ( 0x00, ATTR_NORMAL ): # Only the first 8-bits are compared. We ignore other bits like temp, compressed, encryption, sparse, indexed, etc
2117                    accept_result = (search == SMB_FILE_ATTRIBUTE_NORMAL) or (search & SMB_FILE_ATTRIBUTE_INCL_NORMAL)
2118                else:
2119                    accept_result = (file_attributes & search) > 0
2120                if accept_result:
2121                    results.append(SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time),
2122                                              convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time),
2123                                              file_size, alloc_size, file_attributes, short_name, filename))
2124
2125                if next_offset:
2126                    offset += next_offset
2127                else:
2128                    break
2129            return ''
2130
2131        def findFirstCB(find_message, **kwargs):
2132            messages_history.append(find_message)
2133            if not find_message.status.hasError:
2134                if not kwargs.has_key('total_count'):
2135                    # TRANS2_FIND_FIRST2 response. [MS-CIFS]: 2.2.6.2.2
2136                    sid, search_count, end_of_search, _, last_name_offset = struct.unpack('<HHHHH', find_message.payload.params_bytes[:10])
2137                    kwargs.update({ 'sid': sid, 'end_of_search': end_of_search, 'last_name_offset': last_name_offset, 'data_buf': '' })
2138                else:
2139                    sid, end_of_search, last_name_offset = kwargs['sid'], kwargs['end_of_search'], kwargs['last_name_offset']
2140
2141                send_next = True
2142                if find_message.payload.data_bytes:
2143                    d = decodeFindStruct(kwargs['data_buf'] + find_message.payload.data_bytes)
2144                    if not kwargs.has_key('data_count'):
2145                        if len(find_message.payload.data_bytes) != find_message.payload.total_data_count:
2146                            kwargs.update({ 'data_count': len(find_message.payload.data_bytes),
2147                                            'total_count': find_message.payload.total_data_count,
2148                                            'data_buf': d,
2149                                            })
2150                            send_next = False
2151                    else:
2152                        kwargs['data_count'] += len(find_message.payload.data_bytes)
2153                        kwargs['total_count'] = min(find_message.payload.total_data_count, kwargs['total_count'])
2154                        kwargs['data_buf'] = d
2155                        if kwargs['data_count'] != kwargs['total_count']:
2156                            send_next = False
2157
2158                if not send_next:
2159                    self.pending_requests[find_message.mid] = _PendingRequest(find_message.mid, expiry_time, findFirstCB, errback, **kwargs)
2160                elif end_of_search:
2161                    callback(results)
2162                else:
2163                    sendFindNext(find_message.tid, sid, 0, results[-1].filename, kwargs.get('support_dfs', False))
2164            else:
2165                if find_message.status.internal_value == 0xC000000F:  # [MS-ERREF]: STATUS_NO_SUCH_FILE
2166                    # Remote server returns STATUS_NO_SUCH_FILE error so we assume that the search returns no matching files
2167                    callback([ ])
2168                else:
2169                    errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history))
2170
2171        def sendFindNext(tid, sid, resume_key, resume_file, support_dfs=False):
2172            setup_bytes = struct.pack('<H', 0x0002)  # TRANS2_FIND_NEXT2 sub-command. See [MS-CIFS]: 2.2.6.3.1
2173            params_bytes = \
2174                struct.pack('<HHHIH',
2175                            sid,        # SID
2176                            100,        # SearchCount
2177                            0x0104,     # InfoLevel: SMB_FIND_FILE_BOTH_DIRECTORY_INFO
2178                            resume_key, # ResumeKey
2179                            0x0006)     # Flags: SMB_FIND_RETURN_RESUME_KEYS | SMB_FIND_CLOSE_AT_EOS
2180            params_bytes += (resume_file+'\0').encode('UTF-16LE')
2181
2182            m = SMBMessage(ComTransaction2Request(max_params_count = 10,
2183                                                  max_data_count = 16644,
2184                                                  max_setup_count = 0,
2185                                                  params_bytes = params_bytes,
2186                                                  setup_bytes = setup_bytes))
2187            m.tid = tid
2188            if support_dfs:
2189                m.flags2 |= SMB_FLAGS2_DFS
2190            self._sendSMBMessage(m)
2191            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, findNextCB, errback, sid = sid, support_dfs = support_dfs)
2192            messages_history.append(m)
2193
2194        def findNextCB(find_message, **kwargs):
2195            messages_history.append(find_message)
2196            if not find_message.status.hasError:
2197                if not kwargs.has_key('total_count'):
2198                    # TRANS2_FIND_NEXT2 response. [MS-CIFS]: 2.2.6.3.2
2199                    search_count, end_of_search, _, last_name_offset = struct.unpack('<HHHH', find_message.payload.params_bytes[:8])
2200                    kwargs.update({ 'end_of_search': end_of_search, 'last_name_offset': last_name_offset, 'data_buf': '' })
2201                else:
2202                    end_of_search, last_name_offset = kwargs['end_of_search'], kwargs['last_name_offset']
2203
2204                send_next = True
2205                if find_message.payload.data_bytes:
2206                    d = decodeFindStruct(kwargs['data_buf'] + find_message.payload.data_bytes)
2207                    if not kwargs.has_key('data_count'):
2208                        if len(find_message.payload.data_bytes) != find_message.payload.total_data_count:
2209                            kwargs.update({ 'data_count': len(find_message.payload.data_bytes),
2210                                            'total_count': find_message.payload.total_data_count,
2211                                            'data_buf': d,
2212                                            })
2213                            send_next = False
2214                    else:
2215                        kwargs['data_count'] += len(find_message.payload.data_bytes)
2216                        kwargs['total_count'] = min(find_message.payload.total_data_count, kwargs['total_count'])
2217                        kwargs['data_buf'] = d
2218                        if kwargs['data_count'] != kwargs['total_count']:
2219                            send_next = False
2220
2221                if not send_next:
2222                    self.pending_requests[find_message.mid] = _PendingRequest(find_message.mid, expiry_time, findNextCB, errback, **kwargs)
2223                elif end_of_search:
2224                    callback(results)
2225                else:
2226                    sendFindNext(find_message.tid, kwargs['sid'], 0, results[-1].filename, kwargs.get('support_dfs', False))
2227            else:
2228                errback(OperationFailure('Failed to list %s on %s: Unable to retrieve file list' % ( path, service_name ), messages_history))
2229
2230        def sendDFSReferral(tid):
2231            setup_bytes = struct.pack('<H', 0x0010)  # TRANS2_GET_DFS_REFERRAL sub-command. See [MS-CIFS]: 2.2.6.16.1
2232            params_bytes = struct.pack('<H', 3)      # Max referral level 3
2233            params_bytes += ("\\" + self.remote_name + "\\" + service_name).encode('UTF-16LE')
2234
2235            m = SMBMessage(ComTransaction2Request(max_params_count = 10,
2236                                                  max_data_count = 16644,
2237                                                  max_setup_count = 0,
2238                                                  params_bytes = params_bytes,
2239                                                  setup_bytes = setup_bytes))
2240            m.tid = tid
2241            self._sendSMBMessage(m)
2242            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, dfsReferralCB, errback)
2243            messages_history.append(m)
2244
2245        def dfsReferralCB(dfs_message, **kwargs):
2246            sendFindFirst(dfs_message.tid, True)
2247
2248        if not self.connected_trees.has_key(service_name):
2249            def connectCB(connect_message, **kwargs):
2250                messages_history.append(connect_message)
2251                if not connect_message.status.hasError:
2252                    self.connected_trees[service_name] = connect_message.tid
2253                    if connect_message.payload.optional_support & SMB_TREE_CONNECTX_SUPPORT_DFS:
2254                        sendDFSReferral(connect_message.tid)
2255                    else:
2256                        sendFindFirst(connect_message.tid, False)
2257                else:
2258                    errback(OperationFailure('Failed to list %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2259
2260            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2261            self._sendSMBMessage(m)
2262            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
2263            messages_history.append(m)
2264        else:
2265            sendFindFirst(self.connected_trees[service_name])
2266
2267    def _getAttributes_SMB1(self, service_name, path, callback, errback, timeout = 30):
2268        if not self.has_authenticated:
2269            raise NotReadyError('SMB connection not authenticated')
2270
2271        expiry_time = time.time() + timeout
2272        path = path.replace('/', '\\')
2273        if path.startswith('\\'):
2274            path = path[1:]
2275        if path.endswith('\\'):
2276            path = path[:-1]
2277        messages_history = [ ]
2278
2279        def sendQuery(tid):
2280            setup_bytes = struct.pack('<H', 0x0005)  # TRANS2_QUERY_PATH_INFORMATION sub-command. See [MS-CIFS]: 2.2.6.6.1
2281            params_bytes = \
2282                struct.pack('<HI',
2283                            0x0107, # SMB_QUERY_FILE_ALL_INFO ([MS-CIFS] 2.2.2.3.3)
2284                            0x0000) # Reserved
2285            params_bytes += (path + '\0').encode('UTF-16LE')
2286
2287            m = SMBMessage(ComTransaction2Request(max_params_count = 2,
2288                                                  max_data_count = 65535,
2289                                                  max_setup_count = 0,
2290                                                  params_bytes = params_bytes,
2291                                                  setup_bytes = setup_bytes))
2292            m.tid = tid
2293            self._sendSMBMessage(m)
2294            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, queryCB, errback)
2295            messages_history.append(m)
2296
2297        def queryCB(query_message, **kwargs):
2298            messages_history.append(query_message)
2299            if not query_message.status.hasError:
2300                info_format = '<QQQQIIQQ'
2301                info_size = struct.calcsize(info_format)
2302                create_time, last_access_time, last_write_time, last_attr_change_time, \
2303                file_attributes, _, alloc_size, file_size = struct.unpack(info_format, query_message.payload.data_bytes[:info_size])
2304                filename = self._extractLastPathComponent(unicode(path))
2305
2306                info = SharedFile(convertFILETIMEtoEpoch(create_time), convertFILETIMEtoEpoch(last_access_time), convertFILETIMEtoEpoch(last_write_time), convertFILETIMEtoEpoch(last_attr_change_time),
2307                                  file_size, alloc_size, file_attributes, filename, filename)
2308                callback(info)
2309            else:
2310                errback(OperationFailure('Failed to get attributes for %s on %s: Read failed' % ( path, service_name ), messages_history))
2311
2312        if not self.connected_trees.has_key(service_name):
2313            def connectCB(connect_message, **kwargs):
2314                messages_history.append(connect_message)
2315                if not connect_message.status.hasError:
2316                    self.connected_trees[service_name] = connect_message.tid
2317                    sendQuery(connect_message.tid)
2318                else:
2319                    errback(OperationFailure('Failed to get attributes for %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2320
2321            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2322            self._sendSMBMessage(m)
2323            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
2324            messages_history.append(m)
2325        else:
2326            sendQuery(self.connected_trees[service_name])
2327
2328    def _getSecurity_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30):
2329        raise NotReadyError('getSecurity is not yet implemented for SMB1')
2330
2331    def _retrieveFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30):
2332        return self._retrieveFileFromOffset(service_name, path, file_obj, callback, errback, 0L, -1L, timeout)
2333
2334    def _retrieveFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, max_length, timeout = 30):
2335        if not self.has_authenticated:
2336            raise NotReadyError('SMB connection not authenticated')
2337
2338        path = path.replace('/', '\\')
2339        messages_history = [ ]
2340
2341        def sendOpen(tid):
2342            m = SMBMessage(ComOpenAndxRequest(filename = path,
2343                                              access_mode = 0x0040,  # Sharing mode: Deny nothing to others
2344                                              open_mode = 0x0001,    # Failed if file does not exist
2345                                              search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM,
2346                                              timeout = timeout * 1000))
2347            m.tid = tid
2348            self._sendSMBMessage(m)
2349            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)
2350            messages_history.append(m)
2351
2352        def openCB(open_message, **kwargs):
2353            messages_history.append(open_message)
2354            if not open_message.status.hasError:
2355                if max_length == 0:
2356                    closeFid(open_message.tid, open_message.payload.fid)
2357                    callback(( file_obj, open_message.payload.file_attributes, 0L ))
2358                else:
2359                    sendRead(open_message.tid, open_message.payload.fid, starting_offset, open_message.payload.file_attributes, 0L, max_length)
2360            else:
2361                errback(OperationFailure('Failed to retrieve %s on %s: Unable to open file' % ( path, service_name ), messages_history))
2362
2363        def sendRead(tid, fid, offset, file_attributes, read_len, remaining_len):
2364            read_count = self.max_raw_size - 2
2365            m = SMBMessage(ComReadAndxRequest(fid = fid,
2366                                              offset = offset,
2367                                              max_return_bytes_count = read_count,
2368                                              min_return_bytes_count = min(0xFFFF, read_count)))
2369            m.tid = tid
2370            self._sendSMBMessage(m)
2371            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, readCB, errback, fid = fid, offset = offset, file_attributes = file_attributes,
2372                                                           read_len = read_len, remaining_len = remaining_len)
2373
2374        def readCB(read_message, **kwargs):
2375            # To avoid crazy memory usage when retrieving large files, we do not save every read_message in messages_history.
2376            if not read_message.status.hasError:
2377                read_len = kwargs['read_len']
2378                remaining_len = kwargs['remaining_len']
2379                data_len = read_message.payload.data_length
2380                if max_length > 0:
2381                    if data_len > remaining_len:
2382                        file_obj.write(read_message.payload.data[:remaining_len])
2383                        read_len += remaining_len
2384                        remaining_len = 0
2385                    else:
2386                        file_obj.write(read_message.payload.data)
2387                        remaining_len -= data_len
2388                        read_len += data_len
2389                else:
2390                    file_obj.write(read_message.payload.data)
2391                    read_len += data_len
2392
2393                if (max_length > 0 and remaining_len <= 0) or data_len < (self.max_raw_size - 2):
2394                    closeFid(read_message.tid, kwargs['fid'])
2395                    callback(( file_obj, kwargs['file_attributes'], read_len ))  # Note that this is a tuple of 3-elements
2396                else:
2397                    sendRead(read_message.tid, kwargs['fid'], kwargs['offset']+data_len, kwargs['file_attributes'], read_len, remaining_len)
2398            else:
2399                messages_history.append(read_message)
2400                closeFid(read_message.tid, kwargs['fid'])
2401                errback(OperationFailure('Failed to retrieve %s on %s: Read failed' % ( path, service_name ), messages_history))
2402
2403        def closeFid(tid, fid):
2404            m = SMBMessage(ComCloseRequest(fid))
2405            m.tid = tid
2406            self._sendSMBMessage(m)
2407            messages_history.append(m)
2408
2409        if not self.connected_trees.has_key(service_name):
2410            def connectCB(connect_message, **kwargs):
2411                messages_history.append(connect_message)
2412                if not connect_message.status.hasError:
2413                    self.connected_trees[service_name] = connect_message.tid
2414                    sendOpen(connect_message.tid)
2415                else:
2416                    errback(OperationFailure('Failed to retrieve %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2417
2418            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2419            self._sendSMBMessage(m)
2420            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2421            messages_history.append(m)
2422        else:
2423            sendOpen(self.connected_trees[service_name])
2424
2425    def _storeFile_SMB1(self, service_name, path, file_obj, callback, errback, timeout = 30):
2426        self._storeFileFromOffset_SMB1(service_name, path, file_obj, callback, errback, 0L, True, timeout)
2427
2428    def _storeFileFromOffset_SMB1(self, service_name, path, file_obj, callback, errback, starting_offset, truncate = False, timeout = 30):
2429        if not self.has_authenticated:
2430            raise NotReadyError('SMB connection not authenticated')
2431
2432        path = path.replace('/', '\\')
2433        messages_history = [ ]
2434
2435        def sendOpen(tid):
2436            m = SMBMessage(ComOpenAndxRequest(filename = path,
2437                                              access_mode = 0x0041,  # Sharing mode: Deny nothing to others + Open for writing
2438                                              open_mode = 0x0012 if truncate else 0x0011,    # Create file if file does not exist. Overwrite or append depending on truncate parameter.
2439                                              search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM,
2440                                              timeout = timeout * 1000))
2441            m.tid = tid
2442            self._sendSMBMessage(m)
2443            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)
2444            messages_history.append(m)
2445
2446        def openCB(open_message, **kwargs):
2447            messages_history.append(open_message)
2448            if not open_message.status.hasError:
2449                sendWrite(open_message.tid, open_message.payload.fid, starting_offset)
2450            else:
2451                errback(OperationFailure('Failed to store %s on %s: Unable to open file' % ( path, service_name ), messages_history))
2452
2453        def sendWrite(tid, fid, offset):
2454            # For message signing, the total SMB message size must be not exceed the max_buffer_size. Non-message signing does not have this limitation
2455            write_count = min((self.is_signing_active and (self.max_buffer_size-64)) or self.max_raw_size, 0xFFFF-1)  # Need to minus 1 byte from 0xFFFF because of the first NULL byte in the ComWriteAndxRequest message data
2456            data_bytes = file_obj.read(write_count)
2457            data_len = len(data_bytes)
2458            if data_len > 0:
2459                m = SMBMessage(ComWriteAndxRequest(fid = fid, offset = offset, data_bytes = data_bytes))
2460                m.tid = tid
2461                self._sendSMBMessage(m)
2462                self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, writeCB, errback, fid = fid, offset = offset+data_len)
2463            else:
2464                closeFid(tid, fid)
2465                callback(( file_obj, offset ))  # Note that this is a tuple of 2-elements
2466
2467        def writeCB(write_message, **kwargs):
2468            # To avoid crazy memory usage when saving large files, we do not save every write_message in messages_history.
2469            if not write_message.status.hasError:
2470                sendWrite(write_message.tid, kwargs['fid'], kwargs['offset'])
2471            else:
2472                messages_history.append(write_message)
2473                closeFid(write_message.tid, kwargs['fid'])
2474                errback(OperationFailure('Failed to store %s on %s: Write failed' % ( path, service_name ), messages_history))
2475
2476        def closeFid(tid, fid):
2477            m = SMBMessage(ComCloseRequest(fid))
2478            m.tid = tid
2479            self._sendSMBMessage(m)
2480            messages_history.append(m)
2481
2482        if not self.connected_trees.has_key(service_name):
2483            def connectCB(connect_message, **kwargs):
2484                messages_history.append(connect_message)
2485                if not connect_message.status.hasError:
2486                    self.connected_trees[service_name] = connect_message.tid
2487                    sendOpen(connect_message.tid)
2488                else:
2489                    errback(OperationFailure('Failed to store %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2490
2491            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2492            self._sendSMBMessage(m)
2493            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2494            messages_history.append(m)
2495        else:
2496            sendOpen(self.connected_trees[service_name])
2497
2498    def _deleteFiles_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30):
2499        if not self.has_authenticated:
2500            raise NotReadyError('SMB connection not authenticated')
2501
2502        path = path_file_pattern.replace('/', '\\')
2503        messages_history = [ ]
2504
2505        def sendDelete(tid):
2506            m = SMBMessage(ComDeleteRequest(filename_pattern = path,
2507                                            search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM))
2508            m.tid = tid
2509            self._sendSMBMessage(m)
2510            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback)
2511            messages_history.append(m)
2512
2513        def deleteCB(delete_message, **kwargs):
2514            messages_history.append(delete_message)
2515            if not delete_message.status.hasError:
2516                callback(path_file_pattern)
2517            else:
2518                errback(OperationFailure('Failed to store %s on %s: Delete failed' % ( path, service_name ), messages_history))
2519
2520        if not self.connected_trees.has_key(service_name):
2521            def connectCB(connect_message, **kwargs):
2522                messages_history.append(connect_message)
2523                if not connect_message.status.hasError:
2524                    self.connected_trees[service_name] = connect_message.tid
2525                    sendDelete(connect_message.tid)
2526                else:
2527                    errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2528
2529            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2530            self._sendSMBMessage(m)
2531            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2532            messages_history.append(m)
2533        else:
2534            sendDelete(self.connected_trees[service_name])
2535
2536    def _resetFileAttributes_SMB1(self, service_name, path_file_pattern, callback, errback, timeout = 30):
2537        raise NotReadyError('resetFileAttributes is not yet implemented for SMB1')
2538
2539    def _createDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30):
2540        if not self.has_authenticated:
2541            raise NotReadyError('SMB connection not authenticated')
2542
2543        path = path.replace('/', '\\')
2544        messages_history = [ ]
2545
2546        def sendCreate(tid):
2547            m = SMBMessage(ComCreateDirectoryRequest(path))
2548            m.tid = tid
2549            self._sendSMBMessage(m)
2550            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, createCB, errback)
2551            messages_history.append(m)
2552
2553        def createCB(create_message, **kwargs):
2554            messages_history.append(create_message)
2555            if not create_message.status.hasError:
2556                callback(path)
2557            else:
2558                errback(OperationFailure('Failed to create directory %s on %s: Create failed' % ( path, service_name ), messages_history))
2559
2560        if not self.connected_trees.has_key(service_name):
2561            def connectCB(connect_message, **kwargs):
2562                messages_history.append(connect_message)
2563                if not connect_message.status.hasError:
2564                    self.connected_trees[service_name] = connect_message.tid
2565                    sendCreate(connect_message.tid)
2566                else:
2567                    errback(OperationFailure('Failed to create directory %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2568
2569            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2570            self._sendSMBMessage(m)
2571            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2572            messages_history.append(m)
2573        else:
2574            sendCreate(self.connected_trees[service_name])
2575
2576    def _deleteDirectory_SMB1(self, service_name, path, callback, errback, timeout = 30):
2577        if not self.has_authenticated:
2578            raise NotReadyError('SMB connection not authenticated')
2579
2580        path = path.replace('/', '\\')
2581        messages_history = [ ]
2582
2583        def sendDelete(tid):
2584            m = SMBMessage(ComDeleteDirectoryRequest(path))
2585            m.tid = tid
2586            self._sendSMBMessage(m)
2587            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, deleteCB, errback)
2588            messages_history.append(m)
2589
2590        def deleteCB(delete_message, **kwargs):
2591            messages_history.append(delete_message)
2592            if not delete_message.status.hasError:
2593                callback(path)
2594            else:
2595                errback(OperationFailure('Failed to delete directory %s on %s: Delete failed' % ( path, service_name ), messages_history))
2596
2597        if not self.connected_trees.has_key(service_name):
2598            def connectCB(connect_message, **kwargs):
2599                messages_history.append(connect_message)
2600                if not connect_message.status.hasError:
2601                    self.connected_trees[service_name] = connect_message.tid
2602                    sendDelete(connect_message.tid)
2603                else:
2604                    errback(OperationFailure('Failed to delete %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2605
2606            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2607            self._sendSMBMessage(m)
2608            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2609            messages_history.append(m)
2610        else:
2611            sendDelete(self.connected_trees[service_name])
2612
2613    def _rename_SMB1(self, service_name, old_path, new_path, callback, errback, timeout = 30):
2614        if not self.has_authenticated:
2615            raise NotReadyError('SMB connection not authenticated')
2616
2617        new_path = new_path.replace('/', '\\')
2618        old_path = old_path.replace('/', '\\')
2619        messages_history = [ ]
2620
2621        def sendRename(tid):
2622            m = SMBMessage(ComRenameRequest(old_path = old_path,
2623                                            new_path = new_path,
2624                                            search_attributes = SMB_FILE_ATTRIBUTE_HIDDEN | SMB_FILE_ATTRIBUTE_SYSTEM))
2625            m.tid = tid
2626            self._sendSMBMessage(m)
2627            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, renameCB, errback)
2628            messages_history.append(m)
2629
2630        def renameCB(rename_message, **kwargs):
2631            messages_history.append(rename_message)
2632            if not rename_message.status.hasError:
2633                callback(( old_path, new_path ))  # Note that this is a tuple of 2-elements
2634            else:
2635                errback(OperationFailure('Failed to rename %s on %s: Rename failed' % ( old_path, service_name ), messages_history))
2636
2637        if not self.connected_trees.has_key(service_name):
2638            def connectCB(connect_message, **kwargs):
2639                messages_history.append(connect_message)
2640                if not connect_message.status.hasError:
2641                    self.connected_trees[service_name] = connect_message.tid
2642                    sendRename(connect_message.tid)
2643                else:
2644                    errback(OperationFailure('Failed to rename %s on %s: Unable to connect to shared device' % ( old_path, service_name ), messages_history))
2645
2646            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2647            self._sendSMBMessage(m)
2648            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, connectCB, errback, path = service_name)
2649            messages_history.append(m)
2650        else:
2651            sendRename(self.connected_trees[service_name])
2652
2653    def _listSnapshots_SMB1(self, service_name, path, callback, errback, timeout = 30):
2654        if not self.has_authenticated:
2655            raise NotReadyError('SMB connection not authenticated')
2656
2657        expiry_time = time.time() + timeout
2658        path = path.replace('/', '\\')
2659        if not path.endswith('\\'):
2660            path += '\\'
2661        messages_history = [ ]
2662        results = [ ]
2663
2664        def sendOpen(tid):
2665            m = SMBMessage(ComOpenAndxRequest(filename = path,
2666                                              access_mode = 0x0040,  # Sharing mode: Deny nothing to others
2667                                              open_mode = 0x0001,    # Failed if file does not exist
2668                                              search_attributes = 0,
2669                                              timeout = timeout * 1000))
2670            m.tid = tid
2671            self._sendSMBMessage(m)
2672            self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, openCB, errback)
2673            messages_history.append(m)
2674
2675        def openCB(open_message, **kwargs):
2676            messages_history.append(open_message)
2677            if not open_message.status.hasError:
2678                sendEnumSnapshots(open_message.tid, open_message.payload.fid)
2679            else:
2680                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to open path' % ( path, service_name ), messages_history))
2681
2682        def sendEnumSnapshots(tid, fid):
2683            # [MS-CIFS]: 2.2.7.2
2684            # [MS-SMB]: 2.2.7.2.1
2685            setup_bytes = struct.pack('<IHBB',
2686                                      0x00144064,  # [MS-SMB]: 2.2.7.2.1
2687                                      fid,         # FID
2688                                      0x01,        # IsFctl
2689                                      0)           # IsFlags
2690            m = SMBMessage(ComNTTransactRequest(function = 0x0002,  # NT_TRANSACT_IOCTL. [MS-CIFS]: 2.2.7.2.1
2691                                                max_params_count = 0,
2692                                                max_data_count = 0xFFFF,
2693                                                max_setup_count = 0,
2694                                                setup_bytes = setup_bytes))
2695            m.tid = tid
2696            self._sendSMBMessage(m)
2697            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, enumSnapshotsCB, errback, tid = tid, fid = fid)
2698            messages_history.append(m)
2699
2700        def enumSnapshotsCB(enum_message, **kwargs):
2701            messages_history.append(enum_message)
2702            if not enum_message.status.hasError:
2703                results = [ ]
2704                snapshots_count = struct.unpack('<I', enum_message.payload.data_bytes[4:8])[0]
2705                for i in range(0, snapshots_count):
2706                    s = enum_message.payload.data_bytes[12+i*50:12+48+i*50].decode('UTF-16LE')
2707                    results.append(datetime(*map(int, ( s[5:9], s[10:12], s[13:15], s[16:18], s[19:21], s[22:24] ))))
2708                closeFid(kwargs['tid'], kwargs['fid'])
2709                callback(results)
2710            else:
2711                closeFid(kwargs['tid'], kwargs['fid'])
2712                errback(OperationFailure('Failed to list snapshots %s on %s: Unable to list snapshots on path' % ( path, service_name ), messages_history))
2713
2714        def closeFid(tid, fid):
2715            m = SMBMessage(ComCloseRequest(fid))
2716            m.tid = tid
2717            self._sendSMBMessage(m)
2718            messages_history.append(m)
2719
2720        if not self.connected_trees.has_key(service_name):
2721            def connectCB(connect_message, **kwargs):
2722                messages_history.append(connect_message)
2723                if not connect_message.status.hasError:
2724                    self.connected_trees[service_name] = connect_message.tid
2725                    sendOpen(connect_message.tid)
2726                else:
2727                    errback(OperationFailure('Failed to list snapshots %s on %s: Unable to connect to shared device' % ( path, service_name ), messages_history))
2728
2729            m = SMBMessage(ComTreeConnectAndxRequest(r'\\%s\%s' % ( self.remote_name.upper(), service_name ), SERVICE_ANY, ''))
2730            self._sendSMBMessage(m)
2731            self.pending_requests[m.mid] = _PendingRequest(m.mid, expiry_time, connectCB, errback, path = service_name)
2732            messages_history.append(m)
2733        else:
2734            sendOpen(self.connected_trees[service_name])
2735
2736    def _echo_SMB1(self, data, callback, errback, timeout = 30):
2737        messages_history = [ ]
2738
2739        if not isinstance(data, type(b'')):
2740            raise TypeError('Echo data must be %s not %s' % (type(b'').__name__, type(data).__name__))
2741
2742        def echoCB(echo_message, **kwargs):
2743            messages_history.append(echo_message)
2744            if not echo_message.status.hasError:
2745                callback(echo_message.payload.data)
2746            else:
2747                errback(OperationFailure('Echo failed', messages_history))
2748
2749        m = SMBMessage(ComEchoRequest(echo_data = data))
2750        self._sendSMBMessage(m)
2751        self.pending_requests[m.mid] = _PendingRequest(m.mid, int(time.time()) + timeout, echoCB, errback)
2752        messages_history.append(m)
2753
2754    def _extractLastPathComponent(self, path):
2755        return path.replace('\\', '/').split('/')[-1]
2756
2757
2758class SharedDevice:
2759    """
2760    Contains information about a single shared device on the remote server.
2761
2762    The following attributes are available:
2763
2764    * name : An unicode string containing the name of the shared device
2765    * comments : An unicode string containing the user description of the shared device
2766    """
2767
2768    # The following constants are taken from [MS-SRVS]: 2.2.2.4
2769    # They are used to identify the type of shared resource from the results from the NetrShareEnum in Server Service RPC
2770    DISK_TREE   = 0x00
2771    PRINT_QUEUE = 0x01
2772    COMM_DEVICE = 0x02
2773    IPC         = 0x03
2774
2775    def __init__(self, type, name, comments):
2776        self._type = type
2777        self.name = name         #: An unicode string containing the name of the shared device
2778        self.comments = comments #: An unicode string containing the user description of the shared device
2779
2780    @property
2781    def type(self):
2782        """
2783        Returns one of the following integral constants.
2784         - SharedDevice.DISK_TREE
2785         - SharedDevice.PRINT_QUEUE
2786         - SharedDevice.COMM_DEVICE
2787         - SharedDevice.IPC
2788        """
2789        return self._type & 0xFFFF
2790
2791    @property
2792    def isSpecial(self):
2793        """
2794        Returns True if this shared device is a special share reserved for interprocess communication (IPC$)
2795        or remote administration of the server (ADMIN$). Can also refer to administrative shares such as
2796        C$, D$, E$, and so forth
2797        """
2798        return bool(self._type & 0x80000000)
2799
2800    @property
2801    def isTemporary(self):
2802        """
2803        Returns True if this is a temporary share that is not persisted for creation each time the file server initializes.
2804        """
2805        return bool(self._type & 0x40000000)
2806
2807    def __unicode__(self):
2808        return u'Shared device: %s (type:0x%02x comments:%s)' % (self.name, self.type, self.comments )
2809
2810
2811class SharedFile:
2812    """
2813    Contain information about a file/folder entry that is shared on the shared device.
2814
2815    As an application developer, you should not need to instantiate a *SharedFile* instance directly in your application.
2816    These *SharedFile* instances are usually returned via a call to *listPath* method in :doc:`smb.SMBProtocol.SMBProtocolFactory<smb_SMBProtocolFactory>`.
2817
2818    If you encounter *SharedFile* instance where its short_name attribute is empty but the filename attribute contains a short name which does not correspond
2819    to any files/folders on your remote shared device, it could be that the original filename on the file/folder entry on the shared device contains
2820    one of these prohibited characters: "\/[]:+|<>=;?,* (see [MS-CIFS]: 2.2.1.1.1 for more details).
2821
2822    The following attributes are available:
2823
2824    * create_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server
2825    * last_access_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server
2826    * last_write_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server
2827    * last_attr_change_time : Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server
2828    * file_size : File size in number of bytes
2829    * alloc_size : Total number of bytes allocated to store this file
2830    * file_attributes : A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py.
2831    * short_name : Unicode string containing the short name of this file (usually in 8.3 notation)
2832    * filename : Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters.
2833    """
2834
2835    def __init__(self, create_time, last_access_time, last_write_time, last_attr_change_time, file_size, alloc_size, file_attributes, short_name, filename):
2836        self.create_time = create_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of creation of this file resource on the remote server
2837        self.last_access_time = last_access_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last access of this file resource on the remote server
2838        self.last_write_time = last_write_time    #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last modification of this file resource on the remote server
2839        self.last_attr_change_time = last_attr_change_time  #: Float value in number of seconds since 1970-01-01 00:00:00 to the time of last attribute change of this file resource on the remote server
2840        self.file_size = file_size   #: File size in number of bytes
2841        self.alloc_size = alloc_size #: Total number of bytes allocated to store this file
2842        self.file_attributes = file_attributes #: A SMB_EXT_FILE_ATTR integer value. See [MS-CIFS]: 2.2.1.2.3. You can perform bit-wise tests to determine the status of the file using the ATTR_xxx constants in smb_constants.py.
2843        self.short_name = short_name #: Unicode string containing the short name of this file (usually in 8.3 notation)
2844        self.filename = filename     #: Unicode string containing the long filename of this file. Each OS has a limit to the length of this file name. On Windows, it is 256 characters.
2845
2846    @property
2847    def isDirectory(self):
2848        """A convenient property to return True if this file resource is a directory on the remote server"""
2849        return bool(self.file_attributes & ATTR_DIRECTORY)
2850
2851    @property
2852    def isReadOnly(self):
2853        """A convenient property to return True if this file resource is read-only on the remote server"""
2854        return bool(self.file_attributes & ATTR_READONLY)
2855
2856    @property
2857    def isNormal(self):
2858        """
2859        A convenient property to return True if this is a normal file.
2860
2861        Note that pysmb defines a normal file as a file entry that is not read-only, not hidden, not system, not archive and not a directory.
2862        It ignores other attributes like compression, indexed, sparse, temporary and encryption.
2863        """
2864        return (self.file_attributes==ATTR_NORMAL) or ((self.file_attributes & 0xff)==0)
2865
2866    def __unicode__(self):
2867        return u'Shared file: %s (FileSize:%d bytes, isDirectory:%s)' % ( self.filename, self.file_size, self.isDirectory )
2868
2869
2870class _PendingRequest:
2871
2872    def __init__(self, mid, expiry_time, callback, errback, **kwargs):
2873        self.mid = mid
2874        self.expiry_time = expiry_time
2875        self.callback = callback
2876        self.errback = errback
2877        self.kwargs = kwargs
2878