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