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