smbclientng.core.CommandCompleter

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3# File name          : CommandCompleter.py
  4# Author             : Podalirius (@podalirius_)
  5# Date created       : 20 may 2024
  6
  7
  8import ntpath
  9import os
 10
 11
 12class CommandCompleter(object):
 13    """
 14    A class to handle command completion for the smbclient-ng shell.
 15
 16    This class provides a command completion feature that suggests possible command names based on the current input.
 17    It uses a dictionary to store commands and their descriptions, which helps in providing hints during the command line
 18    interaction in the smbclient-ng shell.
 19
 20    Attributes:
 21        smbSession (SMBSession): An instance of SMBSession which maintains the current SMB session.
 22        commands (dict): A dictionary containing command names as keys and their descriptions and subcommands as values.
 23
 24    Methods:
 25        __init__(self, smbSession): Initializes the CommandCompleter with an SMBSession.
 26    """
 27
 28    commands = {
 29        "bat": {
 30            "description": [
 31                "Pretty prints the contents of a file.", 
 32                "Syntax: 'bat <file>'"
 33            ], 
 34            "subcommands": []
 35        },
 36        "cat": {
 37            "description": [
 38                "Get the contents of a file.", 
 39                "Syntax: 'cat <file>'"
 40            ], 
 41            "subcommands": []
 42        },
 43        "cd": {
 44            "description": [
 45                "Change the current working directory.", 
 46                "Syntax: 'cd <directory>'"
 47            ], 
 48            "subcommands": []
 49        },
 50        "close": {
 51            "description": [
 52                "Closes the SMB connection to the remote machine.", 
 53                "Syntax: 'close'"
 54            ], 
 55            "subcommands": []
 56        },
 57        "connect": {
 58            "description": [
 59                "Connect to the remote machine (useful if connection timed out).", 
 60                "Syntax: 'connect'"
 61            ], 
 62            "subcommands": []
 63        },
 64        "dir": {
 65            "description": [
 66                "List the contents of the current working directory.",
 67                "Syntax: 'dir'"
 68            ], 
 69            "subcommands": []
 70        },
 71        "exit": {
 72            "description": [
 73                "Exits the smbclient-ng script.",
 74                "Syntax: 'exit'"
 75            ], 
 76            "subcommands": []
 77        },
 78        "get": {
 79            "description": [
 80                "Get a remote file.",
 81                "Syntax: 'get [-r] <directory or file>'"
 82            ], 
 83            "subcommands": []
 84        },
 85        "help": {
 86            "description": [
 87                "Displays this help message.",
 88                "Syntax: 'help'"
 89            ], 
 90            "subcommands": ["format"]
 91        },
 92        "info": {
 93            "description": [
 94                "Get information about the server and or the share.",
 95                "Syntax: 'info [server|share]'"
 96            ], 
 97            "subcommands": ["server", "share"]
 98        },
 99        "lcd": {
100            "description": [
101                "Changes the current local directory.",
102                "Syntax: 'lcd <directory>'"
103            ], 
104            "subcommands": []
105        },
106        "lls": {
107            "description": [
108                "Lists the contents of the current local directory.", 
109                "Syntax: 'lls'"
110            ],
111            "subcommands": []
112        },
113        "lmkdir": {
114            "description": [
115                "Creates a new local directory.", 
116                "Syntax: 'lmkdir <directory>'"
117            ],
118            "subcommands": []
119        },
120        "lpwd": {
121            "description": [
122                "Shows the current local directory.", 
123                "Syntax: 'lpwd'"
124            ],
125            "subcommands": []
126        },
127        "lrm": {
128            "description": [
129                "Removes a local file.", 
130                "Syntax: 'lrm <file>'"
131            ], 
132            "subcommands": []
133        },
134        "lrmdir": {
135            "description": [
136                "Removes a local directory.", 
137                "Syntax: 'lrmdir <directory>'"
138            ], 
139            "subcommands": []
140        },
141        "ls": {
142            "description": [
143                "List the contents of the current remote working directory.", 
144                "Syntax: 'ls'"
145            ], 
146            "subcommands": []
147        },
148        "ltree": {
149            "description": [
150                "Displays a tree view of the local directories.",
151                "Syntax: 'ltree [directory]'"
152            ], 
153            "subcommands": []
154        },
155        "mkdir": {
156            "description": [
157                "Creates a new remote directory.", 
158                "Syntax: 'mkdir <directory>'"
159            ], 
160            "subcommands": []
161        },
162        "module": {
163            "description": [
164                "Loads a specific module for additional functionalities.",
165                "Syntax: 'module <name>'"
166            ], 
167            "subcommands": []
168        },
169        "put": {
170            "description": [
171                "Put a local file or directory in a remote directory.", 
172                "Syntax: 'put [-r] <directory or file>'"
173            ], 
174            "subcommands": []
175        },
176        "reconnect": {
177            "description": [
178                "Reconnect to the remote machine (useful if connection timed out).", 
179                "Syntax: 'reconnect'"
180            ], 
181            "subcommands": []
182        },
183        "reset": {
184            "description": [
185                "Reset the TTY output, useful if it was broken after printing a binary file on stdout.",
186                "Syntax: 'reset'"
187            ], 
188            "subcommands": []
189        },
190        "rmdir": {
191            "description": [
192                "Removes a remote directory.", 
193                "Syntax: 'rmdir <directory>'"
194            ], 
195            "subcommands": []
196        },
197        "rm": {
198            "description": [
199                "Removes a remote file.", 
200                "Syntax: 'rm <file>'"
201            ], 
202            "subcommands": []
203        },
204        "shares": {
205            "description": [
206                "Lists the SMB shares served by the remote machine.", 
207                "Syntax: 'shares'"
208            ], 
209            "subcommands": []
210        },
211        "use": {
212            "description": [
213                "Use a SMB share.", 
214                "Syntax: 'use <sharename>'"
215            ], 
216            "subcommands": []
217        },
218        "tree": {
219            "description": [
220                "Displays a tree view of the remote directories.",
221                "Syntax: 'tree [directory]'"
222            ], 
223            "subcommands": []
224        },
225    }
226
227    def __init__(self, smbSession, config):
228        # Objects
229        self.smbSession = smbSession
230        self.config = config
231        # Pre computing for some commands 
232        self.commands["help"]["subcommands"] = ["format"] + list(self.commands.keys())
233        self.commands["help"]["subcommands"].remove("help")
234
235    def complete(self, text, state):
236        """
237        Function to handle command completion in the LDAP console.
238
239        This function completes the user"s input based on the available options for commands in the LDAP console.
240
241        Args:
242            text (str): The current text input by the user.
243            state (int): The current state of completion.
244
245        Returns:
246            str: The next completion suggestion based on the user"s input state.
247        """
248
249        if state == 0:
250            
251            # No text typed yet, need the list of commands available
252            if len(text) == 0:
253                self.matches = [s for s in self.commands.keys()]
254
255            elif len(text) != 0:
256                # This is for the main command
257                if text.count(" ") == 0:
258                    self.matches = [s for s in self.commands.keys() if s and s.startswith(text)]
259                
260                # This is for subcommands
261                elif text.count(" ") >= 1:
262                    command, remainder = text.split(" ", 1)
263                    if command in self.commands.keys():
264                        if command == "use":
265                            # Choose SMB Share to connect to
266                            self.matches = [
267                                command + " " + s.lower()
268                                for s in self.smbSession.list_shares().keys()
269                                if s.lower().startswith(remainder.lower())
270                            ]
271
272                        elif command in ["cd", "dir", "ls", "mkdir", "rmdir", "tree"]:
273                            # Choose remote directory
274                            path = ""
275                            if '\\' in remainder.strip() or '/' in remainder.strip():
276                                path = remainder.strip().replace('/', ntpath.sep)
277                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
278
279                            directory_contents = self.smbSession.list_contents(path=path).items()
280
281                            matching_entries = []
282                            for _, entry in directory_contents:
283                                if entry.is_directory() and entry.get_longname() not in [".",".."]:
284                                    if len(path) != 0:
285                                        matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
286                                    else:
287                                        matching_entries.append(entry.get_longname() + ntpath.sep)
288
289                            self.matches = [
290                                command + " " + s 
291                                for s in matching_entries
292                                if s.lower().startswith(remainder.lower())
293                            ]
294
295                        elif command in ["bat", "cat", "get", "rm"]:
296                            # Choose local files and directories
297                            path = ""
298                            if '\\' in remainder.strip() or '/' in remainder.strip():
299                                path = remainder.strip().replace('/', ntpath.sep)
300                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
301
302                            directory_contents = self.smbSession.list_contents(path=path).items()
303
304                            matching_entries = []
305                            for _, entry in directory_contents:
306                                if entry.get_longname() not in [".",".."]:
307                                    if len(path) != 0:
308                                        if entry.is_directory():
309                                            matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
310                                        else:
311                                            matching_entries.append(path + ntpath.sep + entry.get_longname())
312                                    else:
313                                        if entry.is_directory():
314                                            matching_entries.append(entry.get_longname() + ntpath.sep)
315                                        else:
316                                            matching_entries.append(entry.get_longname())
317
318                            self.matches = [
319                                command + " " + s 
320                                for s in matching_entries
321                                if s.lower().startswith(remainder.lower())
322                            ]
323
324                        elif command in ["lcd", "lls", "put", "lmkdir", "lrm", "lrmdir"]:
325                            # Choose directory
326                            path = ""
327                            if os.path.sep in remainder.strip():
328                                path = path.split(os.path.sep)[:-1]
329                                path = os.path.sep.join(path)
330                            
331                            # Current dir
332                            if len(path.strip()) == 0:
333                                path = "."
334
335                            directory_contents = os.listdir(path=path + os.path.sep)
336                            matching_entries = []
337                            for entry in directory_contents:
338                                if entry not in [".",".."]:
339                                    entry_path = path + os.path.sep + entry
340                                    if os.path.isdir(entry_path):
341                                        matching_entries.append(entry_path + os.path.sep)
342                                    else:
343                                        matching_entries.append(entry_path)
344
345                            self.matches = [
346                                command + " " + s
347                                for s in matching_entries
348                                if s.startswith(remainder)
349                            ]
350                            
351                        else:
352                            # Generic case for subcommands
353                            self.matches = [
354                                command + " " + s
355                                for s in self.commands[command]["subcommands"]
356                                if s.startswith(remainder)
357                            ]
358                    else:
359                        # Unknown subcommand, skipping autocomplete
360                        pass
361                else:
362                    self.matches = []
363            else:
364                self.matches = self.commands.keys()[:]
365
366        try:
367            return self.matches[state] + " "
368        except IndexError:
369            return None
370
371    def print_help(self, command=None):
372        """
373        Prints help information for a specific command or all commands if no command is specified.
374
375        This method displays the help information for the command passed as an argument. If no command is specified,
376        it prints the help information for all available commands. The help information includes the command syntax,
377        description, and any subcommands associated with it. This method is designed to provide users with the necessary
378        guidance on how to use the commands in the smbclient-ng shell.
379
380        Args:
381            command (str, optional): The command to display help information for. If None, help for all commands is displayed.
382
383        Returns:
384            None
385        """
386
387        if command is not None:
388            if command not in list(self.commands.keys())+["format"]:
389                command = None
390        
391        # Print help for a specific command
392        if command is not None:
393            if command == "format":
394                self.print_help_format()
395            else:
396                print("│")
397                if self.config.no_colors:
398                    command_str = command + "─"* (15 - len(command))
399                    if len(self.commands[command]["description"]) == 0:
400                        print("│ ■ %s┤  " % command_str)
401                    elif len(self.commands[command]["description"]) == 1:
402                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
403                    else:
404                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
405                        for line in self.commands[command]["description"][1:]:
406                            print("│ %s│ %s " % (" "*(15+2), line))
407                else:
408                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
409                    if len(self.commands[command]["description"]) == 0:
410                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
411                    elif len(self.commands[command]["description"]) == 1:
412                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
413                    else:
414                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
415                        for line in self.commands[command]["description"][1:]:
416                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
417                print("│")
418        # Generic help
419        else:
420            print("│")
421            commands = sorted(self.commands.keys())
422            for command in commands:
423                if self.config.no_colors:
424                    command_str = command + "─"* (15 - len(command))
425                    if len(self.commands[command]["description"]) == 0:
426                        print("│ ■ %s┤  " % command_str)
427                    elif len(self.commands[command]["description"]) == 1:
428                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
429                    else:
430                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
431                        for line in self.commands[command]["description"][1:]:
432                            print("│ %s│ %s " % (" "*(15+2), line))
433                else:
434                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
435                    if len(self.commands[command]["description"]) == 0:
436                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
437                    elif len(self.commands[command]["description"]) == 1:
438                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
439                    else:
440                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
441                        for line in self.commands[command]["description"][1:]:
442                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
443                print("│")
444
445    def print_help_format(self):
446        """
447        Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.
448
449        This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning
450        of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.
451        """
452
453        print("File attributes format:\n")
454        print("\x1b[1mdachnrst\x1b[0m")
455        print("\x1b[90m│││││││└──>\x1b[0m Temporary")
456        print("\x1b[90m││││││└───>\x1b[0m System")
457        print("\x1b[90m│││││└────>\x1b[0m Read-Only")
458        print("\x1b[90m││││└─────>\x1b[0m Normal")
459        print("\x1b[90m│││└──────>\x1b[0m Hidden")
460        print("\x1b[90m││└───────>\x1b[0m Compressed")
461        print("\x1b[90m│└────────>\x1b[0m Archived")
462        print("\x1b[90m└─────────>\x1b[0m Directory")
class CommandCompleter:
 13class CommandCompleter(object):
 14    """
 15    A class to handle command completion for the smbclient-ng shell.
 16
 17    This class provides a command completion feature that suggests possible command names based on the current input.
 18    It uses a dictionary to store commands and their descriptions, which helps in providing hints during the command line
 19    interaction in the smbclient-ng shell.
 20
 21    Attributes:
 22        smbSession (SMBSession): An instance of SMBSession which maintains the current SMB session.
 23        commands (dict): A dictionary containing command names as keys and their descriptions and subcommands as values.
 24
 25    Methods:
 26        __init__(self, smbSession): Initializes the CommandCompleter with an SMBSession.
 27    """
 28
 29    commands = {
 30        "bat": {
 31            "description": [
 32                "Pretty prints the contents of a file.", 
 33                "Syntax: 'bat <file>'"
 34            ], 
 35            "subcommands": []
 36        },
 37        "cat": {
 38            "description": [
 39                "Get the contents of a file.", 
 40                "Syntax: 'cat <file>'"
 41            ], 
 42            "subcommands": []
 43        },
 44        "cd": {
 45            "description": [
 46                "Change the current working directory.", 
 47                "Syntax: 'cd <directory>'"
 48            ], 
 49            "subcommands": []
 50        },
 51        "close": {
 52            "description": [
 53                "Closes the SMB connection to the remote machine.", 
 54                "Syntax: 'close'"
 55            ], 
 56            "subcommands": []
 57        },
 58        "connect": {
 59            "description": [
 60                "Connect to the remote machine (useful if connection timed out).", 
 61                "Syntax: 'connect'"
 62            ], 
 63            "subcommands": []
 64        },
 65        "dir": {
 66            "description": [
 67                "List the contents of the current working directory.",
 68                "Syntax: 'dir'"
 69            ], 
 70            "subcommands": []
 71        },
 72        "exit": {
 73            "description": [
 74                "Exits the smbclient-ng script.",
 75                "Syntax: 'exit'"
 76            ], 
 77            "subcommands": []
 78        },
 79        "get": {
 80            "description": [
 81                "Get a remote file.",
 82                "Syntax: 'get [-r] <directory or file>'"
 83            ], 
 84            "subcommands": []
 85        },
 86        "help": {
 87            "description": [
 88                "Displays this help message.",
 89                "Syntax: 'help'"
 90            ], 
 91            "subcommands": ["format"]
 92        },
 93        "info": {
 94            "description": [
 95                "Get information about the server and or the share.",
 96                "Syntax: 'info [server|share]'"
 97            ], 
 98            "subcommands": ["server", "share"]
 99        },
100        "lcd": {
101            "description": [
102                "Changes the current local directory.",
103                "Syntax: 'lcd <directory>'"
104            ], 
105            "subcommands": []
106        },
107        "lls": {
108            "description": [
109                "Lists the contents of the current local directory.", 
110                "Syntax: 'lls'"
111            ],
112            "subcommands": []
113        },
114        "lmkdir": {
115            "description": [
116                "Creates a new local directory.", 
117                "Syntax: 'lmkdir <directory>'"
118            ],
119            "subcommands": []
120        },
121        "lpwd": {
122            "description": [
123                "Shows the current local directory.", 
124                "Syntax: 'lpwd'"
125            ],
126            "subcommands": []
127        },
128        "lrm": {
129            "description": [
130                "Removes a local file.", 
131                "Syntax: 'lrm <file>'"
132            ], 
133            "subcommands": []
134        },
135        "lrmdir": {
136            "description": [
137                "Removes a local directory.", 
138                "Syntax: 'lrmdir <directory>'"
139            ], 
140            "subcommands": []
141        },
142        "ls": {
143            "description": [
144                "List the contents of the current remote working directory.", 
145                "Syntax: 'ls'"
146            ], 
147            "subcommands": []
148        },
149        "ltree": {
150            "description": [
151                "Displays a tree view of the local directories.",
152                "Syntax: 'ltree [directory]'"
153            ], 
154            "subcommands": []
155        },
156        "mkdir": {
157            "description": [
158                "Creates a new remote directory.", 
159                "Syntax: 'mkdir <directory>'"
160            ], 
161            "subcommands": []
162        },
163        "module": {
164            "description": [
165                "Loads a specific module for additional functionalities.",
166                "Syntax: 'module <name>'"
167            ], 
168            "subcommands": []
169        },
170        "put": {
171            "description": [
172                "Put a local file or directory in a remote directory.", 
173                "Syntax: 'put [-r] <directory or file>'"
174            ], 
175            "subcommands": []
176        },
177        "reconnect": {
178            "description": [
179                "Reconnect to the remote machine (useful if connection timed out).", 
180                "Syntax: 'reconnect'"
181            ], 
182            "subcommands": []
183        },
184        "reset": {
185            "description": [
186                "Reset the TTY output, useful if it was broken after printing a binary file on stdout.",
187                "Syntax: 'reset'"
188            ], 
189            "subcommands": []
190        },
191        "rmdir": {
192            "description": [
193                "Removes a remote directory.", 
194                "Syntax: 'rmdir <directory>'"
195            ], 
196            "subcommands": []
197        },
198        "rm": {
199            "description": [
200                "Removes a remote file.", 
201                "Syntax: 'rm <file>'"
202            ], 
203            "subcommands": []
204        },
205        "shares": {
206            "description": [
207                "Lists the SMB shares served by the remote machine.", 
208                "Syntax: 'shares'"
209            ], 
210            "subcommands": []
211        },
212        "use": {
213            "description": [
214                "Use a SMB share.", 
215                "Syntax: 'use <sharename>'"
216            ], 
217            "subcommands": []
218        },
219        "tree": {
220            "description": [
221                "Displays a tree view of the remote directories.",
222                "Syntax: 'tree [directory]'"
223            ], 
224            "subcommands": []
225        },
226    }
227
228    def __init__(self, smbSession, config):
229        # Objects
230        self.smbSession = smbSession
231        self.config = config
232        # Pre computing for some commands 
233        self.commands["help"]["subcommands"] = ["format"] + list(self.commands.keys())
234        self.commands["help"]["subcommands"].remove("help")
235
236    def complete(self, text, state):
237        """
238        Function to handle command completion in the LDAP console.
239
240        This function completes the user"s input based on the available options for commands in the LDAP console.
241
242        Args:
243            text (str): The current text input by the user.
244            state (int): The current state of completion.
245
246        Returns:
247            str: The next completion suggestion based on the user"s input state.
248        """
249
250        if state == 0:
251            
252            # No text typed yet, need the list of commands available
253            if len(text) == 0:
254                self.matches = [s for s in self.commands.keys()]
255
256            elif len(text) != 0:
257                # This is for the main command
258                if text.count(" ") == 0:
259                    self.matches = [s for s in self.commands.keys() if s and s.startswith(text)]
260                
261                # This is for subcommands
262                elif text.count(" ") >= 1:
263                    command, remainder = text.split(" ", 1)
264                    if command in self.commands.keys():
265                        if command == "use":
266                            # Choose SMB Share to connect to
267                            self.matches = [
268                                command + " " + s.lower()
269                                for s in self.smbSession.list_shares().keys()
270                                if s.lower().startswith(remainder.lower())
271                            ]
272
273                        elif command in ["cd", "dir", "ls", "mkdir", "rmdir", "tree"]:
274                            # Choose remote directory
275                            path = ""
276                            if '\\' in remainder.strip() or '/' in remainder.strip():
277                                path = remainder.strip().replace('/', ntpath.sep)
278                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
279
280                            directory_contents = self.smbSession.list_contents(path=path).items()
281
282                            matching_entries = []
283                            for _, entry in directory_contents:
284                                if entry.is_directory() and entry.get_longname() not in [".",".."]:
285                                    if len(path) != 0:
286                                        matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
287                                    else:
288                                        matching_entries.append(entry.get_longname() + ntpath.sep)
289
290                            self.matches = [
291                                command + " " + s 
292                                for s in matching_entries
293                                if s.lower().startswith(remainder.lower())
294                            ]
295
296                        elif command in ["bat", "cat", "get", "rm"]:
297                            # Choose local files and directories
298                            path = ""
299                            if '\\' in remainder.strip() or '/' in remainder.strip():
300                                path = remainder.strip().replace('/', ntpath.sep)
301                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
302
303                            directory_contents = self.smbSession.list_contents(path=path).items()
304
305                            matching_entries = []
306                            for _, entry in directory_contents:
307                                if entry.get_longname() not in [".",".."]:
308                                    if len(path) != 0:
309                                        if entry.is_directory():
310                                            matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
311                                        else:
312                                            matching_entries.append(path + ntpath.sep + entry.get_longname())
313                                    else:
314                                        if entry.is_directory():
315                                            matching_entries.append(entry.get_longname() + ntpath.sep)
316                                        else:
317                                            matching_entries.append(entry.get_longname())
318
319                            self.matches = [
320                                command + " " + s 
321                                for s in matching_entries
322                                if s.lower().startswith(remainder.lower())
323                            ]
324
325                        elif command in ["lcd", "lls", "put", "lmkdir", "lrm", "lrmdir"]:
326                            # Choose directory
327                            path = ""
328                            if os.path.sep in remainder.strip():
329                                path = path.split(os.path.sep)[:-1]
330                                path = os.path.sep.join(path)
331                            
332                            # Current dir
333                            if len(path.strip()) == 0:
334                                path = "."
335
336                            directory_contents = os.listdir(path=path + os.path.sep)
337                            matching_entries = []
338                            for entry in directory_contents:
339                                if entry not in [".",".."]:
340                                    entry_path = path + os.path.sep + entry
341                                    if os.path.isdir(entry_path):
342                                        matching_entries.append(entry_path + os.path.sep)
343                                    else:
344                                        matching_entries.append(entry_path)
345
346                            self.matches = [
347                                command + " " + s
348                                for s in matching_entries
349                                if s.startswith(remainder)
350                            ]
351                            
352                        else:
353                            # Generic case for subcommands
354                            self.matches = [
355                                command + " " + s
356                                for s in self.commands[command]["subcommands"]
357                                if s.startswith(remainder)
358                            ]
359                    else:
360                        # Unknown subcommand, skipping autocomplete
361                        pass
362                else:
363                    self.matches = []
364            else:
365                self.matches = self.commands.keys()[:]
366
367        try:
368            return self.matches[state] + " "
369        except IndexError:
370            return None
371
372    def print_help(self, command=None):
373        """
374        Prints help information for a specific command or all commands if no command is specified.
375
376        This method displays the help information for the command passed as an argument. If no command is specified,
377        it prints the help information for all available commands. The help information includes the command syntax,
378        description, and any subcommands associated with it. This method is designed to provide users with the necessary
379        guidance on how to use the commands in the smbclient-ng shell.
380
381        Args:
382            command (str, optional): The command to display help information for. If None, help for all commands is displayed.
383
384        Returns:
385            None
386        """
387
388        if command is not None:
389            if command not in list(self.commands.keys())+["format"]:
390                command = None
391        
392        # Print help for a specific command
393        if command is not None:
394            if command == "format":
395                self.print_help_format()
396            else:
397                print("│")
398                if self.config.no_colors:
399                    command_str = command + "─"* (15 - len(command))
400                    if len(self.commands[command]["description"]) == 0:
401                        print("│ ■ %s┤  " % command_str)
402                    elif len(self.commands[command]["description"]) == 1:
403                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
404                    else:
405                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
406                        for line in self.commands[command]["description"][1:]:
407                            print("│ %s│ %s " % (" "*(15+2), line))
408                else:
409                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
410                    if len(self.commands[command]["description"]) == 0:
411                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
412                    elif len(self.commands[command]["description"]) == 1:
413                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
414                    else:
415                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
416                        for line in self.commands[command]["description"][1:]:
417                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
418                print("│")
419        # Generic help
420        else:
421            print("│")
422            commands = sorted(self.commands.keys())
423            for command in commands:
424                if self.config.no_colors:
425                    command_str = command + "─"* (15 - len(command))
426                    if len(self.commands[command]["description"]) == 0:
427                        print("│ ■ %s┤  " % command_str)
428                    elif len(self.commands[command]["description"]) == 1:
429                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
430                    else:
431                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
432                        for line in self.commands[command]["description"][1:]:
433                            print("│ %s│ %s " % (" "*(15+2), line))
434                else:
435                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
436                    if len(self.commands[command]["description"]) == 0:
437                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
438                    elif len(self.commands[command]["description"]) == 1:
439                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
440                    else:
441                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
442                        for line in self.commands[command]["description"][1:]:
443                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
444                print("│")
445
446    def print_help_format(self):
447        """
448        Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.
449
450        This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning
451        of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.
452        """
453
454        print("File attributes format:\n")
455        print("\x1b[1mdachnrst\x1b[0m")
456        print("\x1b[90m│││││││└──>\x1b[0m Temporary")
457        print("\x1b[90m││││││└───>\x1b[0m System")
458        print("\x1b[90m│││││└────>\x1b[0m Read-Only")
459        print("\x1b[90m││││└─────>\x1b[0m Normal")
460        print("\x1b[90m│││└──────>\x1b[0m Hidden")
461        print("\x1b[90m││└───────>\x1b[0m Compressed")
462        print("\x1b[90m│└────────>\x1b[0m Archived")
463        print("\x1b[90m└─────────>\x1b[0m Directory")

