smbclientng.modules.Find

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3# File name          : InteractiveShell.py
  4# Author             : Podalirius (@podalirius_)
  5# Date created       : 23 may 2024
  6
  7
  8import impacket
  9import ntpath
 10import re
 11from smbclientng.core.Module import Module
 12from smbclientng.core.ModuleArgumentParser import ModuleArgumentParser
 13from smbclientng.core.utils import windows_ls_entry
 14
 15
 16class Find(Module):
 17    """
 18    A class to search for files in a directory hierarchy.
 19
 20    This class provides functionality to search for files based on various criteria in a directory hierarchy.
 21    """
 22
 23    name = "find"
 24    description = "Search for files in a directory hierarchy"
 25
 26    def parseArgs(self, arguments):
 27        """
 28        Parses the command line arguments provided to the module.
 29
 30        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
 31
 32        Args:
 33            arguments (str): A string of command line arguments.
 34
 35        Returns:
 36            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
 37        """
 38
 39        parser = ModuleArgumentParser(prog=self.name, description=self.description)
 40
 41        # Adding positional arguments
 42        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
 43
 44        # Adding options for filtering
 45        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
 46        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
 47        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
 48        parser.add_argument("-size", type=str, help="File uses n units of space.")
 49        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
 50        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
 51        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
 52        
 53        # Adding actions
 54        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 55        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 56
 57        # Other options
 58        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
 59        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
 60
 61        if len(arguments.strip()) == 0:
 62            parser.print_help()
 63            return None
 64        else:
 65            self.options = self.processArguments(parser, arguments)
 66
 67        if self.options is not None:
 68            if len(self.options.paths) == 0:
 69                parser.print_help()
 70                self.options = None
 71
 72        return self.options
 73
 74    def __find_callback(self, entry, fullpath, depth):
 75        # Documentation for __find_callback function
 76        """
 77        This function serves as a callback for the find operation. It applies filters based on the command line arguments
 78        and decides whether to print, download, or list the entry in 'ls -dils' format if it matches the specified filters.
 79
 80        Args:
 81            entry (SMBEntry): The current file or directory entry being processed.
 82            fullpath (str): The full path to the entry.
 83
 84        The function checks against filters such as file name, case sensitivity, file type, and size. If the entry matches
 85        the filters, it will perform actions like printing the entry's details, downloading the entry, or listing the entry
 86        based on the options provided in the command line arguments.
 87        """
 88
 89        # Match and print results
 90        do_print_results = True
 91        if self.options.mindepth is not None:
 92            if depth < self.options.mindepth:
 93                do_print_results = False
 94        if self.options.maxdepth is not None:
 95            if depth > self.options.maxdepth:
 96                do_print_results = False
 97        
 98        if do_print_results:
 99            do_print_entry = False
