Coverage for /root/GitHubProjects/impacket/impacket/smbconnection.py : 60%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
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#
15import ntpath
16import socket
18from impacket import smb, smb3, nmb, nt_errors, LOG
19from impacket.ntlm import compute_lmhash, compute_nthash
20from impacket.smb3structs import SMB2Packet, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, GENERIC_ALL, FILE_SHARE_READ, \
21 FILE_SHARE_WRITE, FILE_SHARE_DELETE, FILE_NON_DIRECTORY_FILE, FILE_OVERWRITE_IF, FILE_ATTRIBUTE_NORMAL, \
22 SMB2_IL_IMPERSONATION, SMB2_OPLOCK_LEVEL_NONE, FILE_READ_DATA , FILE_WRITE_DATA, FILE_OPEN, GENERIC_READ, GENERIC_WRITE, \
23 FILE_OPEN_REPARSE_POINT, MOUNT_POINT_REPARSE_DATA_STRUCTURE, FSCTL_SET_REPARSE_POINT, SMB2_0_IOCTL_IS_FSCTL, \
24 MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE, FSCTL_DELETE_REPARSE_POINT, FSCTL_SRV_ENUMERATE_SNAPSHOTS, SRV_SNAPSHOT_ARRAY, \
25 FILE_SYNCHRONOUS_IO_NONALERT, FILE_READ_EA, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE, SMB2_DIALECT_311
28# So the user doesn't need to import smb, the smb3 are already in here
29SMB_DIALECT = smb.SMB_DIALECT
31class SMBConnection:
32 """
33 SMBConnection class
35 :param string remoteName: name of the remote host, can be its NETBIOS name, IP or *\*SMBSERVER*. If the later,
36 and port is 139, the library will try to get the target's server name.
37 :param string remoteHost: target server's remote address (IPv4, IPv6) or FQDN
38 :param string/optional myName: client's NETBIOS name
39 :param integer/optional sess_port: target port to connect
40 :param integer/optional timeout: timeout in seconds when receiving packets
41 :param optional preferredDialect: the dialect desired to talk with the target server. If not specified the highest
42 one available will be used
43 :param optional boolean manualNegotiate: the user manually performs SMB_COM_NEGOTIATE
45 :return: a SMBConnection instance, if not raises a SessionError exception
46 """
48 def __init__(self, remoteName='', remoteHost='', myName=None, sess_port=nmb.SMB_SESSION_PORT, timeout=60, preferredDialect=None,
49 existingConnection=None, manualNegotiate=False):
51 self._SMBConnection = 0
52 self._dialect = ''
53 self._nmbSession = 0
54 self._sess_port = sess_port
55 self._myName = myName
56 self._remoteHost = remoteHost
57 self._remoteName = remoteName
58 self._timeout = timeout
59 self._preferredDialect = preferredDialect
60 self._existingConnection = existingConnection
61 self._manualNegotiate = manualNegotiate
62 self._doKerberos = False
63 self._kdcHost = None
64 self._useCache = True
65 self._ntlmFallback = True
67 if existingConnection is not None: 67 ↛ 69line 67 didn't jump to line 69, because the condition on line 67 was never true
68 # Existing Connection must be a smb or smb3 instance
69 assert ( isinstance(existingConnection,smb.SMB) or isinstance(existingConnection, smb3.SMB3))
70 self._SMBConnection = existingConnection
71 self._preferredDialect = self._SMBConnection.getDialect()
72 self._doKerberos = self._SMBConnection.getKerberos()
73 return
75 ##preferredDialect = smb.SMB_DIALECT
77 if manualNegotiate is False:
78 self.negotiateSession(preferredDialect)
80 def negotiateSession(self, preferredDialect=None,
81 flags1=smb.SMB.FLAGS1_PATHCASELESS | smb.SMB.FLAGS1_CANONICALIZED_PATHS,
82 flags2=smb.SMB.FLAGS2_EXTENDED_SECURITY | smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES,
83 negoData='\x02NT LM 0.12\x00\x02SMB 2.002\x00\x02SMB 2.???\x00'):
84 """
85 Perform protocol negotiation
87 :param string preferredDialect: the dialect desired to talk with the target server. If None is specified the highest one available will be used
88 :param string flags1: the SMB FLAGS capabilities
89 :param string flags2: the SMB FLAGS2 capabilities
90 :param string negoData: data to be sent as part of the nego handshake
92 :return: True, raises a Session Error if error.
93 """
95 # If port 445 and the name sent is *SMBSERVER we're setting the name to the IP. This is to help some old
96 # applications still believing
97 # *SMSBSERVER will work against modern OSes. If port is NETBIOS_SESSION_PORT the user better know about i
98 # *SMBSERVER's limitations
99 if self._sess_port == nmb.SMB_SESSION_PORT and self._remoteName == '*SMBSERVER':
100 self._remoteName = self._remoteHost
101 elif self._sess_port == nmb.NETBIOS_SESSION_PORT and self._remoteName == '*SMBSERVER':
102 # If remote name is *SMBSERVER let's try to query its name.. if can't be guessed, continue and hope for the best
103 nb = nmb.NetBIOS()
104 try:
105 res = nb.getnetbiosname(self._remoteHost)
106 except:
107 pass
108 else:
109 self._remoteName = res
111 if self._sess_port == nmb.NETBIOS_SESSION_PORT:
112 negoData = '\x02NT LM 0.12\x00\x02SMB 2.002\x00'
114 hostType = nmb.TYPE_SERVER
115 if preferredDialect is None:
116 # If no preferredDialect sent, we try the highest available one.
117 packet = self.negotiateSessionWildcard(self._myName, self._remoteName, self._remoteHost, self._sess_port,
118 self._timeout, True, flags1=flags1, flags2=flags2, data=negoData)
119 if packet[0:1] == b'\xfe': 119 ↛ 126line 119 didn't jump to line 126, because the condition on line 119 was never false
120 # Answer is SMB2 packet
121 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
122 self._sess_port, self._timeout, session=self._nmbSession,
123 negSessionResponse=SMB2Packet(packet))
124 else:
125 # Answer is SMB packet, sticking to SMBv1
126 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
127 self._sess_port, self._timeout, session=self._nmbSession,
128 negPacket=packet)
129 else:
130 if preferredDialect == smb.SMB_DIALECT:
131 self._SMBConnection = smb.SMB(self._remoteName, self._remoteHost, self._myName, hostType,
132 self._sess_port, self._timeout)
133 elif preferredDialect in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30, SMB2_DIALECT_311]: 133 ↛ 137line 133 didn't jump to line 137, because the condition on line 133 was never false
134 self._SMBConnection = smb3.SMB3(self._remoteName, self._remoteHost, self._myName, hostType,
135 self._sess_port, self._timeout, preferredDialect=preferredDialect)
136 else:
137 raise Exception("Unknown dialect %s")
139 # propagate flags to the smb sub-object, except for Unicode (if server supports)
140 # does not affect smb3 objects
141 if isinstance(self._SMBConnection, smb.SMB):
142 if self._SMBConnection.get_flags()[1] & smb.SMB.FLAGS2_UNICODE: 142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true
143 flags2 |= smb.SMB.FLAGS2_UNICODE
144 self._SMBConnection.set_flags(flags1=flags1, flags2=flags2)
146 return True
148 def negotiateSessionWildcard(self, myName, remoteName, remoteHost, sess_port, timeout, extended_security=True, flags1=0,
149 flags2=0, data=None):
150 # Here we follow [MS-SMB2] negotiation handshake trying to understand what dialects
151 # (including SMB1) is supported on the other end.
153 if not myName: 153 ↛ 159line 153 didn't jump to line 159, because the condition on line 153 was never false
154 myName = socket.gethostname()
155 i = myName.find('.')
156 if i > -1: 156 ↛ 157line 156 didn't jump to line 157, because the condition on line 156 was never true
157 myName = myName[:i]
159 tries = 0
160 smbp = smb.NewSMBPacket()
161 smbp['Flags1'] = flags1
162 # FLAGS2_UNICODE is required by some stacks to continue, regardless of subsequent support
163 smbp['Flags2'] = flags2 | smb.SMB.FLAGS2_UNICODE
164 resp = None
165 while tries < 2: 165 ↛ 186line 165 didn't jump to line 186, because the condition on line 165 was never false
166 self._nmbSession = nmb.NetBIOSTCPSession(myName, remoteName, remoteHost, nmb.TYPE_SERVER, sess_port,
167 timeout)
169 negSession = smb.SMBCommand(smb.SMB.SMB_COM_NEGOTIATE)
170 if extended_security is True: 170 ↛ 172line 170 didn't jump to line 172, because the condition on line 170 was never false
171 smbp['Flags2'] |= smb.SMB.FLAGS2_EXTENDED_SECURITY
172 negSession['Data'] = data
173 smbp.addCommand(negSession)
174 self._nmbSession.send_packet(smbp.getData())
176 try:
177 resp = self._nmbSession.recv_packet(timeout)
178 break
179 except nmb.NetBIOSError:
180 # OSX Yosemite asks for more Flags. Let's give it a try and see what happens
181 smbp['Flags2'] |= smb.SMB.FLAGS2_NT_STATUS | smb.SMB.FLAGS2_LONG_NAMES | smb.SMB.FLAGS2_UNICODE
182 smbp['Data'] = []
184 tries += 1
186 if resp is None: 186 ↛ 188line 186 didn't jump to line 188, because the condition on line 186 was never true
187 # No luck, quitting
188 raise Exception('No answer!')
190 return resp.get_trailer()
193 def getNMBServer(self):
194 return self._nmbSession
196 def getSMBServer(self):
197 """
198 returns the SMB/SMB3 instance being used. Useful for calling low level methods
199 """
200 return self._SMBConnection
202 def getDialect(self):
203 return self._SMBConnection.getDialect()
205 def getServerName(self):
206 return self._SMBConnection.get_server_name()
208 def getClientName(self):
209 return self._SMBConnection.get_client_name()
211 def getRemoteHost(self):
212 return self._SMBConnection.get_remote_host()
214 def getRemoteName(self):
215 return self._SMBConnection.get_remote_name()
217 def setRemoteName(self, name):
218 return self._SMBConnection.set_remote_name(name)
220 def getServerDomain(self):
221 return self._SMBConnection.get_server_domain()
223 def getServerDNSDomainName(self):
224 return self._SMBConnection.get_server_dns_domain_name()
226 def getServerDNSHostName(self):
227 return self._SMBConnection.get_server_dns_host_name()
229 def getServerOS(self):
230 return self._SMBConnection.get_server_os()
232 def getServerOSMajor(self):
233 return self._SMBConnection.get_server_os_major()
235 def getServerOSMinor(self):
236 return self._SMBConnection.get_server_os_minor()
238 def getServerOSBuild(self):
239 return self._SMBConnection.get_server_os_build()
241 def doesSupportNTLMv2(self):
242 return self._SMBConnection.doesSupportNTLMv2()
244 def isLoginRequired(self):
245 return self._SMBConnection.is_login_required()
247 def isSigningRequired(self):
248 return self._SMBConnection.is_signing_required()
250 def getCredentials(self):
251 return self._SMBConnection.getCredentials()
253 def getIOCapabilities(self):
254 return self._SMBConnection.getIOCapabilities()
256 def login(self, user, password, domain = '', lmhash = '', nthash = '', ntlmFallback = True):
257 """
258 logins into the target system
260 :param string user: username
261 :param string password: password for the user
262 :param string domain: domain where the account is valid for
263 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
264 :param string nthash: NTHASH used to authenticate using hashes (password is not used)
265 :param bool ntlmFallback: If True it will try NTLMv1 authentication if NTLMv2 fails. Only available for SMBv1
267 :return: None, raises a Session Error if error.
268 """
269 self._ntlmFallback = ntlmFallback
270 try:
271 if self.getDialect() == smb.SMB_DIALECT:
272 return self._SMBConnection.login(user, password, domain, lmhash, nthash, ntlmFallback)
273 else:
274 return self._SMBConnection.login(user, password, domain, lmhash, nthash)
275 except (smb.SessionError, smb3.SessionError) as e:
276 raise SessionError(e.get_error_code(), e.get_error_packet())
278 def kerberosLogin(self, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None,
279 TGS=None, useCache=True):
280 """
281 logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
283 :param string user: username
284 :param string password: password for the user
285 :param string domain: domain where the account is valid for (required)
286 :param string lmhash: LMHASH used to authenticate using hashes (password is not used)
287 :param string nthash: NTHASH used to authenticate using hashes (password is not used)
288 :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication
289 :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho)
290 :param struct TGT: If there's a TGT available, send the structure here and it will be used
291 :param struct TGS: same for TGS. See smb3.py for the format
292 :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
294 :return: None, raises a Session Error if error.
295 """
296 import os
297 from impacket.krb5.ccache import CCache
298 from impacket.krb5.kerberosv5 import KerberosError
299 from impacket.krb5 import constants
301 self._kdcHost = kdcHost
302 self._useCache = useCache
304 if TGT is not None or TGS is not None: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true
305 useCache = False
307 if useCache is True: 307 ↛ 344line 307 didn't jump to line 344, because the condition on line 307 was never false
308 try:
309 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
310 except:
311 # No cache present
312 pass
313 else:
314 LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
315 # retrieve domain information from CCache file if needed
316 if domain == '':
317 domain = ccache.principal.realm['data'].decode('utf-8')
318 LOG.debug('Domain retrieved from CCache: %s' % domain)
320 principal = 'cifs/%s@%s' % (self.getRemoteName().upper(), domain.upper())
321 creds = ccache.getCredential(principal)
322 if creds is None:
323 # Let's try for the TGT and go from there
324 principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper())
325 creds = ccache.getCredential(principal)
326 if creds is not None:
327 TGT = creds.toTGT()
328 LOG.debug('Using TGT from cache')
329 else:
330 LOG.debug("No valid credentials found in cache. ")
331 else:
332 TGS = creds.toTGS(principal)
333 LOG.debug('Using TGS from cache')
335 # retrieve user information from CCache file if needed
336 if user == '' and creds is not None:
337 user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')
338 LOG.debug('Username retrieved from CCache: %s' % user)
339 elif user == '' and len(ccache.principal.components) > 0:
340 user = ccache.principal.components[0]['data'].decode('utf-8')
341 LOG.debug('Username retrieved from CCache: %s' % user)
343 while True:
344 try:
345 if self.getDialect() == smb.SMB_DIALECT:
346 return self._SMBConnection.kerberos_login(user, password, domain, lmhash, nthash, aesKey, kdcHost,
347 TGT, TGS)
348 return self._SMBConnection.kerberosLogin(user, password, domain, lmhash, nthash, aesKey, kdcHost, TGT,
349 TGS)
350 except (smb.SessionError, smb3.SessionError) as e:
351 raise SessionError(e.get_error_code(), e.get_error_packet())
352 except KerberosError as e:
353 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
354 # We might face this if the target does not support AES
355 # So, if that's the case we'll force using RC4 by converting
356 # the password to lm/nt hashes and hope for the best. If that's already
357 # done, byebye.
358 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None:
359 lmhash = compute_lmhash(password)
360 nthash = compute_nthash(password)
361 else:
362 raise e
363 else:
364 raise e
366 def isGuestSession(self):
367 try:
368 return self._SMBConnection.isGuestSession()
369 except (smb.SessionError, smb3.SessionError) as e:
370 raise SessionError(e.get_error_code(), e.get_error_packet())
372 def logoff(self):
373 try:
374 return self._SMBConnection.logoff()
375 except (smb.SessionError, smb3.SessionError) as e:
376 raise SessionError(e.get_error_code(), e.get_error_packet())
379 def connectTree(self,share):
380 if self.getDialect() == smb.SMB_DIALECT:
381 # If we already have a UNC we do nothing.
382 if ntpath.ismount(share) is False:
383 # Else we build it
384 share = ntpath.basename(share)
385 share = '\\\\' + self.getRemoteHost() + '\\' + share
386 try:
387 return self._SMBConnection.connect_tree(share)
388 except (smb.SessionError, smb3.SessionError) as e:
389 raise SessionError(e.get_error_code(), e.get_error_packet())
392 def disconnectTree(self, treeId):
393 try:
394 return self._SMBConnection.disconnect_tree(treeId)
395 except (smb.SessionError, smb3.SessionError) as e:
396 raise SessionError(e.get_error_code(), e.get_error_packet())
399 def listShares(self):
400 """
401 get a list of available shares at the connected target
403 :return: a list containing dict entries for each share, raises exception if error
404 """
405 # Get the shares through RPC
406 from impacket.dcerpc.v5 import transport, srvs
407 rpctransport = transport.SMBTransport(self.getRemoteName(), self.getRemoteHost(), filename=r'\srvsvc',
408 smb_connection=self)
409 dce = rpctransport.get_dce_rpc()
410 dce.connect()
411 dce.bind(srvs.MSRPC_UUID_SRVS)
412 resp = srvs.hNetrShareEnum(dce, 1)
413 return resp['InfoStruct']['ShareInfo']['Level1']['Buffer']
415 def listPath(self, shareName, path, password = None):
416 """
417 list the files/directories under shareName/path
419 :param string shareName: a valid name for the share where the files/directories are going to be searched
420 :param string path: a base path relative to shareName
421 :param string password: the password for the share
423 :return: a list containing smb.SharedFile items, raises a SessionError exception if error.
424 """
426 try:
427 return self._SMBConnection.list_path(shareName, path, password)
428 except (smb.SessionError, smb3.SessionError) as e:
429 raise SessionError(e.get_error_code(), e.get_error_packet())
431 def createFile(self, treeId, pathName, desiredAccess=GENERIC_ALL,
432 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
433 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OVERWRITE_IF,
434 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
435 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
436 """
437 creates a remote file
440 :param HANDLE treeId: a valid handle for the share where the file is to be created
441 :param string pathName: the path name of the file to create
442 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx
443 :param int shareMode: Specifies the sharing mode for the open.
444 :param int creationOption: Specifies the options to be applied when creating or opening the file.
445 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name
446 field already exists.
447 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section.
448 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.
449 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it.
450 :param int oplockLevel: The requested oplock level
451 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed.
453 :return: a valid file descriptor, if not raises a SessionError exception.
454 """
456 if self.getDialect() == smb.SMB_DIALECT:
457 _, flags2 = self._SMBConnection.get_flags()
459 pathName = pathName.replace('/', '\\')
460 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
462 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
463 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
464 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)
465 ntCreate['Parameters']['FileNameLength']= len(packetPathName)
466 ntCreate['Parameters']['AccessMask'] = desiredAccess
467 ntCreate['Parameters']['FileAttributes']= fileAttributes
468 ntCreate['Parameters']['ShareAccess'] = shareMode
469 ntCreate['Parameters']['Disposition'] = creationDisposition
470 ntCreate['Parameters']['CreateOptions'] = creationOption
471 ntCreate['Parameters']['Impersonation'] = impersonationLevel
472 ntCreate['Parameters']['SecurityFlags'] = securityFlags
473 ntCreate['Parameters']['CreateFlags'] = 0x16
474 ntCreate['Data']['FileName'] = packetPathName
476 if flags2 & smb.SMB.FLAGS2_UNICODE:
477 ntCreate['Data']['Pad'] = 0x0
479 if createContexts is not None: 479 ↛ 480line 479 didn't jump to line 480, because the condition on line 479 was never true
480 LOG.error("CreateContexts not supported in SMB1")
482 try:
483 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
484 except (smb.SessionError, smb3.SessionError) as e:
485 raise SessionError(e.get_error_code(), e.get_error_packet())
486 else:
487 try:
488 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
489 creationDisposition, fileAttributes, impersonationLevel,
490 securityFlags, oplockLevel, createContexts)
491 except (smb.SessionError, smb3.SessionError) as e:
492 raise SessionError(e.get_error_code(), e.get_error_packet())
494 def openFile(self, treeId, pathName, desiredAccess=FILE_READ_DATA | FILE_WRITE_DATA, shareMode=FILE_SHARE_READ,
495 creationOption=FILE_NON_DIRECTORY_FILE, creationDisposition=FILE_OPEN,
496 fileAttributes=FILE_ATTRIBUTE_NORMAL, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
497 oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
498 """
499 opens a remote file
501 :param HANDLE treeId: a valid handle for the share where the file is to be opened
502 :param string pathName: the path name to open
503 :param int desiredAccess: The level of access that is required, as specified in https://msdn.microsoft.com/en-us/library/cc246503.aspx
504 :param int shareMode: Specifies the sharing mode for the open.
505 :param int creationOption: Specifies the options to be applied when creating or opening the file.
506 :param int creationDisposition: Defines the action the server MUST take if the file that is specified in the name
507 field already exists.
508 :param int fileAttributes: This field MUST be a combination of the values specified in [MS-FSCC] section 2.6, and MUST NOT include any values other than those specified in that section.
509 :param int impersonationLevel: This field specifies the impersonation level requested by the application that is issuing the create request.
510 :param int securityFlags: This field MUST NOT be used and MUST be reserved. The client MUST set this to 0, and the server MUST ignore it.
511 :param int oplockLevel: The requested oplock level
512 :param createContexts: A variable-length attribute that is sent with an SMB2 CREATE Request or SMB2 CREATE Response that either gives extra information about how the create will be processed, or returns extra information about how the create was processed.
515 :return: a valid file descriptor, if not raises a SessionError exception.
516 """
518 if self.getDialect() == smb.SMB_DIALECT:
519 _, flags2 = self._SMBConnection.get_flags()
521 pathName = pathName.replace('/', '\\')
522 packetPathName = pathName.encode('utf-16le') if flags2 & smb.SMB.FLAGS2_UNICODE else pathName
524 ntCreate = smb.SMBCommand(smb.SMB.SMB_COM_NT_CREATE_ANDX)
525 ntCreate['Parameters'] = smb.SMBNtCreateAndX_Parameters()
526 ntCreate['Data'] = smb.SMBNtCreateAndX_Data(flags=flags2)
527 ntCreate['Parameters']['FileNameLength']= len(packetPathName)
528 ntCreate['Parameters']['AccessMask'] = desiredAccess
529 ntCreate['Parameters']['FileAttributes']= fileAttributes
530 ntCreate['Parameters']['ShareAccess'] = shareMode
531 ntCreate['Parameters']['Disposition'] = creationDisposition
532 ntCreate['Parameters']['CreateOptions'] = creationOption
533 ntCreate['Parameters']['Impersonation'] = impersonationLevel
534 ntCreate['Parameters']['SecurityFlags'] = securityFlags
535 ntCreate['Parameters']['CreateFlags'] = 0x16
536 ntCreate['Data']['FileName'] = packetPathName
538 if flags2 & smb.SMB.FLAGS2_UNICODE:
539 ntCreate['Data']['Pad'] = 0x0
541 if createContexts is not None: 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true
542 LOG.error("CreateContexts not supported in SMB1")
544 try:
545 return self._SMBConnection.nt_create_andx(treeId, pathName, cmd = ntCreate)
546 except (smb.SessionError, smb3.SessionError) as e:
547 raise SessionError(e.get_error_code(), e.get_error_packet())
548 else:
549 try:
550 return self._SMBConnection.create(treeId, pathName, desiredAccess, shareMode, creationOption,
551 creationDisposition, fileAttributes, impersonationLevel,
552 securityFlags, oplockLevel, createContexts)
553 except (smb.SessionError, smb3.SessionError) as e:
554 raise SessionError(e.get_error_code(), e.get_error_packet())
556 def writeFile(self, treeId, fileId, data, offset=0):
557 """
558 writes data to a file
560 :param HANDLE treeId: a valid handle for the share where the file is to be written
561 :param HANDLE fileId: a valid handle for the file
562 :param string data: buffer with the data to write
563 :param integer offset: offset where to start writing the data
565 :return: amount of bytes written, if not raises a SessionError exception.
566 """
567 try:
568 return self._SMBConnection.writeFile(treeId, fileId, data, offset)
569 except (smb.SessionError, smb3.SessionError) as e:
570 raise SessionError(e.get_error_code(), e.get_error_packet())
573 def readFile(self, treeId, fileId, offset = 0, bytesToRead = None, singleCall = True):
574 """
575 reads data from a file
577 :param HANDLE treeId: a valid handle for the share where the file is to be read
578 :param HANDLE fileId: a valid handle for the file to be read
579 :param integer offset: offset where to start reading the data
580 :param integer bytesToRead: amount of bytes to attempt reading. If None, it will attempt to read Dialect['MaxBufferSize'] bytes.
581 :param boolean singleCall: If True it won't attempt to read all bytesToRead. It will only make a single read call
583 :return: the data read, if not raises a SessionError exception. Length of data read is not always bytesToRead
584 """
585 finished = False
586 data = b''
587 maxReadSize = self._SMBConnection.getIOCapabilities()['MaxReadSize']
588 if bytesToRead is None:
589 bytesToRead = maxReadSize
590 remainingBytesToRead = bytesToRead
591 while not finished:
592 if remainingBytesToRead > maxReadSize:
593 toRead = maxReadSize
594 else:
595 toRead = remainingBytesToRead
596 try:
597 bytesRead = self._SMBConnection.read_andx(treeId, fileId, offset, toRead)
598 except (smb.SessionError, smb3.SessionError) as e:
599 if e.get_error_code() == nt_errors.STATUS_END_OF_FILE:
600 toRead = b''
601 break
602 else:
603 raise SessionError(e.get_error_code(), e.get_error_packet())
605 data += bytesRead
606 if len(data) >= bytesToRead:
607 finished = True
608 elif len(bytesRead) == 0: 608 ↛ 610line 608 didn't jump to line 610, because the condition on line 608 was never true
609 # End of the file achieved.
610 finished = True
611 elif singleCall is True: 611 ↛ 614line 611 didn't jump to line 614, because the condition on line 611 was never false
612 finished = True
613 else:
614 offset += len(bytesRead)
615 remainingBytesToRead -= len(bytesRead)
617 return data
619 def closeFile(self, treeId, fileId):
620 """
621 closes a file handle
623 :param HANDLE treeId: a valid handle for the share where the file is to be opened
624 :param HANDLE fileId: a valid handle for the file/directory to be closed
626 :return: None, raises a SessionError exception if error.
628 """
629 try:
630 return self._SMBConnection.close(treeId, fileId)
631 except (smb.SessionError, smb3.SessionError) as e:
632 raise SessionError(e.get_error_code(), e.get_error_packet())
634 def deleteFile(self, shareName, pathName):
635 """
636 removes a file
638 :param string shareName: a valid name for the share where the file is to be deleted
639 :param string pathName: the path name to remove
641 :return: None, raises a SessionError exception if error.
643 """
644 try:
645 return self._SMBConnection.remove(shareName, pathName)
646 except (smb.SessionError, smb3.SessionError) as e:
647 raise SessionError(e.get_error_code(), e.get_error_packet())
649 def queryInfo(self, treeId, fileId):
650 """
651 queries basic information about an opened file/directory
653 :param HANDLE treeId: a valid handle for the share where the file is to be opened
654 :param HANDLE fileId: a valid handle for the file/directory to be closed
656 :return: a smb.SMBQueryFileBasicInfo structure. raises a SessionError exception if error.
658 """
659 try:
660 if self.getDialect() == smb.SMB_DIALECT:
661 res = self._SMBConnection.query_file_info(treeId, fileId)
662 else:
663 res = self._SMBConnection.queryInfo(treeId, fileId)
664 return smb.SMBQueryFileStandardInfo(res)
665 except (smb.SessionError, smb3.SessionError) as e:
666 raise SessionError(e.get_error_code(), e.get_error_packet())
668 def createDirectory(self, shareName, pathName ):
669 """
670 creates a directory
672 :param string shareName: a valid name for the share where the directory is to be created
673 :param string pathName: the path name or the directory to create
675 :return: None, raises a SessionError exception if error.
677 """
678 try:
679 return self._SMBConnection.mkdir(shareName, pathName)
680 except (smb.SessionError, smb3.SessionError) as e:
681 raise SessionError(e.get_error_code(), e.get_error_packet())
683 def deleteDirectory(self, shareName, pathName):
684 """
685 deletes a directory
687 :param string shareName: a valid name for the share where directory is to be deleted
688 :param string pathName: the path name or the directory to delete
690 :return: None, raises a SessionError exception if error.
692 """
693 try:
694 return self._SMBConnection.rmdir(shareName, pathName)
695 except (smb.SessionError, smb3.SessionError) as e:
696 raise SessionError(e.get_error_code(), e.get_error_packet())
698 def waitNamedPipe(self, treeId, pipeName, timeout = 5):
699 """
700 waits for a named pipe
702 :param HANDLE treeId: a valid handle for the share where the pipe is
703 :param string pipeName: the pipe name to check
704 :param integer timeout: time to wait for an answer
706 :return: None, raises a SessionError exception if error.
708 """
709 try:
710 return self._SMBConnection.waitNamedPipe(treeId, pipeName, timeout = timeout)
711 except (smb.SessionError, smb3.SessionError) as e:
712 raise SessionError(e.get_error_code(), e.get_error_packet())
714 def transactNamedPipe(self, treeId, fileId, data, waitAnswer = True):
715 """
716 writes to a named pipe using a transaction command
718 :param HANDLE treeId: a valid handle for the share where the pipe is
719 :param HANDLE fileId: a valid handle for the pipe
720 :param string data: buffer with the data to write
721 :param boolean waitAnswer: whether or not to wait for an answer
723 :return: None, raises a SessionError exception if error.
725 """
726 try:
727 return self._SMBConnection.TransactNamedPipe(treeId, fileId, data, waitAnswer = waitAnswer)
728 except (smb.SessionError, smb3.SessionError) as e:
729 raise SessionError(e.get_error_code(), e.get_error_packet())
732 def transactNamedPipeRecv(self):
733 """
734 reads from a named pipe using a transaction command
736 :return: data read, raises a SessionError exception if error.
738 """
739 try:
740 return self._SMBConnection.TransactNamedPipeRecv()
741 except (smb.SessionError, smb3.SessionError) as e:
742 raise SessionError(e.get_error_code(), e.get_error_packet())
744 def writeNamedPipe(self, treeId, fileId, data, waitAnswer = True):
745 """
746 writes to a named pipe
748 :param HANDLE treeId: a valid handle for the share where the pipe is
749 :param HANDLE fileId: a valid handle for the pipe
750 :param string data: buffer with the data to write
751 :param boolean waitAnswer: whether or not to wait for an answer
753 :return: None, raises a SessionError exception if error.
755 """
756 try:
757 if self.getDialect() == smb.SMB_DIALECT:
758 return self._SMBConnection.write_andx(treeId, fileId, data, wait_answer = waitAnswer, write_pipe_mode = True)
759 else:
760 return self.writeFile(treeId, fileId, data, 0)
761 except (smb.SessionError, smb3.SessionError) as e:
762 raise SessionError(e.get_error_code(), e.get_error_packet())
765 def readNamedPipe(self,treeId, fileId, bytesToRead = None ):
766 """
767 read from a named pipe
769 :param HANDLE treeId: a valid handle for the share where the pipe resides
770 :param HANDLE fileId: a valid handle for the pipe
771 :param integer bytesToRead: amount of data to read
773 :return: None, raises a SessionError exception if error.
775 """
777 try:
778 return self.readFile(treeId, fileId, bytesToRead = bytesToRead, singleCall = True)
779 except (smb.SessionError, smb3.SessionError) as e:
780 raise SessionError(e.get_error_code(), e.get_error_packet())
783 def getFile(self, shareName, pathName, callback, shareAccessMode = None):
784 """
785 downloads a file
787 :param string shareName: name for the share where the file is to be retrieved
788 :param string pathName: the path name to retrieve
789 :param callback callback: function called to write the contents read.
790 :param int shareAccessMode:
792 :return: None, raises a SessionError exception if error.
794 """
795 try:
796 if shareAccessMode is None: 796 ↛ 800line 796 didn't jump to line 800, because the condition on line 796 was never false
797 # if share access mode is none, let's the underlying API deals with it
798 return self._SMBConnection.retr_file(shareName, pathName, callback)
799 else:
800 return self._SMBConnection.retr_file(shareName, pathName, callback, shareAccessMode=shareAccessMode)
801 except (smb.SessionError, smb3.SessionError) as e:
802 raise SessionError(e.get_error_code(), e.get_error_packet())
804 def putFile(self, shareName, pathName, callback, shareAccessMode = None):
805 """
806 uploads a file
808 :param string shareName: name for the share where the file is to be uploaded
809 :param string pathName: the path name to upload
810 :param callback callback: function called to read the contents to be written.
811 :param int shareAccessMode:
813 :return: None, raises a SessionError exception if error.
815 """
816 try:
817 if shareAccessMode is None: 817 ↛ 821line 817 didn't jump to line 821, because the condition on line 817 was never false
818 # if share access mode is none, let's the underlying API deals with it
819 return self._SMBConnection.stor_file(shareName, pathName, callback)
820 else:
821 return self._SMBConnection.stor_file(shareName, pathName, callback, shareAccessMode)
822 except (smb.SessionError, smb3.SessionError) as e:
823 raise SessionError(e.get_error_code(), e.get_error_packet())
825 def listSnapshots(self, tid, path):
826 """
827 lists the snapshots for the given directory
829 :param int tid: tree id of current connection
830 :param string path: directory to list the snapshots of
831 """
833 # Verify we're under SMB2+ session
834 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
835 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
837 fid = self.openFile(tid, path, FILE_READ_DATA | FILE_READ_EA | FILE_READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE,
838 fileAttributes=None, creationOption=FILE_SYNCHRONOUS_IO_NONALERT,
839 shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE)
841 # first send with maxOutputResponse=16 to get the required size
842 try:
843 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,
844 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=16))
845 except (smb.SessionError, smb3.SessionError) as e:
846 self.closeFile(tid, fid)
847 raise SessionError(e.get_error_code(), e.get_error_packet())
849 if snapshotData['SnapShotArraySize'] >= 52:
850 # now send an appropriate sized buffer
851 try:
852 snapshotData = SRV_SNAPSHOT_ARRAY(self._SMBConnection.ioctl(tid, fid, FSCTL_SRV_ENUMERATE_SNAPSHOTS,
853 flags=SMB2_0_IOCTL_IS_FSCTL, maxOutputResponse=snapshotData['SnapShotArraySize']+12))
854 except (smb.SessionError, smb3.SessionError) as e:
855 self.closeFile(tid, fid)
856 raise SessionError(e.get_error_code(), e.get_error_packet())
858 self.closeFile(tid, fid)
859 return list(filter(None, snapshotData['SnapShots'].decode('utf16').split('\x00')))
861 def createMountPoint(self, tid, path, target):
862 """
863 creates a mount point at an existing directory
865 :param int tid: tree id of current connection
866 :param string path: directory at which to create mount point (must already exist)
867 :param string target: target address of mount point
868 """
870 # Verify we're under SMB2+ session
871 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
872 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
874 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,
875 creationOption=FILE_OPEN_REPARSE_POINT)
877 if target.startswith("\\"):
878 fixed_name = target.encode('utf-16le')
879 else:
880 fixed_name = ("\\??\\" + target).encode('utf-16le')
882 name = target.encode('utf-16le')
884 reparseData = MOUNT_POINT_REPARSE_DATA_STRUCTURE()
886 reparseData['PathBuffer'] = fixed_name + b"\x00\x00" + name + b"\x00\x00"
887 reparseData['SubstituteNameLength'] = len(fixed_name)
888 reparseData['PrintNameOffset'] = len(fixed_name) + 2
889 reparseData['PrintNameLength'] = len(name)
891 self._SMBConnection.ioctl(tid, fid, FSCTL_SET_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,
892 inputBlob=reparseData)
894 self.closeFile(tid, fid)
896 def removeMountPoint(self, tid, path):
897 """
898 removes a mount point without deleting the underlying directory
900 :param int tid: tree id of current connection
901 :param string path: path to mount point to remove
902 """
904 # Verify we're under SMB2+ session
905 if self.getDialect() not in [SMB2_DIALECT_002, SMB2_DIALECT_21, SMB2_DIALECT_30]:
906 raise SessionError(error = nt_errors.STATUS_NOT_SUPPORTED)
908 fid = self.openFile(tid, path, GENERIC_READ | GENERIC_WRITE,
909 creationOption=FILE_OPEN_REPARSE_POINT)
911 reparseData = MOUNT_POINT_REPARSE_GUID_DATA_STRUCTURE()
913 reparseData['DataBuffer'] = b""
915 try:
916 self._SMBConnection.ioctl(tid, fid, FSCTL_DELETE_REPARSE_POINT, flags=SMB2_0_IOCTL_IS_FSCTL,
917 inputBlob=reparseData)
918 except (smb.SessionError, smb3.SessionError) as e:
919 self.closeFile(tid, fid)
920 raise SessionError(e.get_error_code(), e.get_error_packet())
922 self.closeFile(tid, fid)
924 def rename(self, shareName, oldPath, newPath):
925 """
926 renames a file/directory
928 :param string shareName: name for the share where the files/directories are
929 :param string oldPath: the old path name or the directory/file to rename
930 :param string newPath: the new path name or the directory/file to rename
932 :return: True, raises a SessionError exception if error.
934 """
936 try:
937 return self._SMBConnection.rename(shareName, oldPath, newPath)
938 except (smb.SessionError, smb3.SessionError) as e:
939 raise SessionError(e.get_error_code(), e.get_error_packet())
941 def reconnect(self):
942 """
943 reconnects the SMB object based on the original options and credentials used. Only exception is that
944 manualNegotiate will not be honored.
945 Not only the connection will be created but also a login attempt using the original credentials and
946 method (Kerberos, PtH, etc)
948 :return: True, raises a SessionError exception if error
949 """
950 userName, password, domain, lmhash, nthash, aesKey, TGT, TGS = self.getCredentials()
951 self.negotiateSession(self._preferredDialect)
952 if self._doKerberos is True: 952 ↛ 953line 952 didn't jump to line 953, because the condition on line 952 was never true
953 self.kerberosLogin(userName, password, domain, lmhash, nthash, aesKey, self._kdcHost, TGT, TGS, self._useCache)
954 else:
955 self.login(userName, password, domain, lmhash, nthash, self._ntlmFallback)
957 return True
959 def setTimeout(self, timeout):
960 try:
961 return self._SMBConnection.set_timeout(timeout)
962 except (smb.SessionError, smb3.SessionError) as e:
963 raise SessionError(e.get_error_code(), e.get_error_packet())
965 def getSessionKey(self):
966 if self.getDialect() == smb.SMB_DIALECT:
967 return self._SMBConnection.get_session_key()
968 else:
969 return self._SMBConnection.getSessionKey()
971 def setSessionKey(self, key):
972 if self.getDialect() == smb.SMB_DIALECT:
973 return self._SMBConnection.set_session_key(key)
974 else:
975 return self._SMBConnection.setSessionKey(key)
977 def setHostnameValidation(self, validate, accept_empty, hostname):
978 return self._SMBConnection.set_hostname_validation(validate, accept_empty, hostname)
980 def close(self):
981 """
982 logs off and closes the underlying _NetBIOSSession()
984 :return: None
985 """
986 try:
987 self.logoff()
988 except:
989 pass
990 self._SMBConnection.close_session()
992class SessionError(Exception):
993 """
994 This is the exception every client should catch regardless of the underlying
995 SMB version used. We'll take care of that. NETBIOS exceptions are NOT included,
996 since all SMB versions share the same NETBIOS instances.
997 """
998 def __init__( self, error = 0, packet=0):
999 Exception.__init__(self)
1000 self.error = error
1001 self.packet = packet
1003 def getErrorCode( self ):
1004 return self.error
1006 def getErrorPacket( self ):
1007 return self.packet
1009 def getErrorString( self ):
1010 return nt_errors.ERROR_MESSAGES[self.error]
1012 def __str__( self ):
1013 if self.error in nt_errors.ERROR_MESSAGES: 1013 ↛ 1016line 1013 didn't jump to line 1016, because the condition on line 1013 was never false
1014 return 'SMB SessionError: %s(%s)' % (nt_errors.ERROR_MESSAGES[self.error])
1015 else:
1016 return 'SMB SessionError: 0x%x' % self.error