A class to handle command completion for the smbclient-ng shell.

This class provides a command completion feature that suggests possible command names based on the current input. It uses a dictionary to store commands and their descriptions, which helps in providing hints during the command line interaction in the smbclient-ng shell.

Attributes: smbSession (SMBSession): An instance of SMBSession which maintains the current SMB session. commands (dict): A dictionary containing command names as keys and their descriptions and subcommands as values.

Methods: __init__(self, smbSession): Initializes the CommandCompleter with an SMBSession.

CommandCompleter(smbSession, config)
228    def __init__(self, smbSession, config):
229        # Objects
230        self.smbSession = smbSession
231        self.config = config
232        # Pre computing for some commands 
233        self.commands["help"]["subcommands"] = ["format"] + list(self.commands.keys())
234        self.commands["help"]["subcommands"].remove("help")
commands = {'bat': {'description': ['Pretty prints the contents of a file.', "Syntax: 'bat <file>'"], 'subcommands': []}, 'cat': {'description': ['Get the contents of a file.', "Syntax: 'cat <file>'"], 'subcommands': []}, 'cd': {'description': ['Change the current working directory.', "Syntax: 'cd <directory>'"], 'subcommands': []}, 'close': {'description': ['Closes the SMB connection to the remote machine.', "Syntax: 'close'"], 'subcommands': []}, 'connect': {'description': ['Connect to the remote machine (useful if connection timed out).', "Syntax: 'connect'"], 'subcommands': []}, 'dir': {'description': ['List the contents of the current working directory.', "Syntax: 'dir'"], 'subcommands': []}, 'exit': {'description': ['Exits the smbclient-ng script.', "Syntax: 'exit'"], 'subcommands': []}, 'get': {'description': ['Get a remote file.', "Syntax: 'get [-r] <directory or file>'"], 'subcommands': []}, 'help': {'description': ['Displays this help message.', "Syntax: 'help'"], 'subcommands': ['format']}, 'info': {'description': ['Get information about the server and or the share.', "Syntax: 'info [server|share]'"], 'subcommands': ['server', 'share']}, 'lcd': {'description': ['Changes the current local directory.', "Syntax: 'lcd <directory>'"], 'subcommands': []}, 'lls': {'description': ['Lists the contents of the current local directory.', "Syntax: 'lls'"], 'subcommands': []}, 'lmkdir': {'description': ['Creates a new local directory.', "Syntax: 'lmkdir <directory>'"], 'subcommands': []}, 'lpwd': {'description': ['Shows the current local directory.', "Syntax: 'lpwd'"], 'subcommands': []}, 'lrm': {'description': ['Removes a local file.', "Syntax: 'lrm <file>'"], 'subcommands': []}, 'lrmdir': {'description': ['Removes a local directory.', "Syntax: 'lrmdir <directory>'"], 'subcommands': []}, 'ls': {'description': ['List the contents of the current remote working directory.', "Syntax: 'ls'"], 'subcommands': []}, 'ltree': {'description': ['Displays a tree view of the local directories.', "Syntax: 'ltree [directory]'"], 'subcommands': []}, 'mkdir': {'description': ['Creates a new remote directory.', "Syntax: 'mkdir <directory>'"], 'subcommands': []}, 'module': {'description': ['Loads a specific module for additional functionalities.', "Syntax: 'module <name>'"], 'subcommands': []}, 'put': {'description': ['Put a local file or directory in a remote directory.', "Syntax: 'put [-r] <directory or file>'"], 'subcommands': []}, 'reconnect': {'description': ['Reconnect to the remote machine (useful if connection timed out).', "Syntax: 'reconnect'"], 'subcommands': []}, 'reset': {'description': ['Reset the TTY output, useful if it was broken after printing a binary file on stdout.', "Syntax: 'reset'"], 'subcommands': []}, 'rmdir': {'description': ['Removes a remote directory.', "Syntax: 'rmdir <directory>'"], 'subcommands': []}, 'rm': {'description': ['Removes a remote file.', "Syntax: 'rm <file>'"], 'subcommands': []}, 'shares': {'description': ['Lists the SMB shares served by the remote machine.', "Syntax: 'shares'"], 'subcommands': []}, 'use': {'description': ['Use a SMB share.', "Syntax: 'use <sharename>'"], 'subcommands': []}, 'tree': {'description': ['Displays a tree view of the remote directories.', "Syntax: 'tree [directory]'"], 'subcommands': []}}
smbSession
config
def complete(self, text, state):
236    def complete(self, text, state):
237        """
238        Function to handle command completion in the LDAP console.
239
240        This function completes the user"s input based on the available options for commands in the LDAP console.
241
242        Args:
243            text (str): The current text input by the user.
244            state (int): The current state of completion.
245
246        Returns:
247            str: The next completion suggestion based on the user"s input state.
248        """
249
250        if state == 0:
251            
252            # No text typed yet, need the list of commands available
253            if len(text) == 0:
254                self.matches = [s for s in self.commands.keys()]
255
256            elif len(text) != 0:
257                # This is for the main command
258                if text.count(" ") == 0:
259                    self.matches = [s for s in self.commands.keys() if s and s.startswith(text)]
260                
261                # This is for subcommands
262                elif text.count(" ") >= 1:
263                    command, remainder = text.split(" ", 1)
264                    if command in self.commands.keys():
265                        if command == "use":
266                            # Choose SMB Share to connect to
267                            self.matches = [
268                                command + " " + s.lower()
269                                for s in self.smbSession.list_shares().keys()
270                                if s.lower().startswith(remainder.lower())
271                            ]
272
273                        elif command in ["cd", "dir", "ls", "mkdir", "rmdir", "tree"]:
274                            # Choose remote directory
275                            path = ""
276                            if '\\' in remainder.strip() or '/' in remainder.strip():
277                                path = remainder.strip().replace('/', ntpath.sep)
278                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
279
280                            directory_contents = self.smbSession.list_contents(path=path).items()
281
282                            matching_entries = []
283                            for _, entry in directory_contents:
284                                if entry.is_directory() and entry.get_longname() not in [".",".."]:
285                                    if len(path) != 0:
286                                        matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
287                                    else:
288                                        matching_entries.append(entry.get_longname() + ntpath.sep)
289
290                            self.matches = [
291                                command + " " + s 
292                                for s in matching_entries
293                                if s.lower().startswith(remainder.lower())
294                            ]
295
296                        elif command in ["bat", "cat", "get", "rm"]:
297                            # Choose local files and directories
298                            path = ""
299                            if '\\' in remainder.strip() or '/' in remainder.strip():
300                                path = remainder.strip().replace('/', ntpath.sep)
301                                path = ntpath.sep.join(path.split(ntpath.sep)[:-1]) 
302
303                            directory_contents = self.smbSession.list_contents(path=path).items()
304
305                            matching_entries = []
306                            for _, entry in directory_contents:
307                                if entry.get_longname() not in [".",".."]:
308                                    if len(path) != 0:
309                                        if entry.is_directory():
310                                            matching_entries.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
311                                        else:
312                                            matching_entries.append(path + ntpath.sep + entry.get_longname())
313                                    else:
314                                        if entry.is_directory():
315                                            matching_entries.append(entry.get_longname() + ntpath.sep)
316                                        else:
317                                            matching_entries.append(entry.get_longname())
318
319                            self.matches = [
320                                command + " " + s 
321                                for s in matching_entries
322                                if s.lower().startswith(remainder.lower())
323                            ]
324
325                        elif command in ["lcd", "lls", "put", "lmkdir", "lrm", "lrmdir"]:
326                            # Choose directory
327                            path = ""
328                            if os.path.sep in remainder.strip():
329                                path = path.split(os.path.sep)[:-1]
330                                path = os.path.sep.join(path)
331                            
332                            # Current dir
333                            if len(path.strip()) == 0:
334                                path = "."
335
336                            directory_contents = os.listdir(path=path + os.path.sep)
337                            matching_entries = []
338                            for entry in directory_contents:
339                                if entry not in [".",".."]:
340                                    entry_path = path + os.path.sep + entry
341                                    if os.path.isdir(entry_path):
342                                        matching_entries.append(entry_path + os.path.sep)
343                                    else:
344                                        matching_entries.append(entry_path)
345
346                            self.matches = [
347                                command + " " + s
348                                for s in matching_entries
349                                if s.startswith(remainder)
350                            ]
351                            
352                        else:
353                            # Generic case for subcommands
354                            self.matches = [
355                                command + " " + s
356                                for s in self.commands[command]["subcommands"]
357                                if s.startswith(remainder)
358                            ]
359                    else:
360                        # Unknown subcommand, skipping autocomplete
361                        pass
362                else:
363                    self.matches = []
364            else:
365                self.matches = self.commands.keys()[:]
366
367        try:
368            return self.matches[state] + " "
369        except IndexError:
370            return None