100            # Print directory
101            if entry.is_directory():
102                if (self.options.type == 'd' or self.options.type is None):
103                    # No name filtering
104                    if self.options.name is None and self.options.iname is None:
105                        do_print_entry = True
106                    
107                    # Filtering on names case sensitive
108                    elif self.options.name is not None:
109                        if '*' in self.options.name:
110                            regex = self.options.name
111                            regex = regex.replace('.', '\\.')
112                            regex = regex.replace('*', '.*')
113                            regex = '^' + regex + '$'
114                            if re.match(regex, entry.get_longname()):
115                                do_print_entry = True
116                            else:
117                                do_print_entry = False
118                        else:
119                            do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
120                    
121                    # Filtering on names case insensitive  
122                    elif self.options.iname is not None:
123                        if '*' in self.options.iname:
124                            regex = self.options.iname
125                            regex = regex.replace('.', '\\.')
126                            regex = regex.replace('*', '.*')
127                            regex = '^' + regex + '$'
128                            if re.match(regex, entry.get_longname(), re.IGNORECASE):
129                                do_print_entry = True
130                            else:
131                                do_print_entry = False
132                        else:
133                            do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
134                            
135            # Print file
136            else:
137                if (self.options.type == 'f' or self.options.type is None):
138                    # No name filtering
139                    if self.options.name is None and self.options.iname is None:
140                        do_print_entry = True
141                    
142                    # Filtering on names case sensitive
143                    elif self.options.name is not None:
144                        if '*' in self.options.name:
145                            regex = self.options.name
146                            regex = regex.replace('.', '\\.')
147                            regex = regex.replace('*', '.*')
148                            regex = '^' + regex + '$'
149                            if re.match(regex, entry.get_longname()):
150                                do_print_entry = True
151                            else:
152                                do_print_entry = False
153                        else:
154                            do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
155                    
156                    # Filtering on names case insensitive
157                    elif self.options.iname is not None:
158                        if '*' in self.options.iname:
159                            regex = self.options.iname
160                            regex = regex.replace('.', '\\.')
161                            regex = regex.replace('*', '.*')
162                            regex = '^' + regex + '$'
163                            if re.match(regex, entry.get_longname(), re.IGNORECASE):
164                                do_print_entry = True
165                            else:
166                                do_print_entry = False
167                        else:
168                            do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
169
170            if do_print_entry:
171                # Actions on matches
172                if self.options.download:
173                    if entry.is_directory():
174                        self.smbSession.get_file_recursively(path=fullpath)
175                    else:
176                        self.smbSession.get_file(path=fullpath, keepRemotePath=True)
177                # Output formats
178                if self.options.ls:
179                    if entry.is_directory():
180                        windows_ls_entry(entry, fullpath)
181                    else:
182                        windows_ls_entry(entry, fullpath)
183                else:
184                    if entry.is_directory():
185                        print("%s" % fullpath)
186                    else:
187                        print("%s" % fullpath)
188                        
189        return None
190
191    def run(self, arguments):
192        """
193        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
194
195        Args:
196            base_dir (str): The base directory to start the search from.
197            paths (list): List of paths to search within the base directory.
198            depth (int): The current depth level in the directory hierarchy.
199
200        Returns:
201            None
202        """
203
204        self.options = self.parseArgs(arguments=arguments)
205
206        if self.options is not None:
207            # Entrypoint
208            try:
209                next_directories_to_explore = []
210                for path in list(set(self.options.paths)):
211                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
212                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
213                
214                self.smbSession.find(
215                    paths=next_directories_to_explore,
216                    callback=self.__find_callback
217                )
218
219            except (BrokenPipeError, KeyboardInterrupt) as e:
220                print("[!] Interrupted.")
221                self.smbSession.close_smb_session()
222                self.smbSession.init_smb_session()
class Find(smbclientng.core.Module.Module):
 17class Find(Module):
 18    """
 19    A class to search for files in a directory hierarchy.
 20
 21    This class provides functionality to search for files based on various criteria in a directory hierarchy.
 22    """
 23
 24    name = "find"
 25    description = "Search for files in a directory hierarchy"
 26
 27    def parseArgs(self, arguments):
 28        """
 29        Parses the command line arguments provided to the module.
 30
 31        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
 32
 33        Args:
 34            arguments (str): A string of command line arguments.
 35
 36        Returns:
 37            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
 38        """
 39
 40        parser = ModuleArgumentParser(prog=self.name, description=self.description)
 41
 42        # Adding positional arguments
 43        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
 44
 45        # Adding options for filtering
 46        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
 47        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
 48        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
 49        parser.add_argument("-size", type=str, help="File uses n units of space.")
 50        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
 51        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
 52        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
 53        
 54        # Adding actions
 55        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 56        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
 57
 58        # Other options
 59        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
 60        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
 61
 62        if len(arguments.strip()) == 0:
 63            parser.print_help()
 64            return None
 65        else:
 66            self.options = self.processArguments(parser, arguments)
 67
 68        if self.options is not None:
 69            if len(self.options.paths) == 0:
 70                parser.print_help()
 71                self.options = None
 72
 73        return self.options
 74
 75    def __find_callback(self, entry, fullpath, depth):
 76        # Documentation for __find_callback function
 77        """
 78        This function serves as a callback for the find operation. It applies filters based on the command line arguments
 79        and decides whether to print, download, or list the entry in 'ls -dils' format if it matches the specified filters.
 80
 81        Args:
 82            entry (SMBEntry): The current file or directory entry being processed.
 83            fullpath (str): The full path to the entry.
 84
 85        The function checks against filters such as file name, case sensitivity, file type, and size. If the entry matches
 86        the filters, it will perform actions like printing the entry's details, downloading the entry, or listing the entry
 87        based on the options provided in the command line arguments.
 88        """
 89
 90        # Match and print results
 91        do_print_results = True
 92        if self.options.mindepth is not None:
 93            if depth < self.options.mindepth:
 94                do_print_results = False
 95        if self.options.maxdepth is not None:
 96            if depth > self.options.maxdepth:
 97                do_print_results = False
 98        
 99        if do_print_results:
