Skip to content

Commit 4ff7e70

Browse files
author
SkelSec
committed
Update dependencies in setup.py and enhance error messages in SMBConnection. Add new user management methods in SMBMachine and implement snapshot listing in SMBShare and SMBDirectory. Improve directory query handling in SMBServerConnection and add user management commands in SMBClient for better usability.
1 parent 9da4074 commit 4ff7e70

File tree

21 files changed

+1203
-88
lines changed

21 files changed

+1203
-88
lines changed

aiosmb/commons/connection/factory.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ def get_credential(self):
9090
"""Returns a new SPNEGOCredential object with the credential from the factory"""
9191
return SPNEGOCredential([copy.deepcopy(self.credential)]).build_context()
9292

93+
async def test_connection(self):
94+
ntlm_data = None
95+
try:
96+
connection = self.get_connection()
97+
async with connection:
98+
_, err = await connection.login()
99+
ntlm_data = connection.get_extra_info()
100+
if err is not None:
101+
raise err
102+
103+
return True, ntlm_data, None
104+
except Exception as e:
105+
return False, ntlm_data, e
106+
93107
@staticmethod
94108
def from_components(ip_or_hostname:str, username:str, secret:str, secrettype:str = 'password',
95109
domain:str = None, port:str = 445, dialect:str = 'smb', dcip:str = None, proxies = None, authproto:str = 'ntlm',

aiosmb/commons/interfaces/directory.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,42 @@ async def list_r(self, connection:SMBConnection, depth:int = 3, maxentries:int =
316316
except Exception as e:
317317
yield None, None, e
318318

319+
async def snapshots(self, connection, path = '\\'):
320+
"""
321+
Lists all snapshots of the share
322+
"""
323+
try:
324+
should_close = False
325+
if self.tree_id is None:
326+
should_close = True
327+
tree_entry, err = await connection.tree_connect(self.get_share_path())
328+
if err is not None:
329+
raise err
330+
self.tree_id = tree_entry.tree_id
331+
332+
fpath = self.fullpath
333+
print(fpath)
334+
file_id, err = await connection.create(self.tree_id, '', FileAccessMask.READ_CONTROL|FileAccessMask.FILE_READ_DATA, ShareAccess.FILE_SHARE_READ, CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT, CreateDisposition.FILE_OPEN, 0)
335+
if err is not None:
336+
raise err
337+
snapshots, err = await connection.ioctl(self.tree_id, file_id, CtlCode.FSCTL_SRV_ENUMERATE_SNAPSHOTS, flags = IOCTLREQFlags.IS_FSCTL, MaxOutputResponse=16)
338+
if err is not None:
339+
return None, err
340+
341+
# checking if we actually got all the snapshots
342+
if len(snapshots.SnapShots) < snapshots.NumberOfSnapShots:
343+
snapshots, err = await connection.ioctl(self.tree_id, file_id, CtlCode.FSCTL_SRV_ENUMERATE_SNAPSHOTS, flags = IOCTLREQFlags.IS_FSCTL, MaxOutputResponse=snapshots.SnapShotArraySize+12)
344+
if err is not None:
345+
return None, err
346+
347+
return snapshots.SnapShots, None
348+
except Exception as e:
349+
return None, e
350+
finally:
351+
if file_id is not None:
352+
await connection.close(self.tree_id, file_id)
353+
if should_close is True:
354+
await connection.tree_disconnect(self.tree_id)
319355

320356
async def list_gen(self, connection:SMBConnection):
321357
"""

aiosmb/commons/interfaces/file.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ async def open(self, connection:SMBConnection, mode:str = 'r', share_mode = None
342342
async def download(self, connection:SMBConnection ,local_path:str, share_mode = ShareAccess.FILE_SHARE_READ | ShareAccess.FILE_SHARE_WRITE):
343343
"""Downloads the file to the local path. File must not be open."""
344344
try:
345+
# if treeid is already present, we won't be closing it after download
346+
closetree = True if self.tree_id is None else False
345347
if self.is_pipe:
346348
raise Exception('Cannot download a pipe!')
347349
if self.__connection is not None:
@@ -365,10 +367,10 @@ async def download(self, connection:SMBConnection ,local_path:str, share_mode =
365367

366368
return str(local_path.absolute()), None
367369
except Exception as e:
368-
return False, e
370+
return None, e #this should return "None, e" and NOT "False, e"
369371
finally:
370372
await self.close()
371-
if self.tree_id is not None:
373+
if self.tree_id is not None and closetree is True:
372374
await connection.tree_disconnect(self.tree_id)
373375

374376
async def open_pipe(self, connection:SMBConnection, mode:str):

aiosmb/commons/interfaces/machine.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,98 @@ async def list_group_members(self, domain_name:str, group_name:str) -> AsyncGene
203203
except Exception as e:
204204
yield None, None, None, e
205205

206+
async def add_new_user(self, domain_name:str, user_name:str, password:str):
207+
try:
208+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
209+
if domain_name is None or domain_name == '' or domain_name == 'Builtin':
210+
dhandle, err = await samrpc.openBuiltinDomain()
211+
else:
212+
dhandle, err = await samrpc.open_domain_by_name(domain_name)
213+
if err is not None:
214+
raise err
215+
216+
_, err = await samrpc.create_user(dhandle, user_name, password)
217+
if err is not None:
218+
raise err
219+
return True, None
220+
221+
except Exception as e:
222+
return None, e
223+
224+
async def enable_user(self, domain_name:str, user_name:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
225+
try:
226+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
227+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
228+
return await samrpc.enable_account(uhandle)
229+
230+
except Exception as e:
231+
import traceback
232+
traceback.print_exc()
233+
return None, e
234+
235+
async def disable_user(self, domain_name:str, user_name:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
236+
try:
237+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
238+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
239+
return await samrpc.disable_account(uhandle)
240+
except Exception as e:
241+
return None, e
242+
243+
async def delete_user(self, domain_name:str, user_name:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
244+
try:
245+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
246+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
247+
return await samrpc.disable_account(uhandle)
248+
249+
except Exception as e:
250+
return None, e
251+
252+
async def change_user_password(self, domain_name:str, user_name:str, old_password:str, new_password:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
253+
try:
254+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
255+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
256+
return await samrpc.change_account_password(uhandle, old_password, new_password)
257+
258+
except Exception as e:
259+
return None, e
260+
261+
async def change_user_password_nt(self, domain_name:str, user_name:str, old_pw_hash:str, new_password:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
262+
try:
263+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
264+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
265+
return await samrpc.change_account_password_nt(uhandle, old_pw_hash, new_password)
266+
267+
except Exception as e:
268+
return None, e
269+
270+
async def change_user_password_new(self, domain_name:str, user_name:str, new_password:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
271+
try:
272+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
273+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
274+
return await samrpc.change_account_password_4(uhandle, new_password)
275+
276+
except Exception as e:
277+
return None, e
278+
279+
280+
async def change_user_password_internal(self, domain_name:str, user_name:str, new_password:str) -> Awaitable[Tuple[str, Union[Exception, None]]]:
281+
try:
282+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
283+
async with samrpc.get_user_handle(domain_name, user_name) as uhandle:
284+
return await samrpc.change_account_password_internal(uhandle, new_password)
285+
286+
except Exception as e:
287+
return None, e
288+
289+
async def list_users_allowed_to_replicate(self, domain_name:str):
290+
try:
291+
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
292+
async for name, sid, rid, err in samrpc.list_users_allowed_to_replicate(domain_name):
293+
yield name, sid, rid, err
294+
except Exception as e:
295+
yield None, None, None, err
296+
297+
206298
async def add_sid_to_group(self, domain_name:str, group_name:str, sid:str) -> Awaitable[Tuple[bool, Union[Exception, None]]]:
207299
try:
208300
async with samrrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as samrpc:
@@ -372,6 +464,13 @@ async def list_services(self) -> AsyncGenerator[Tuple[SMBService, Union[Exceptio
372464
except Exception as e:
373465
yield None, e
374466

467+
async def get_service_config(self, service_name:str) -> Awaitable[Tuple[ServiceStatus, Union[Exception, None]]]:
468+
try:
469+
async with remsvcrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as rpc:
470+
return await rpc.get_config(service_name)
471+
except Exception as e:
472+
return None, e
473+
375474
async def enable_service(self, service_name:str) -> Awaitable[Tuple[bool, Union[Exception, None]]]:
376475
try:
377476
async with remsvcrpc_from_smb(self.connection, auth_level=self.force_rpc_auth) as rpc:

aiosmb/commons/interfaces/share.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ async def connect(self, connection):
4949
except Exception as e:
5050
return None, e
5151

52+
async def snapshots(self, connection, path = '\\'):
53+
"""
54+
Lists all snapshots of the share
55+
"""
56+
try:
57+
if self.tree_id is None:
58+
_, err = await self.connect(connection)
59+
if err is not None:
60+
raise err
61+
tree_id = self.tree_id
62+
if tree_id is not None:
63+
file_id, err = await connection.create(tree_id, path, FileAccessMask.READ_CONTROL, ShareAccess.FILE_SHARE_READ, CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT, CreateDisposition.FILE_OPEN, 0)
64+
if err is not None:
65+
raise err
66+
snapshots, err = await connection.ioctl(tree_id, file_id, CtlCode.FSCTL_SRV_ENUMERATE_SNAPSHOTS)
67+
if err is not None:
68+
return None, err
69+
return snapshots, None
70+
except Exception as e:
71+
return None, e
72+
5273
async def get_security_descriptor(self, connection) -> Awaitable[Tuple[SECURITY_DESCRIPTOR, Union[Exception, None]]]:
5374
if self.security_descriptor is None:
5475
file_id = None

aiosmb/commons/utils/glob2re.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ def glob2re(pat):
3838
res = '%s[%s]' % (res, stuff)
3939
else:
4040
res = res + re.escape(c)
41-
return res + '\Z(?ms)'
41+
# Anchor to end of string; no inline flags
42+
return res + r'\Z'

aiosmb/connection.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -997,7 +997,7 @@ async def create(self, tree_id, file_path, desired_access, share_mode, create_op
997997
raise SMBConnectionTerminated()
998998

999999
if tree_id not in self.TreeConnectTable_id:
1000-
raise Exception('Unknown Tree ID!')
1000+
raise Exception('Unknown Tree ID! %s' % tree_id)
10011001

10021002
command = CREATE_REQ()
10031003
command.RequestedOplockLevel = oplock_level
@@ -1058,9 +1058,9 @@ async def read(self, tree_id, file_id, offset = 0, length = 0):
10581058
raise SMBConnectionTerminated()
10591059

10601060
if tree_id not in self.TreeConnectTable_id:
1061-
raise Exception('Unknown Tree ID!')
1061+
raise Exception('Unknown Tree ID! %s' % tree_id)
10621062
if file_id not in self.FileHandleTable:
1063-
raise Exception('Unknown File ID!')
1063+
raise Exception('Unknown File ID! %s' % file_id)
10641064

10651065
async with self.FileHandleTable[file_id].oplock:
10661066
header = SMB2Header_SYNC()
@@ -1174,9 +1174,9 @@ async def query_info(self, tree_id, file_id, info_type = QueryInfoType.FILE, inf
11741174
raise SMBConnectionTerminated()
11751175

11761176
if tree_id not in self.TreeConnectTable_id:
1177-
raise Exception('Unknown Tree ID!')
1177+
raise Exception('Unknown Tree ID! %s' % tree_id)
11781178
if file_id not in self.FileHandleTable:
1179-
raise Exception('Unknown File ID!')
1179+
raise Exception('Unknown File ID! %s' % file_id)
11801180

11811181
command = QUERY_INFO_REQ()
11821182
command.InfoType = info_type
@@ -1239,9 +1239,9 @@ async def query_directory(self, tree_id, file_id, search_pattern = '*', resume_i
12391239
raise SMBConnectionTerminated()
12401240

12411241
if tree_id not in self.TreeConnectTable_id:
1242-
raise Exception('Unknown Tree ID!')
1242+
raise Exception('Unknown Tree ID! %s' % tree_id)
12431243
if file_id not in self.FileHandleTable:
1244-
raise Exception('Unknown File ID!')
1244+
raise Exception('Unknown File ID! %s' % file_id)
12451245

12461246

12471247
command = QUERY_DIRECTORY_REQ()
@@ -1281,7 +1281,7 @@ async def query_directory(self, tree_id, file_id, search_pattern = '*', resume_i
12811281
except Exception as e:
12821282
return None, e
12831283

1284-
async def ioctl(self, tree_id, file_id, ctlcode, data = None, flags = IOCTLREQFlags.IS_IOCTL):
1284+
async def ioctl(self, tree_id, file_id, ctlcode, data = None, flags = IOCTLREQFlags.IS_IOCTL, MaxOutputResponse=65535):
12851285
try:
12861286
if self.session_closed == True or self.status == SMBConnectionStatus.CLOSED:
12871287
raise SMBConnectionTerminated()
@@ -1291,6 +1291,7 @@ async def ioctl(self, tree_id, file_id, ctlcode, data = None, flags = IOCTLREQFl
12911291
command.FileId = file_id
12921292
command.Flags = flags
12931293
command.Buffer = data
1294+
command.MaxOutputResponse = MaxOutputResponse
12941295

12951296
header = SMB2Header_SYNC()
12961297
header.Command = SMB2Command.IOCTL

aiosmb/dcerpc/v5/interfaces/remoteregistry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ async def DeleteValue(self, key, value):
258258
try:
259259
key = await self.__get_rawhandle(key)
260260

261-
_, err = await rrp.hBaseRegDeleteValue(self.dce, key, value)
261+
_, err = await rrp.hBaseRegDeleteValue(self.dce, key, value)
262262
if err is not None:
263263
raise err
264264

0 commit comments

Comments
 (0)