Function to handle command completion in the LDAP console.

This function completes the user"s input based on the available options for commands in the LDAP console.

Args: text (str): The current text input by the user. state (int): The current state of completion.

Returns: str: The next completion suggestion based on the user"s input state.

def print_help(self, command=None):
372    def print_help(self, command=None):
373        """
374        Prints help information for a specific command or all commands if no command is specified.
375
376        This method displays the help information for the command passed as an argument. If no command is specified,
377        it prints the help information for all available commands. The help information includes the command syntax,
378        description, and any subcommands associated with it. This method is designed to provide users with the necessary
379        guidance on how to use the commands in the smbclient-ng shell.
380
381        Args:
382            command (str, optional): The command to display help information for. If None, help for all commands is displayed.
383
384        Returns:
385            None
386        """
387
388        if command is not None:
389            if command not in list(self.commands.keys())+["format"]:
390                command = None
391        
392        # Print help for a specific command
393        if command is not None:
394            if command == "format":
395                self.print_help_format()
396            else:
397                print("│")
398                if self.config.no_colors:
399                    command_str = command + "─"* (15 - len(command))
400                    if len(self.commands[command]["description"]) == 0:
401                        print("│ ■ %s┤  " % command_str)
402                    elif len(self.commands[command]["description"]) == 1:
403                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
404                    else:
405                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
406                        for line in self.commands[command]["description"][1:]:
407                            print("│ %s│ %s " % (" "*(15+2), line))
408                else:
409                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
410                    if len(self.commands[command]["description"]) == 0:
411                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
412                    elif len(self.commands[command]["description"]) == 1:
413                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
414                    else:
415                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
416                        for line in self.commands[command]["description"][1:]:
417                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
418                print("│")
419        # Generic help
420        else:
421            print("│")
422            commands = sorted(self.commands.keys())
423            for command in commands:
424                if self.config.no_colors:
425                    command_str = command + "─"* (15 - len(command))
426                    if len(self.commands[command]["description"]) == 0:
427                        print("│ ■ %s┤  " % command_str)
428                    elif len(self.commands[command]["description"]) == 1:
429                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
430                    else:
431                        print("│ ■ %s┤ %s " % (command_str, self.commands[command]["description"][0]))
432                        for line in self.commands[command]["description"][1:]:
433                            print("│ %s│ %s " % (" "*(15+2), line))
434                else:
435                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
436                    if len(self.commands[command]["description"]) == 0:
437                        print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
438                    elif len(self.commands[command]["description"]) == 1:
439                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
440                    else:
441                        print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
442                        for line in self.commands[command]["description"][1:]:
443                            print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
444                print("│")

Prints help information for a specific command or all commands if no command is specified.

This method displays the help information for the command passed as an argument. If no command is specified, it prints the help information for all available commands. The help information includes the command syntax, description, and any subcommands associated with it. This method is designed to provide users with the necessary guidance on how to use the commands in the smbclient-ng shell.

Args: command (str, optional): The command to display help information for. If None, help for all commands is displayed.

Returns: None

def print_help_format(self):
446    def print_help_format(self):
447        """
448        Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.
449
450        This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning
451        of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.
452        """
453
454        print("File attributes format:\n")
455        print("\x1b[1mdachnrst\x1b[0m")
456        print("\x1b[90m│││││││└──>\x1b[0m Temporary")
457        print("\x1b[90m││││││└───>\x1b[0m System")
458        print("\x1b[90m│││││└────>\x1b[0m Read-Only")
459        print("\x1b[90m││││└─────>\x1b[0m Normal")
460        print("\x1b[90m│││└──────>\x1b[0m Hidden")
461        print("\x1b[90m││└───────>\x1b[0m Compressed")
462        print("\x1b[90m│└────────>\x1b[0m Archived")
463        print("\x1b[90m└─────────>\x1b[0m Directory")

Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.

This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.