100            do_print_entry = False
101            # Print directory
102            if entry.is_directory():
103                if (self.options.type == 'd' or self.options.type is None):
104                    # No name filtering
105                    if self.options.name is None and self.options.iname is None:
106                        do_print_entry = True
107                    
108                    # Filtering on names case sensitive
109                    elif self.options.name is not None:
110                        if '*' in self.options.name:
111                            regex = self.options.name
112                            regex = regex.replace('.', '\\.')
113                            regex = regex.replace('*', '.*')
114                            regex = '^' + regex + '$'
115                            if re.match(regex, entry.get_longname()):
116                                do_print_entry = True
117                            else:
118                                do_print_entry = False
119                        else:
120                            do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
121                    
122                    # Filtering on names case insensitive  
123                    elif self.options.iname is not None:
124                        if '*' in self.options.iname:
125                            regex = self.options.iname
126                            regex = regex.replace('.', '\\.')
127                            regex = regex.replace('*', '.*')
128                            regex = '^' + regex + '$'
129                            if re.match(regex, entry.get_longname(), re.IGNORECASE):
130                                do_print_entry = True
131                            else:
132                                do_print_entry = False
133                        else:
134                            do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
135                            
136            # Print file
137            else:
138                if (self.options.type == 'f' or self.options.type is None):
139                    # No name filtering
140                    if self.options.name is None and self.options.iname is None:
141                        do_print_entry = True
142                    
143                    # Filtering on names case sensitive
144                    elif self.options.name is not None:
145                        if '*' in self.options.name:
146                            regex = self.options.name
147                            regex = regex.replace('.', '\\.')
148                            regex = regex.replace('*', '.*')
149                            regex = '^' + regex + '$'
150                            if re.match(regex, entry.get_longname()):
151                                do_print_entry = True
152                            else:
153                                do_print_entry = False
154                        else:
155                            do_print_entry = (entry.get_longname().lower() == self.options.name.lower())
156                    
157                    # Filtering on names case insensitive
158                    elif self.options.iname is not None:
159                        if '*' in self.options.iname:
160                            regex = self.options.iname
161                            regex = regex.replace('.', '\\.')
162                            regex = regex.replace('*', '.*')
163                            regex = '^' + regex + '$'
164                            if re.match(regex, entry.get_longname(), re.IGNORECASE):
165                                do_print_entry = True
166                            else:
167                                do_print_entry = False
168                        else:
169                            do_print_entry = (entry.get_longname().lower() == self.options.iname.lower())
170
171            if do_print_entry:
172                # Actions on matches
173                if self.options.download:
174                    if entry.is_directory():
175                        self.smbSession.get_file_recursively(path=fullpath)
176                    else:
177                        self.smbSession.get_file(path=fullpath, keepRemotePath=True)
178                # Output formats
179                if self.options.ls:
180                    if entry.is_directory():
181                        windows_ls_entry(entry, fullpath)
182                    else:
183                        windows_ls_entry(entry, fullpath)
184                else:
185                    if entry.is_directory():
186                        print("%s" % fullpath)
187                    else:
188                        print("%s" % fullpath)
189                        
190        return None
191
192    def run(self, arguments):
193        """
194        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
195
196        Args:
197            base_dir (str): The base directory to start the search from.
198            paths (list): List of paths to search within the base directory.
199            depth (int): The current depth level in the directory hierarchy.
200
201        Returns:
202            None
203        """
204
205        self.options = self.parseArgs(arguments=arguments)
206
207        if self.options is not None:
208            # Entrypoint
209            try:
210                next_directories_to_explore = []
211                for path in list(set(self.options.paths)):
212                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
213                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
214                
215                self.smbSession.find(
216                    paths=next_directories_to_explore,
217                    callback=self.__find_callback
218                )
219
220            except (BrokenPipeError, KeyboardInterrupt) as e:
221                print("[!] Interrupted.")
222                self.smbSession.close_smb_session()
223                self.smbSession.init_smb_session()

