From 0f5dd34b24ec16786230febc0f013ae30c825d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20GASCOU=20=28Podalirius=29?= Date: Wed, 5 Jun 2024 15:50:19 +0200 Subject: [PATCH] Add command sizeof (#20) * Created get_entry() function * Created 'sizeof' command --- smbclientng/core/CommandCompleter.py | 16 +++++- smbclientng/core/InteractiveShell.py | 79 ++++++++++++++++++++++++++++ smbclientng/core/SMBSession.py | 32 +++++++++-- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/smbclientng/core/CommandCompleter.py b/smbclientng/core/CommandCompleter.py index ed4a7ec..a2e902e 100644 --- a/smbclientng/core/CommandCompleter.py +++ b/smbclientng/core/CommandCompleter.py @@ -61,6 +61,13 @@ class CommandCompleter(object): ], "subcommands": [] }, + "debug": { + "description": [ + "Command for dev debugging.", + "Syntax: 'debug'" + ], + "subcommands": [] + }, "dir": { "description": [ "List the contents of the current working directory.", @@ -208,6 +215,13 @@ class CommandCompleter(object): ], "subcommands": [] }, + "sizeof": { + "description": [ + "Recursively compute the size of a folder.", + "Syntax: 'sizeof [directory|file]'" + ], + "subcommands": [] + }, "shares": { "description": [ "Lists the SMB shares served by the remote machine.", @@ -299,7 +313,7 @@ def complete(self, text, state): if s.lower().startswith(remainder.lower()) ] - elif command in ["bat", "cat", "get", "rm"]: + elif command in ["bat", "cat", "debug", "get", "rm"]: # Choose local files and directories path = "" if '\\' in remainder.strip() or '/' in remainder.strip(): diff --git a/smbclientng/core/InteractiveShell.py b/smbclientng/core/InteractiveShell.py index fef8a26..6ca4161 100644 --- a/smbclientng/core/InteractiveShell.py +++ b/smbclientng/core/InteractiveShell.py @@ -149,6 +149,10 @@ def process_command(self, command, arguments=[]): # Change directory in the current share elif command == "cd": self.command_cd(arguments, command) + + # debug + elif command == "debug": + self.command_debug(arguments, command) # Get a file elif command == "get": @@ -222,6 +226,10 @@ def process_command(self, command, arguments=[]): elif command == "rmdir": self.command_rmdir(arguments, command) + # List shares + elif command == "sizeof": + self.command_sizeof(arguments, command) + # List shares elif command == "shares": self.command_shares(arguments, command) @@ -236,6 +244,12 @@ def process_command(self, command, arguments=[]): # Commands ================================================================ + def command_debug(self, arguments, command): + try: + pass + except Exception as e: + traceback.print_exc() + @command_arguments_required @active_smb_connection_needed @smb_share_is_set @@ -626,6 +640,71 @@ def command_rmdir(self, arguments, command): else: print("[!] Remote directory '%s' does not exist." % path) + @active_smb_connection_needed + @smb_share_is_set + def command_sizeof(self, arguments, command): + # Command arguments required : Yes + # Active SMB connection needed : Yes + # SMB share needed : Yes + + class RecursiveSizeOfPrint(object): + def __init__(self, entry, smbSession, config): + self.entry = entry + self.config = config + self.smbSession = smbSession + self.size = 0 + + def update(self, entry, fullpath, depth): + self.size += entry.get_filesize() + self.print(end='\r') + + def print(self, end='\n'): + # + if self.entry.is_directory(): + if self.config.no_colors: + path = "%s\\" % self.entry.get_longname() + else: + path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname() + # + else: + if self.config.no_colors: + path = "%s" % self.entry.get_longname() + else: + path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname() + print("%10s %s" % (b_filesize(self.size), path), end=end) + + entries = [] + if len(arguments) == 0: + entries = self.smbSession.list_contents() + entries = [entry for name, entry in entries.items() if name not in ['.', '..']] + else: + entry = self.smbSession.get_entry(path=' '.join(arguments)) + entries = [] + if entry is not None: + entries = [entry] + else: + print("[!] Path '%s' does not exist." % ' '.join(arguments)) + + total = 0 + for entry in entries: + rsop = RecursiveSizeOfPrint(entry=entry, smbSession=self.smbSession, config=self.config) + # Directory + if entry.is_directory(): + self.smbSession.find( + paths=[entry.get_longname()], + callback=rsop.update + ) + # File + else: + rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0) + # Close the print + rsop.print() + total += rsop.size + + if len(entries) > 1: + print("──────────────────────") + print(" total %s" % b_filesize(total)) + @active_smb_connection_needed def command_shares(self, arguments, command): # Command arguments required : No diff --git a/smbclientng/core/SMBSession.py b/smbclientng/core/SMBSession.py index 5004d90..f9944dd 100644 --- a/smbclientng/core/SMBSession.py +++ b/smbclientng/core/SMBSession.py @@ -191,7 +191,9 @@ def find(self, paths=[], callback=None): def recurse_action(paths=[], depth=0, callback=None): if callback is None: return [] + next_directories_to_explore = [] + for path in paths: remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path) entries = [] @@ -210,14 +212,14 @@ def recurse_action(paths=[], depth=0, callback=None): for entry in entries: if entry.is_directory(): - callback(entry, path + entry.get_longname() + ntpath.sep, depth) + callback(entry, path + ntpath.sep + entry.get_longname() + ntpath.sep, depth) else: - callback(entry, path + entry.get_longname(), depth) + callback(entry, path + ntpath.sep + entry.get_longname(), depth) # Next directories to explore for entry in entries: if entry.is_directory(): - next_directories_to_explore.append(path + entry.get_longname() + ntpath.sep) + next_directories_to_explore.append(path + ntpath.sep + entry.get_longname() + ntpath.sep) return next_directories_to_explore # @@ -352,6 +354,30 @@ def recurse_action(base_dir="", path=[]): self.close_smb_session() self.init_smb_session() + def get_entry(self, path=None): + """ + Retrieves information about a specific entry located at the provided path on the SMB share. + + This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None. + + Args: + path (str): The path of the entry to retrieve information about. + + Returns: + Entry: An object representing the entry at the specified path, or None if the entry is not found. + """ + + if self.path_exists(path=path): + matches = self.smbClient.listPath(shareName=self.smb_share, path=path) + + if len(matches) == 1: + return matches[0] + else: + return None + + else: + return None + def info(self, share=True, server=True): """ Displays information about the server and optionally the shares.