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