A class to search for files in a directory hierarchy.

This class provides functionality to search for files based on various criteria in a directory hierarchy.

name = 'find'
description = 'Search for files in a directory hierarchy'
def parseArgs(self, arguments):
27    def parseArgs(self, arguments):
28        """
29        Parses the command line arguments provided to the module.
30
31        This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.
32
33        Args:
34            arguments (str): A string of command line arguments.
35
36        Returns:
37            ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.
38        """
39
40        parser = ModuleArgumentParser(prog=self.name, description=self.description)
41
42        # Adding positional arguments
43        parser.add_argument("paths", metavar="PATH", type=str, nargs="*", default=[], help="The starting point(s) for the search.")
44
45        # Adding options for filtering
46        parser.add_argument("-name", type=str, help="Base of file name (the path with the leading directories removed).")
47        parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
48        parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
49        parser.add_argument("-size", type=str, help="File uses n units of space.")
50        # parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
51        # parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
52        # parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
53        
54        # Adding actions
55        parser.add_argument("-ls", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
56        parser.add_argument("-download", action="store_true", default=False, help="List current file in ls -dils format on standard output.")
57
58        # Other options
59        parser.add_argument("-maxdepth", type=int, help="Descend at most levels (a non-negative integer) levels of directories below the command line arguments.")
60        parser.add_argument("-mindepth", type=int, help="Do not apply any tests or actions at levels less than levels (a non-negative integer).")
61
62        if len(arguments.strip()) == 0:
63            parser.print_help()
64            return None
65        else:
66            self.options = self.processArguments(parser, arguments)
67
68        if self.options is not None:
69            if len(self.options.paths) == 0:
70                parser.print_help()
71                self.options = None
72
73        return self.options

Parses the command line arguments provided to the module.

This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions.

Args: arguments (str): A string of command line arguments.

Returns: ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested.

def run(self, arguments):
192    def run(self, arguments):
193        """
194        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
195
196        Args:
197            base_dir (str): The base directory to start the search from.
198            paths (list): List of paths to search within the base directory.
199            depth (int): The current depth level in the directory hierarchy.
200
201        Returns:
202            None
203        """
204
205        self.options = self.parseArgs(arguments=arguments)
206
207        if self.options is not None:
208            # Entrypoint
209            try:
210                next_directories_to_explore = []
211                for path in list(set(self.options.paths)):
212                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
213                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
214                
215                self.smbSession.find(
216                    paths=next_directories_to_explore,
217                    callback=self.__find_callback
218                )
219
220            except (BrokenPipeError, KeyboardInterrupt) as e:
221                print("[!] Interrupted.")
222                self.smbSession.close_smb_session()
223                self.smbSession.init_smb_session()

This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.

Args: base_dir (str): The base directory to start the search from. paths (list): List of paths to search within the base directory. depth (int): The current depth level in the directory hierarchy.

Returns: None