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