From c1ceb2444e91673f78285b5f7835b04b43dc37bb Mon Sep 17 00:00:00 2001 From: "Remi GASCOU (Podalirius)" <79218792+p0dalirius@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:52:30 +0200 Subject: [PATCH] Release 2.0 --- README.md | 28 +- documentation/search.js | 2 +- documentation/smbclientng/core.html | 3 + .../smbclientng/core/CommandCompleter.html | 2659 +++--- documentation/smbclientng/core/Config.html | 192 +- .../smbclientng/core/Credentials.html | 776 ++ .../smbclientng/core/InteractiveShell.html | 5285 ++++++------ .../smbclientng/core/LocalFileIO.html | 824 +- documentation/smbclientng/core/Logger.html | 928 ++ documentation/smbclientng/core/Module.html | 156 +- .../core/ModuleArgumentParser.html | 98 +- .../smbclientng/core/SMBSession.html | 7520 +++++++++-------- .../smbclientng/core/SessionsManager.html | 1172 +++ documentation/smbclientng/core/utils.html | 1506 ++-- documentation/smbclientng/modules/Find.html | 1 + .../smbclientng/modules/GPPPasswords.html | 1 + pyproject.toml | 2 +- setup.py | 40 +- smbclientng/__main__.py | 2 +- smbclientng/core/CommandCompleter.py | 2 +- 20 files changed, 12612 insertions(+), 8585 deletions(-) create mode 100644 documentation/smbclientng/core/Credentials.html create mode 100644 documentation/smbclientng/core/Logger.html create mode 100644 documentation/smbclientng/core/SessionsManager.html diff --git a/README.md b/README.md index 797f932..15119ff 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,14 @@ - [x] `reset`: Reset the TTY output, useful if it was broken after printing a binary file on stdout. Syntax: `reset` - [x] `rm`: Removes a remote file. Syntax: `rm ` - [x] `rmdir`: Removes a remote directory. Syntax: `rmdir ` +- [x] `sessions`: Manage the SMB sessions. Syntax: `sessions [interact|create|delete|execute|list]` - [x] `shares`: Lists the SMB shares served by the remote machine. Syntax: `shares` - [x] `sizeof`: Recursively compute the size of a folder. Syntax: `sizeof [directory|file]` - [x] `tree`: Displays a tree view of the remote directories. Syntax: `tree [directory]` - [x] `umount`: Removes a mount point of the remote share on the local machine. Syntax: `umount ` - [x] `use`: Use a SMB share. Syntax: `use ` + ## Install To install `smbclient-ng`, you can use pip. Run the following command in your terminal: @@ -56,6 +58,7 @@ To install `smbclient-ng`, you can use pip. Run the following command in your te python3 -m pip install smbclientng ``` + ## Demonstration ![](./.github/example.png) @@ -70,10 +73,11 @@ $ ./smbclient-ng.py -h / __| '_ ` _ \| '_ \ / __| | |/ _ \ '_ \| __|____| '_ \ / _` | \__ \ | | | | | |_) | (__| | | __/ | | | ||_____| | | | (_| | |___/_| |_| |_|_.__/ \___|_|_|\___|_| |_|\__| |_| |_|\__, | - by @podalirius_ v1.4 |___/ + by @podalirius_ v2.0 |___/ -usage: smbclient-ng.py [-h] [--debug] [--no-colors] --target ip address [--kdcHost FQDN KDC] [-d DOMAIN] [-u USER] - [--no-pass | -p PASSWORD | -H [LMHASH:]NTHASH | --aes-key hex key] [-k] +usage: smbclient-ng.py [-h] [--debug] [--no-colors] [-S startup_script] [-N] --host HOST [--port PORT] + [--kdcHost FQDN KDC] [-d DOMAIN] [-u USER] [--no-pass | -p [PASSWORD] | -H + [LMHASH:]NTHASH | --aes-key hex key] [-k] smbclient-ng, a fast and user friendly way to interact with SMB shares. @@ -81,7 +85,14 @@ options: -h, --help show this help message and exit --debug Debug mode. --no-colors No colors mode. - --target ip address IP Address of the SMB Server to connect to. + -S startup_script, --startup-script startup_script + File containing the list of commands to be typed at start of the console. + -N, --not-interactive + Non interactive mode. + +Target: + --host HOST IP address or hostname of the SMB Server to connect to. + --port PORT Port of the SMB Server to connect to. (default: 445) Authentication & connection: --kdcHost FQDN KDC FQDN of KDC for Kerberos. @@ -90,13 +101,14 @@ Authentication & connection: -u USER, --user USER User to authenticate with. --no-pass Don't ask for password (useful for -k). - -p PASSWORD, --password PASSWORD + -p [PASSWORD], --password [PASSWORD] Password to authenticate with. -H [LMHASH:]NTHASH, --hashes [LMHASH:]NTHASH NT/LM hashes, format is LMhash:NThash. --aes-key hex key AES key to use for Kerberos Authentication (128 or 256 bits). - -k, --kerberos Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target parameters. If valid - credentials cannot be found, it will use the ones specified in the command line. + -k, --kerberos Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target + parameters. If valid credentials cannot be found, it will use the ones specified in the + command line. ``` @@ -104,7 +116,7 @@ Authentication & connection: + Connect to a remote SMB server: ``` - ./smbclient-ng.py -u "Administrator" -d LAB -p 'Admin123!' --target "10.0.0.201" + ./smbclient-ng.py -d "LAB" -u "Administrator" -p 'Admin123!' --host "10.0.0.201" ``` diff --git a/documentation/search.js b/documentation/search.js index 8c80f7e..939e0ee 100644 --- a/documentation/search.js +++ b/documentation/search.js @@ -1,6 +1,6 @@ window.pdocSearch = (function(){ /** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, {"fullname": "smbclientng.core", "modulename": "smbclientng.core", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter", "modulename": "smbclientng.core.CommandCompleter", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter", "kind": "class", "doc": "

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

\n\n

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

\n\n

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

\n\n

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

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.__init__", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.__init__", "kind": "function", "doc": "

\n", "signature": "(smbSession, config)"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.commands", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.commands", "kind": "variable", "doc": "

\n", "default_value": "{'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': ['rights']}, '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': []}}"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.smbSession", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.smbSession", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.config", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.complete", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.complete", "kind": "function", "doc": "

Function to handle command completion in the LDAP console.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, text, state):", "funcdef": "def"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.print_help", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.print_help", "kind": "function", "doc": "

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

\n\n

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, command=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.print_help_format", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.print_help_format", "kind": "function", "doc": "

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

\n\n

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

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Config", "modulename": "smbclientng.core.Config", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Config.Config", "modulename": "smbclientng.core.Config", "qualname": "Config", "kind": "class", "doc": "

Configuration handler for smbclientng.

\n\n

This class manages the configuration settings for the smbclientng tool, including debug and color output settings.\nIt provides a structured way to access and modify these settings throughout the application.

\n\n

Attributes:\n _debug (bool): Flag to enable or disable debug mode.\n _no_colors (bool): Flag to enable or disable colored output, depending on the platform.

\n\n

Methods:\n debug: Property to get or set the debug mode.\n no_colors: Property to get or set the colored output preference.

\n"}, {"fullname": "smbclientng.core.Config.Config.__init__", "modulename": "smbclientng.core.Config", "qualname": "Config.__init__", "kind": "function", "doc": "

\n", "signature": "(debug=False, no_colors=None)"}, {"fullname": "smbclientng.core.Config.Config.debug", "modulename": "smbclientng.core.Config", "qualname": "Config.debug", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Config.Config.no_colors", "modulename": "smbclientng.core.Config", "qualname": "Config.no_colors", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell", "modulename": "smbclientng.core.InteractiveShell", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.command_arguments_required", "modulename": "smbclientng.core.InteractiveShell", "qualname": "command_arguments_required", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.active_smb_connection_needed", "modulename": "smbclientng.core.InteractiveShell", "qualname": "active_smb_connection_needed", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.smb_share_is_set", "modulename": "smbclientng.core.InteractiveShell", "qualname": "smb_share_is_set", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell", "kind": "class", "doc": "

Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.

\n\n

This class handles user input, executes commands, and manages the state of the SMB session. It provides\na command line interface for users to interact with SMB shares, execute commands like directory listing,\nfile transfer, and more.

\n\n

Attributes:\n smbSession (SMBConnection): The active SMB connection session.\n debug (bool): Flag to enable or disable debug mode.\n smb_share (str): The current SMB share in use.\n smb_path (str): The current path within the SMB share.\n commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.

\n\n

Methods:\n __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.\n run(self): Starts the command line interface loop, processing user input until exit.

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.__init__", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.__init__", "kind": "function", "doc": "

\n", "signature": "(smbSession, config)"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.smbSession", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.smbSession", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.config", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.commandCompleterObject", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.commandCompleterObject", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.modules", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.modules", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.run", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.run", "kind": "function", "doc": "

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.process_command", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.process_command", "kind": "function", "doc": "

\n", "signature": "(self, command, arguments=[]):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_debug", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_debug", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_bat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_bat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_cd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_cd", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_cat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_cat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_close", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_close", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_get", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_get", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_help", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_help", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_info", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_info", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcd", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcp", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcp", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lbat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lbat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lls", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lls", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lmkdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lmkdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lpwd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lpwd", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrename", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrename", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrm", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrm", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrmdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrmdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_ltree", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_ltree", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_ls", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_ls", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_mkdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_mkdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_module", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_module", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_mount", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_mount", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_put", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_put", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_reconnect", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_reconnect", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_reset", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_reset", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_rm", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_rm", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_rmdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_rmdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_sizeof", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_sizeof", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_shares", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_shares", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_tree", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_tree", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_umount", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_umount", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_use", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_use", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO", "modulename": "smbclientng.core.LocalFileIO", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO", "kind": "class", "doc": "

Class LocalFileIO is designed to handle local file input/output operations within the smbclient-ng tool.\nIt provides functionalities to open, read, write, and manage progress of file operations based on the expected size of the file.

\n\n

Attributes:\n mode (str): The mode in which the file should be opened (e.g., 'rb', 'wb').\n path (str): The path to the file that needs to be handled.\n expected_size (int, optional): The expected size of the file in bytes. This is used to display progress.\n debug (bool): Flag to enable debug mode which provides additional output during operations.

\n\n

Methods:\n __init__(self, mode, path=None, expected_size=None, debug=False): Initializes the LocalFileIO instance.\n write(self, data): Writes data to the file and updates the progress bar if expected size is provided.\n read(self, size): Reads data from the file up to the specified size and updates the progress bar if expected size is provided.

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.__init__", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.__init__", "kind": "function", "doc": "

\n", "signature": "(\tmode,\tpath=None,\texpected_size=None,\tkeepRemotePath=False,\tdebug=False)"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.mode", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.mode", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.path", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.path", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.dir", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.dir", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.debug", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.debug", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.expected_size", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.expected_size", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.keepRemotePath", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.keepRemotePath", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.write", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.write", "kind": "function", "doc": "

Writes data to the file.

\n\n

This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set.

\n\n

Args:\n data (bytes): The data to be written to the file.

\n\n

Returns:\n int: The number of bytes written.

\n", "signature": "(self, data):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.read", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.read", "kind": "function", "doc": "

Reads a specified amount of data from the file.

\n\n

This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set.

\n\n

Args:\n size (int): The number of bytes to read from the file.

\n\n

Returns:\n bytes: The data read from the file.

\n", "signature": "(self, size):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.close", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.close", "kind": "function", "doc": "

Closes the file descriptor and optionally removes the file.

\n\n

This method ensures that the file descriptor is properly closed and the file is removed if specified.\nIt also stops the progress bar if it was initiated and cleans up the object by deleting it.

\n\n

Args:\n remove (bool): If True, the file at the path will be removed after closing the file descriptor.

\n", "signature": "(self, remove=False):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.set_error", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.set_error", "kind": "function", "doc": "

Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns.

\n\n

This method is used to communicate error states or important messages directly in the progress bar interface.\nIt updates the task description with the provided message and simplifies the progress bar to show only the text\nand download columns, removing other elements like speed and time remaining which may not be relevant in an error state.

\n\n

Args:\n message (str): The error or status message to display in the progress bar.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module", "modulename": "smbclientng.core.Module", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Module.Module", "modulename": "smbclientng.core.Module", "qualname": "Module", "kind": "class", "doc": "

A parent class for all modules in the smbclient-ng tool.

\n\n

This class provides common attributes and methods that are shared among different modules.

\n"}, {"fullname": "smbclientng.core.Module.Module.__init__", "modulename": "smbclientng.core.Module", "qualname": "Module.__init__", "kind": "function", "doc": "

\n", "signature": "(smbSession, config)"}, {"fullname": "smbclientng.core.Module.Module.name", "modulename": "smbclientng.core.Module", "qualname": "Module.name", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Module.Module.description", "modulename": "smbclientng.core.Module", "qualname": "Module.description", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Module.Module.smbSession", "modulename": "smbclientng.core.Module", "qualname": "Module.smbSession", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Module.Module.options", "modulename": "smbclientng.core.Module", "qualname": "Module.options", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Module.Module.config", "modulename": "smbclientng.core.Module", "qualname": "Module.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Module.Module.parseArgs", "modulename": "smbclientng.core.Module", "qualname": "Module.parseArgs", "kind": "function", "doc": "

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module.Module.run", "modulename": "smbclientng.core.Module", "qualname": "Module.run", "kind": "function", "doc": "

Placeholder method for running the module.

\n\n

This method should be implemented by subclasses to define the specific behavior of the module.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module.Module.processArguments", "modulename": "smbclientng.core.Module", "qualname": "Module.processArguments", "kind": "function", "doc": "

\n", "signature": "(self, parser, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.core.ModuleArgumentParser", "modulename": "smbclientng.core.ModuleArgumentParser", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.ModuleArgumentParser.ModuleArgumentParser", "modulename": "smbclientng.core.ModuleArgumentParser", "qualname": "ModuleArgumentParser", "kind": "class", "doc": "

A custom argument parser for handling module-specific command-line arguments in the smbclientng application.

\n\n

This class extends the argparse.ArgumentParser and provides custom error handling specific to the needs of smbclientng modules.\nIt is designed to provide clear and user-friendly command-line interfaces for various modules within the smbclientng suite.

\n\n

Attributes:\n None

\n\n

Methods:\n error(message: str):\n Overrides the default error handling to provide a more informative error message and display the help text.

\n", "bases": "argparse.ArgumentParser"}, {"fullname": "smbclientng.core.ModuleArgumentParser.ModuleArgumentParser.error", "modulename": "smbclientng.core.ModuleArgumentParser", "qualname": "ModuleArgumentParser.error", "kind": "function", "doc": "

Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display.

\n\n

This method is called when ArgumentParser encounters an error. It writes the error message to stderr,\ndisplays the help message, and then exits the program with a status code of 2.

\n\n

Args:\n message (str): The error message to be displayed.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession", "modulename": "smbclientng.core.SMBSession", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession", "kind": "class", "doc": "

Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections.\nIt provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.

\n\n

Attributes:\n address (str): The IP address or hostname of the SMB server.\n domain (str): The domain name for SMB server authentication.\n username (str): The username for SMB server authentication.\n password (str): The password for SMB server authentication.\n lmhash (str): The LM hash of the user's password, if available.\n nthash (str): The NT hash of the user's password, if available.\n use_kerberos (bool): A flag to determine whether to use Kerberos for authentication.\n kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication.\n debug (bool): A flag to enable debug output.\n smbClient (object): The SMB client object used for the connection.\n connected (bool): A flag to check the status of the connection.\n smb_share (str): The current SMB share in use.\n smb_path (str): The current path within the SMB share.

\n\n

Methods:\n __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False):\n Initializes the SMBSession with the specified parameters.\n init_smb_session():\n Initializes the SMB session by connecting to the server and authenticating using the specified method.

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.__init__", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.__init__", "kind": "function", "doc": "

\n", "signature": "(\taddress,\tdomain,\tusername,\tpassword,\tlmhash,\tnthash,\tuse_kerberos=False,\tkdcHost=None,\tconfig=None)"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.config", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.address", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.address", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.domain", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.domain", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.username", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.username", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.password", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.password", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.lmhash", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.lmhash", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.nthash", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.nthash", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.use_kerberos", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.use_kerberos", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.kdcHost", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.kdcHost", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smbClient", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smbClient", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.connected", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.connected", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.available_shares", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.available_shares", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_share", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_share", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_cwd", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_cwd", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_tree_id", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_tree_id", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.init_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.init_smb_session", "kind": "function", "doc": "

Initializes and establishes a session with the SMB server.

\n\n

This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.\nIt attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.

\n\n

The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.

\n\n

Returns:\n bool: True if the connection and authentication are successful, False otherwise.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.close_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.close_smb_session", "kind": "function", "doc": "

Closes the current SMB session by disconnecting the SMB client.

\n\n

This method ensures that the SMB client connection is properly closed. It checks if the client is connected\nand if so, it closes the connection and resets the connection status.

\n\n

Raises:\n Exception: If the SMB client is not initialized or if there's an error during the disconnection process.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.read_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.read_file", "kind": "function", "doc": "

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.find", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.find", "kind": "function", "doc": "

\n", "signature": "(self, paths=[], callback=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_file", "kind": "function", "doc": "

Retrieves a file from the specified path on the SMB share.

\n\n

This method attempts to retrieve a file from the given path within the currently connected SMB share.\nIf the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local\nfile object and writing the contents of the remote file to it using the SMB client's getFile method.

\n\n

Parameters:\n path (str): The path of the file to retrieve. If None, uses the current smb_path.

\n\n

Returns:\n None

\n", "signature": "(self, path=None, keepRemotePath=False):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_file_recursively", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_file_recursively", "kind": "function", "doc": "

Recursively retrieves files from a specified path on the SMB share.

\n\n

This method navigates through all directories starting from the given path,\nand downloads all files found. It handles directories recursively, ensuring\nthat all nested files are retrieved. The method skips over directory entries\nand handles errors gracefully, attempting to continue the operation where possible.

\n\n

Parameters:\n path (str): The initial directory path from which to start the recursive file retrieval.\n If None, it starts from the root of the configured SMB share.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_entry", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_entry", "kind": "function", "doc": "

Retrieves information about a specific entry located at the provided path on the SMB share.

\n\n

This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.

\n\n

Args:\n path (str): The path of the entry to retrieve information about.

\n\n

Returns:\n Entry: An object representing the entry at the specified path, or None if the entry is not found.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.info", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.info", "kind": "function", "doc": "

Displays information about the server and optionally the shares.

\n\n

This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the share parameter is set to True and a share is currently set, it will also attempt to display information about the share.

\n\n

Parameters:\n share (bool): If True, display information about the current share.\n server (bool): If True, display information about the server.

\n\n

Returns:\n None

\n", "signature": "(self, share=True, server=True):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.list_contents", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.list_contents", "kind": "function", "doc": "

Lists the contents of a specified directory on the SMB share.

\n\n

This method retrieves the contents of a directory specified by shareName and path. If shareName or path\nis not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with\nthe long names of the files and directories as keys and their respective SMB entry objects as values.

\n\n

Args:\n shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.\n path (str, optional): The directory path to list contents from. Defaults to the current path if None.

\n\n

Returns:\n dict: A dictionary with file and directory names as keys and their SMB entry objects as values.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.list_shares", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.list_shares", "kind": "function", "doc": "

Lists all the shares available on the connected SMB server.

\n\n

This method queries the SMB server to retrieve a list of all available shares. It populates the shares dictionary\nwith key-value pairs where the key is the share name and the value is a dictionary containing details about the share\nsuch as its name, type, raw type, and any comments associated with the share.

\n\n

Returns:\n dict: A dictionary containing information about each share available on the server.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.mkdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.mkdir", "kind": "function", "doc": "

Creates a directory at the specified path on the SMB share.

\n\n

This method takes a path and attempts to create the directory structure on the SMB share. If the path includes\nnested directories, it will create each directory in the sequence. If a directory already exists, it will skip\nthe creation for that directory without raising an error.

\n\n

Args:\n path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.

\n\n

Note:\n The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.mount", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.mount", "kind": "function", "doc": "

Generates the command to mount an SMB share on different platforms.

\n\n

This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.\nIt constructs the mount command using the provided parameters and executes it using the os.system() function.

\n\n

Args:\n local_mount_point (str): The local directory where the SMB share will be mounted.\n remote_path (str): The remote path on the SMB share to be mounted.

\n\n

Note:\n - For Windows platform, the command uses 'net use' to mount the share.\n - For Linux platform, the command uses 'mount' to mount the share.\n - For macOS platform, the command uses 'mount_smbfs' to mount the share.\n - If the platform is not supported, an error message is displayed.

\n\n

Returns:\n None

\n", "signature": "(self, local_mount_point, remote_path):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_exists", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_exists", "kind": "function", "doc": "

Checks if the specified path exists on the SMB share.

\n\n

This method determines if a given path exists on the SMB share by attempting to list the contents of the path.\nIf the path listing is successful and returns one or more entries, the path is considered to exist.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path exists, False otherwise or if an error occurs.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_isdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_isdir", "kind": "function", "doc": "

Checks if the specified path is a directory on the SMB share.

\n\n

This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the\ncontents of the path and filtering for entries that match the basename of the path and are marked as directories.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path is a directory, False otherwise or if an error occurs.

\n", "signature": "(self, pathFromRoot=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_isfile", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_isfile", "kind": "function", "doc": "

Checks if the specified path is a file on the SMB share.

\n\n

This method determines if a given path corresponds to a file on the SMB share. It does this by listing the\ncontents of the path and filtering for entries that match the basename of the path and are not marked as directories.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path is a file, False otherwise or if an error occurs.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.ping_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.ping_smb_session", "kind": "function", "doc": "

Tests the connectivity to the SMB server by sending an echo command.

\n\n

This method attempts to send an echo command to the SMB server to check if the session is still active.\nIt updates the connected attribute of the class based on the success or failure of the echo command.

\n\n

Returns:\n bool: True if the echo command succeeds (indicating the session is active), False otherwise.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.put_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.put_file", "kind": "function", "doc": "

Uploads a single file to the SMB share.

\n\n

This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.\nIt handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.\nGeneral exceptions are caught and logged, with a traceback provided if debugging is enabled.

\n\n

Args:\n localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.

\n", "signature": "(self, localpath=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.put_file_recursively", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.put_file_recursively", "kind": "function", "doc": "

Recursively uploads files from a specified local directory to the SMB share.

\n\n

This method walks through the given local directory and all its subdirectories, uploading each file to the\ncorresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,\nit iterates over all files and directories within the local path, creating necessary directories on the SMB share\nand uploading files. If the local path is not a directory, it prints an error message.

\n\n

Args:\n localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.

\n", "signature": "(self, localpath=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.rmdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.rmdir", "kind": "function", "doc": "

Removes a directory from the SMB share at the specified path.

\n\n

This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,\nit prints an error message indicating the failure and the reason. If debugging is enabled, it also prints\nthe stack trace of the exception.

\n\n

Args:\n path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.rm", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.rm", "kind": "function", "doc": "

Removes a file from the SMB share at the specified path.

\n\n

This method attempts to delete a file located at the given path on the SMB share. If the operation fails,\nit prints an error message indicating the failure and the reason. If debugging is enabled, it also prints\nthe stack trace of the exception.

\n\n

Args:\n path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.tree", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.tree", "kind": "function", "doc": "

Recursively lists the directory structure of the SMB share starting from the specified path.

\n\n

This function prints a visual representation of the directory tree of the remote SMB share. It uses\nrecursion to navigate through directories and lists all files and subdirectories in each directory.\nThe output is color-coded and formatted to enhance readability, with directories highlighted in cyan.

\n\n

Args:\n path (str, optional): The starting path on the SMB share from which to begin listing the tree.\n Defaults to the root of the current share.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.umount", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.umount", "kind": "function", "doc": "

Unmounts the specified local mount point of the remote share.

\n\n

This method unmounts the specified local mount point of the remote share based on the platform.\nIt supports Windows, Linux, and macOS platforms for unmounting.

\n\n

Parameters:\n local_mount_point (str): The local mount point to unmount.

\n\n

Raises:\n None

\n", "signature": "(self, local_mount_point):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.test_rights", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.test_rights", "kind": "function", "doc": "

Tests the read and write access rights of the current SMB session.

\n\n

This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.

\n\n

Returns:\n dict: A dictionary containing the read and write access rights status.\n - \"readable\" (bool): Indicates if the session has read access rights.\n - \"writable\" (bool): Indicates if the session has write access rights.

\n", "signature": "(self, sharename):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.set_share", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.set_share", "kind": "function", "doc": "

Sets the current SMB share to the specified share name.

\n\n

This method updates the SMB session to use the specified share name. It checks if the share name is valid\nand updates the smb_share attribute of the SMBSession instance.

\n\n

Parameters:\n shareName (str): The name of the share to set as the current SMB share.

\n\n

Raises:\n ValueError: If the shareName is None or an empty string.

\n", "signature": "(self, shareName):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.set_cwd", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.set_cwd", "kind": "function", "doc": "

Sets the current working directory on the SMB share to the specified path.

\n\n

This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.\nIf the specified path is not a directory, the cwd remains unchanged.

\n\n

Parameters:\n path (str): The path to set as the current working directory.

\n\n

Raises:\n ValueError: If the specified path is not a directory.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils", "modulename": "smbclientng.core.utils", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.utils.parse_lm_nt_hashes", "modulename": "smbclientng.core.utils", "qualname": "parse_lm_nt_hashes", "kind": "function", "doc": "

Parse the input string containing LM and NT hash values and return them separately.

\n\n

This function takes a string containing LM and NT hash values, typically separated by a colon (:).\nIt returns the LM and NT hash values as separate strings. If only one hash value is provided, it is\nassumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are\nfound, both return values are empty strings.

\n\n

Args:\n lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon.

\n\n

Returns:\n tuple: A tuple containing two strings (lm_hash_value, nt_hash_value).\n - lm_hash_value: The LM hash value or its default if not provided.\n - nt_hash_value: The NT hash value or its default if not provided.

\n\n

Extracted from p0dalirius/sectools library\nSrc: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24

\n", "signature": "(lm_nt_hashes_string):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.b_filesize", "modulename": "smbclientng.core.utils", "qualname": "b_filesize", "kind": "function", "doc": "

Convert a file size from bytes to a more readable format using the largest appropriate unit.

\n\n

This function takes an integer representing a file size in bytes and converts it to a human-readable\nstring using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to\ntwo decimal places.

\n\n

Args:\n l (int): The file size in bytes.

\n\n

Returns:\n str: A string representing the file size in a more readable format, including the appropriate unit.

\n", "signature": "(l):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.unix_permissions", "modulename": "smbclientng.core.utils", "qualname": "unix_permissions", "kind": "function", "doc": "

Generate a string representing the Unix-style permissions for a given file or directory.

\n\n

This function uses the os.lstat() method to retrieve the status of the specified file or directory,\nthen constructs a string that represents the Unix-style permissions based on the mode of the file.

\n\n

Args:\n entryname (str): The path to the file or directory for which permissions are being determined.

\n\n

Returns:\n str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--').\n The first character is either 'd' (directory), '-' (not a directory), followed by\n three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group,\n and others respectively.

\n", "signature": "(entryname):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.STYPE_MASK", "modulename": "smbclientng.core.utils", "qualname": "STYPE_MASK", "kind": "function", "doc": "

Extracts the share type flags from a given share type value.

\n\n

This function uses bitwise operations to determine which share type flags are set in the provided stype_value.\nIt checks against known share type flags and returns a list of the flags that are set.

\n\n

Parameters:\n stype_value (int): The share type value to analyze, typically obtained from SMB share properties.

\n\n

Returns:\n list: A list of strings, where each string represents a share type flag that is set in the input value.

\n", "signature": "(stype_value):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.windows_ls_entry", "modulename": "smbclientng.core.utils", "qualname": "windows_ls_entry", "kind": "function", "doc": "

This function generates a metadata string based on the attributes of the provided entry object.

\n\n

Parameters:\n entry (object): An object representing a file or directory entry.

\n\n

Returns:\n str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary.

\n", "signature": "(entry, config, pathToPrint=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.local_tree", "modulename": "smbclientng.core.utils", "qualname": "local_tree", "kind": "function", "doc": "

This function recursively lists the contents of a directory in a tree-like format.

\n\n

Parameters:\n path (str): The path to the directory to list.\n config (object): Configuration settings which may affect the output, such as whether to use colors.

\n\n

Returns:\n None: This function does not return anything but prints the directory tree to the console.

\n", "signature": "(path, config):", "funcdef": "def"}, {"fullname": "smbclientng.modules", "modulename": "smbclientng.modules", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.Find", "modulename": "smbclientng.modules.Find", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.Find.Find", "modulename": "smbclientng.modules.Find", "qualname": "Find", "kind": "class", "doc": "

A class to search for files in a directory hierarchy.

\n\n

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

\n", "bases": "smbclientng.core.Module.Module"}, {"fullname": "smbclientng.modules.Find.Find.name", "modulename": "smbclientng.modules.Find", "qualname": "Find.name", "kind": "variable", "doc": "

\n", "default_value": "'find'"}, {"fullname": "smbclientng.modules.Find.Find.description", "modulename": "smbclientng.modules.Find", "qualname": "Find.description", "kind": "variable", "doc": "

\n", "default_value": "'Search for files in a directory hierarchy'"}, {"fullname": "smbclientng.modules.Find.Find.parseArgs", "modulename": "smbclientng.modules.Find", "qualname": "Find.parseArgs", "kind": "function", "doc": "

Parses the command line arguments provided to the module.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.Find.Find.run", "modulename": "smbclientng.modules.Find", "qualname": "Find.run", "kind": "function", "doc": "

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords", "modulename": "smbclientng.modules.GPPPasswords", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords", "kind": "class", "doc": "

GPPPasswords is a module designed to search and retrieve stored Group Policy Preferences (GPP) passwords from specified network shares. \nIt leverages the SMB protocol to access files across the network, parse them, and extract credentials that are often stored within Group Policy Preferences files.

\n\n

This module is particularly useful in penetration testing scenarios where discovering stored credentials can lead to further system access or reveal poor security practices.

\n\n

Attributes:\n name (str): The name of the module, used in command line invocation.\n description (str): A brief description of what the module does.

\n\n

Methods:\n parseArgs(arguments): Parses and handles command line arguments for the module.\n parse_xmlfile_content(pathtofile): Parses the content of an XML file to extract credentials.

\n", "bases": "smbclientng.core.Module.Module"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.name", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.name", "kind": "variable", "doc": "

\n", "default_value": "'gpppasswords'"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.description", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.description", "kind": "variable", "doc": "

\n", "default_value": "'Searches for Group Policy Preferences Passwords in a share.'"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.parseArgs", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.parseArgs", "kind": "function", "doc": "

Parses the command line arguments provided to the module.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.parse_xmlfile_content", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.parse_xmlfile_content", "kind": "function", "doc": "

Parses the content of an XML file to extract credentials related to Group Policy Preferences.

\n\n

This method attempts to retrieve and parse the content of the specified XML file from the SMB share. It looks for credentials stored within the XML structure, specifically targeting the 'cpassword' attribute which is commonly used for storing encrypted passwords in Group Policy Preferences files.

\n\n

Args:\n pathtofile (str): The path to the XML file on the SMB share.

\n\n

Returns:\n list: A list of dictionaries, each containing details about found credentials such as username, encrypted and decrypted passwords, and other relevant attributes.

\n", "signature": "(self, pathtofile):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.decrypt_password", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.decrypt_password", "kind": "function", "doc": "

Decrypts a password from its Base64 encoded form using a known AES key and IV.

\n\n

This method takes a Base64 encoded string which is encrypted using AES-CBC with a fixed key and IV as per Microsoft's published details. It decodes the Base64 string, decrypts it using the AES key and IV, and returns the plaintext password.

\n\n

Args:\n pw_enc_b64 (str): The Base64 encoded string of the encrypted password.

\n\n

Returns:\n str: The decrypted password in plaintext, or an empty string if input is empty or decryption fails.

\n", "signature": "(self, pw_enc_b64):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.run", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.run", "kind": "function", "doc": "

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, arguments):", "funcdef": "def"}]; + /** pdoc search index */const docs = [{"fullname": "smbclientng", "modulename": "smbclientng", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core", "modulename": "smbclientng.core", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter", "modulename": "smbclientng.core.CommandCompleter", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter", "kind": "class", "doc": "

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

\n\n

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

\n\n

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

\n\n

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

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.__init__", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.__init__", "kind": "function", "doc": "

\n", "signature": "(smbSession, config, logger)"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.commands", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.commands", "kind": "variable", "doc": "

\n", "default_value": "{'bat': {'description': ['Pretty prints the contents of a remote file.', "Syntax: 'bat <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'cat': {'description': ['Get the contents of a remote file.', "Syntax: 'cat <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'cd': {'description': ['Change the current working directory.', "Syntax: 'cd <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'close': {'description': ['Closes the SMB connection to the remote machine.', "Syntax: 'close'"], 'subcommands': [], 'autocomplete': []}, 'connect': {'description': ['Connect to the remote machine (useful if connection timed out).', "Syntax: 'connect'"], 'subcommands': [], 'autocomplete': []}, 'debug': {'description': ['Command for dev debugging.', "Syntax: 'debug'"], 'subcommands': [], 'autocomplete': []}, 'dir': {'description': ['List the contents of the current working directory.', "Syntax: 'dir'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'exit': {'description': ['Exits the smbclient-ng script.', "Syntax: 'exit'"], 'subcommands': [], 'autocomplete': []}, 'get': {'description': ['Get a remote file.', "Syntax: 'get [-r] <directory or file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'help': {'description': ['Displays this help message.', "Syntax: 'help'"], 'subcommands': ['format'], 'autocomplete': []}, 'info': {'description': ['Get information about the server and or the share.', "Syntax: 'info [server|share]'"], 'subcommands': ['server', 'share'], 'autocomplete': []}, 'lbat': {'description': ['Pretty prints the contents of a local file.', "Syntax: 'lbat <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lcat': {'description': ['Print the contents of a local file.', "Syntax: 'lcat <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lcd': {'description': ['Changes the current local directory.', "Syntax: 'lcd <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lcp': {'description': ['Create a copy of a local file.', "Syntax: 'lcp <srcfile> <dstfile>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'lls': {'description': ['Lists the contents of the current local directory.', "Syntax: 'lls'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lmkdir': {'description': ['Creates a new local directory.', "Syntax: 'lmkdir <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lpwd': {'description': ['Shows the current local directory.', "Syntax: 'lpwd'"], 'subcommands': [], 'autocomplete': []}, 'lrename': {'description': ['Renames a local file.', "Syntax: 'lrename <oldfilename> <newfilename>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lrm': {'description': ['Removes a local file.', "Syntax: 'lrm <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lrmdir': {'description': ['Removes a local directory.', "Syntax: 'lrmdir <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'ls': {'description': ['List the contents of the current remote working directory.', "Syntax: 'ls'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'ltree': {'description': ['Displays a tree view of the local directories.', "Syntax: 'ltree [directory]'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'mkdir': {'description': ['Creates a new remote directory.', "Syntax: 'mkdir <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'module': {'description': ['Loads a specific module for additional functionalities.', "Syntax: 'module <name>'"], 'subcommands': [], 'autocomplete': []}, 'mount': {'description': ['Creates a mount point of the remote share on the local machine.', "Syntax: 'mount <remote_path> <local_mountpoint>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'put': {'description': ['Put a local file or directory in a remote directory.', "Syntax: 'put [-r] <directory or file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'reconnect': {'description': ['Reconnect to the remote machine (useful if connection timed out).', "Syntax: 'reconnect'"], 'subcommands': [], 'autocomplete': []}, 'reset': {'description': ['Reset the TTY output, useful if it was broken after printing a binary file on stdout.', "Syntax: 'reset'"], 'subcommands': [], 'autocomplete': []}, 'rmdir': {'description': ['Removes a remote directory.', "Syntax: 'rmdir <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'rm': {'description': ['Removes a remote file.', "Syntax: 'rm <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'sizeof': {'description': ['Recursively compute the size of a folder.', "Syntax: 'sizeof [directory|file]'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'sessions': {'description': ['Manage the SMB sessions.', "Syntax: 'sessions [access|create|delete|execute|list]'"], 'subcommands': ['create', 'delete', 'execute', 'interact', 'list'], 'autocomplete': []}, 'shares': {'description': ['Lists the SMB shares served by the remote machine.', "Syntax: 'shares'"], 'subcommands': ['rights'], 'autocomplete': []}, 'tree': {'description': ['Displays a tree view of the remote directories.', "Syntax: 'tree [directory]'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'umount': {'description': ['Removes a mount point of the remote share on the local machine.', "Syntax: 'umount <local_mount_point>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'use': {'description': ['Use a SMB share.', "Syntax: 'use <sharename>'"], 'subcommands': [], 'autocomplete': ['share']}}"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.smbSession", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.smbSession", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.config", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.logger", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.complete", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.complete", "kind": "function", "doc": "

Function to handle command completion in the LDAP console.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, text, state):", "funcdef": "def"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.print_help", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.print_help", "kind": "function", "doc": "

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

\n\n

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, command=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.CommandCompleter.CommandCompleter.print_help_format", "modulename": "smbclientng.core.CommandCompleter", "qualname": "CommandCompleter.print_help_format", "kind": "function", "doc": "

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

\n\n

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

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Config", "modulename": "smbclientng.core.Config", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Config.Config", "modulename": "smbclientng.core.Config", "qualname": "Config", "kind": "class", "doc": "

Configuration handler for smbclientng.

\n\n

This class manages the configuration settings for the smbclientng tool, including debug and color output settings.\nIt provides a structured way to access and modify these settings throughout the application.

\n\n

Attributes:\n _debug (bool): Flag to enable or disable debug mode.\n _no_colors (bool): Flag to enable or disable colored output, depending on the platform.

\n\n

Methods:\n debug: Property to get or set the debug mode.\n no_colors: Property to get or set the colored output preference.

\n"}, {"fullname": "smbclientng.core.Config.Config.__init__", "modulename": "smbclientng.core.Config", "qualname": "Config.__init__", "kind": "function", "doc": "

\n", "signature": "(debug=False, no_colors=None)"}, {"fullname": "smbclientng.core.Config.Config.not_interactive", "modulename": "smbclientng.core.Config", "qualname": "Config.not_interactive", "kind": "variable", "doc": "

\n", "default_value": "False"}, {"fullname": "smbclientng.core.Config.Config.startup_script", "modulename": "smbclientng.core.Config", "qualname": "Config.startup_script", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Config.Config.debug", "modulename": "smbclientng.core.Config", "qualname": "Config.debug", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Config.Config.no_colors", "modulename": "smbclientng.core.Config", "qualname": "Config.no_colors", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Credentials", "modulename": "smbclientng.core.Credentials", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Credentials.Credentials", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials", "kind": "class", "doc": "

Documentation for class Credentials

\n"}, {"fullname": "smbclientng.core.Credentials.Credentials.__init__", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.__init__", "kind": "function", "doc": "

\n", "signature": "(\tdomain,\tusername,\tpassword,\thashes=None,\tuse_kerberos=False,\taesKey=None,\tkdcHost=None)"}, {"fullname": "smbclientng.core.Credentials.Credentials.domain", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.domain", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Credentials.Credentials.username", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.username", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Credentials.Credentials.password", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.password", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Credentials.Credentials.nt_hex", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.nt_hex", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Credentials.Credentials.nt_raw", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.nt_raw", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Credentials.Credentials.lm_hex", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.lm_hex", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Credentials.Credentials.lm_raw", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.lm_raw", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Credentials.Credentials.use_kerberos", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.use_kerberos", "kind": "variable", "doc": "

\n", "default_value": "False"}, {"fullname": "smbclientng.core.Credentials.Credentials.aesKey", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.aesKey", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Credentials.Credentials.kdcHost", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.kdcHost", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Credentials.Credentials.set_hashes", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.set_hashes", "kind": "function", "doc": "

Sets the LM and NT hashes for the credentials.

\n\n

This method parses the provided hash string and sets the LM and NT hash values accordingly.\nIf the hash string is valid and contains both LM and NT hashes, they are set directly.\nIf only one hash is provided, the other is set to its default value.\nIf the hash string is None or invalid, both hashes are set to None.

\n\n

Args:\n hashes (str): A string containing LM and NT hashes separated by a colon.

\n", "signature": "(self, hashes):", "funcdef": "def"}, {"fullname": "smbclientng.core.Credentials.Credentials.canPassTheHash", "modulename": "smbclientng.core.Credentials", "qualname": "Credentials.canPassTheHash", "kind": "function", "doc": "

Determines if the current credentials can be used for a pass-the-hash attack.

\n\n

This method checks if both LM and NT hashes are available and not None. If both hashes are set,\nit indicates that the credentials may be used for a pass-the-hash attack.

\n\n

Returns:\n bool: True if both LM and NT hashes are available, False otherwise.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell", "modulename": "smbclientng.core.InteractiveShell", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.command_arguments_required", "modulename": "smbclientng.core.InteractiveShell", "qualname": "command_arguments_required", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.active_smb_connection_needed", "modulename": "smbclientng.core.InteractiveShell", "qualname": "active_smb_connection_needed", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.smb_share_is_set", "modulename": "smbclientng.core.InteractiveShell", "qualname": "smb_share_is_set", "kind": "function", "doc": "

\n", "signature": "(func):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell", "kind": "class", "doc": "

Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.

\n\n

This class handles user input, executes commands, and manages the state of the SMB session. It provides\na command line interface for users to interact with SMB shares, execute commands like directory listing,\nfile transfer, and more.

\n\n

Attributes:\n smbSession (SMBConnection): The active SMB connection session.\n debug (bool): Flag to enable or disable debug mode.\n smb_share (str): The current SMB share in use.\n smb_path (str): The current path within the SMB share.\n commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.

\n\n

Methods:\n __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.\n run(self): Starts the command line interface loop, processing user input until exit.

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.__init__", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.__init__", "kind": "function", "doc": "

\n", "signature": "(sessionsManager, config, logger)"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.running", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.running", "kind": "variable", "doc": "

\n", "default_value": "True"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.modules", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.modules", "kind": "variable", "doc": "

\n", "default_value": "{}"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.sessionsManager", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.sessionsManager", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.config", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.logger", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.commandCompleterObject", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.commandCompleterObject", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.run", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.run", "kind": "function", "doc": "

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.process_line", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.process_line", "kind": "function", "doc": "

\n", "signature": "(self, commandLine):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_debug", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_debug", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_bat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_bat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_cd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_cd", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_cat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_cat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_close", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_close", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_get", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_get", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_help", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_help", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_info", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_info", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lbat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lbat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcat", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcat", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcd", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lcp", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcp", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lls", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lls", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lmkdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lmkdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lpwd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lpwd", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrename", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrename", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrm", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrm", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_lrmdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lrmdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_ltree", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_ltree", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_ls", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_ls", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_mkdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_mkdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_module", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_module", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_mount", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_mount", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_put", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_put", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_reconnect", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_reconnect", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_reset", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_reset", "kind": "function", "doc": "

\n", "signature": "(self, arguments, command):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_rm", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_rm", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_rmdir", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_rmdir", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_sizeof", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_sizeof", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_shares", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_shares", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_tree", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_tree", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_umount", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_umount", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.InteractiveShell.InteractiveShell.command_use", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_use", "kind": "function", "doc": "

\n", "signature": "(*args, **kwargs):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO", "modulename": "smbclientng.core.LocalFileIO", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO", "kind": "class", "doc": "

Class LocalFileIO is designed to handle local file input/output operations within the smbclient-ng tool.\nIt provides functionalities to open, read, write, and manage progress of file operations based on the expected size of the file.

\n\n

Attributes:\n mode (str): The mode in which the file should be opened (e.g., 'rb', 'wb').\n path (str): The path to the file that needs to be handled.\n expected_size (int, optional): The expected size of the file in bytes. This is used to display progress.\n debug (bool): Flag to enable debug mode which provides additional output during operations.

\n\n

Methods:\n __init__(self, mode, path=None, expected_size=None, debug=False): Initializes the LocalFileIO instance.\n write(self, data): Writes data to the file and updates the progress bar if expected size is provided.\n read(self, size): Reads data from the file up to the specified size and updates the progress bar if expected size is provided.

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.__init__", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.__init__", "kind": "function", "doc": "

\n", "signature": "(\tmode,\tpath=None,\texpected_size=None,\tkeepRemotePath=False,\tlogger=None)"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.logger", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.mode", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.mode", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.path", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.path", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.dir", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.dir", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.debug", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.debug", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.expected_size", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.expected_size", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.keepRemotePath", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.keepRemotePath", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.write", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.write", "kind": "function", "doc": "

Writes data to the file.

\n\n

This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set.

\n\n

Args:\n data (bytes): The data to be written to the file.

\n\n

Returns:\n int: The number of bytes written.

\n", "signature": "(self, data):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.read", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.read", "kind": "function", "doc": "

Reads a specified amount of data from the file.

\n\n

This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set.

\n\n

Args:\n size (int): The number of bytes to read from the file.

\n\n

Returns:\n bytes: The data read from the file.

\n", "signature": "(self, size):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.close", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.close", "kind": "function", "doc": "

Closes the file descriptor and optionally removes the file.

\n\n

This method ensures that the file descriptor is properly closed and the file is removed if specified.\nIt also stops the progress bar if it was initiated and cleans up the object by deleting it.

\n\n

Args:\n remove (bool): If True, the file at the path will be removed after closing the file descriptor.

\n", "signature": "(self, remove=False):", "funcdef": "def"}, {"fullname": "smbclientng.core.LocalFileIO.LocalFileIO.set_error", "modulename": "smbclientng.core.LocalFileIO", "qualname": "LocalFileIO.set_error", "kind": "function", "doc": "

Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns.

\n\n

This method is used to communicate error states or important messages directly in the progress bar interface.\nIt updates the task description with the provided message and simplifies the progress bar to show only the text\nand download columns, removing other elements like speed and time remaining which may not be relevant in an error state.

\n\n

Args:\n message (str): The error or status message to display in the progress bar.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.Logger", "modulename": "smbclientng.core.Logger", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Logger.LogLevel", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel", "kind": "class", "doc": "

Create a collection of name/value pairs.

\n\n

Example enumeration:

\n\n
\n
>>> class Color(Enum):\n...     RED = 1\n...     BLUE = 2\n...     GREEN = 3\n
\n
\n\n

Access them by:

\n\n
    \n
  • attribute access:

    \n\n
    \n
    >>> Color.RED\n<Color.RED: 1>\n
    \n
  • \n
  • value lookup:

    \n\n
    \n
    >>> Color(1)\n<Color.RED: 1>\n
    \n
  • \n
  • name lookup:

    \n\n
    \n
    >>> Color['RED']\n<Color.RED: 1>\n
    \n
  • \n
\n\n

Enumerations can be iterated over, and know how many members they have:

\n\n
\n
>>> len(Color)\n3\n
\n
\n\n
\n
>>> list(Color)\n[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]\n
\n
\n\n

Methods can be added to enumerations, and members can have their own\nattributes -- see the documentation for details.

\n", "bases": "enum.Enum"}, {"fullname": "smbclientng.core.Logger.LogLevel.INFO", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel.INFO", "kind": "variable", "doc": "

\n", "default_value": "<LogLevel.INFO: 1>"}, {"fullname": "smbclientng.core.Logger.LogLevel.DEBUG", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel.DEBUG", "kind": "variable", "doc": "

\n", "default_value": "<LogLevel.DEBUG: 2>"}, {"fullname": "smbclientng.core.Logger.LogLevel.WARNING", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel.WARNING", "kind": "variable", "doc": "

\n", "default_value": "<LogLevel.WARNING: 3>"}, {"fullname": "smbclientng.core.Logger.LogLevel.ERROR", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel.ERROR", "kind": "variable", "doc": "

\n", "default_value": "<LogLevel.ERROR: 4>"}, {"fullname": "smbclientng.core.Logger.LogLevel.CRITICAL", "modulename": "smbclientng.core.Logger", "qualname": "LogLevel.CRITICAL", "kind": "variable", "doc": "

\n", "default_value": "<LogLevel.CRITICAL: 5>"}, {"fullname": "smbclientng.core.Logger.Logger", "modulename": "smbclientng.core.Logger", "qualname": "Logger", "kind": "class", "doc": "

A Logger class that provides logging functionalities with various levels such as INFO, DEBUG, WARNING, ERROR, and CRITICAL.\nIt supports color-coded output, which can be disabled, and can also log messages to a file.

\n\n

Attributes:\n __debug (bool): If True, debug level messages will be printed and logged.\n __nocolors (bool): If True, disables color-coded output.\n logfile (str|None): Path to a file where logs will be written. If None, logging to a file is disabled.

\n\n

Methods:\n __init__(debug=False, logfile=None, nocolors=False): Initializes the Logger instance.\n print(message=\"\"): Prints a message to stdout and logs it to a file if logging is enabled.\n info(message): Logs a message at the INFO level.\n debug(message): Logs a message at the DEBUG level if debugging is enabled.\n error(message): Logs a message at the ERROR level.

\n"}, {"fullname": "smbclientng.core.Logger.Logger.__init__", "modulename": "smbclientng.core.Logger", "qualname": "Logger.__init__", "kind": "function", "doc": "

\n", "signature": "(config, logfile=None)"}, {"fullname": "smbclientng.core.Logger.Logger.config", "modulename": "smbclientng.core.Logger", "qualname": "Logger.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Logger.Logger.logfile", "modulename": "smbclientng.core.Logger", "qualname": "Logger.logfile", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Logger.Logger.print", "modulename": "smbclientng.core.Logger", "qualname": "Logger.print", "kind": "function", "doc": "

Prints a message to stdout and logs it to a file if logging is enabled.

\n\n

This method prints the provided message to the standard output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

\n\n

Args:\n message (str): The message to be printed and logged.

\n", "signature": "(self, message='', end='\\n'):", "funcdef": "def"}, {"fullname": "smbclientng.core.Logger.Logger.info", "modulename": "smbclientng.core.Logger", "qualname": "Logger.info", "kind": "function", "doc": "

Logs a message at the INFO level.

\n\n

This method logs the provided message at the INFO level. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True. The message is also logged to a file if a log file path is specified during the Logger instance initialization.

\n\n

Args:\n message (str): The message to be logged at the INFO level.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.Logger.Logger.debug", "modulename": "smbclientng.core.Logger", "qualname": "Logger.debug", "kind": "function", "doc": "

Logs a message at the DEBUG level if debugging is enabled.

\n\n

This method logs the provided message at the DEBUG level if the debug attribute is set to True during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

\n\n

Args:\n message (str): The message to be logged.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.Logger.Logger.error", "modulename": "smbclientng.core.Logger", "qualname": "Logger.error", "kind": "function", "doc": "

Logs an error message to the console and the log file.

\n\n

This method logs the provided error message to the standard error output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

\n\n

Args:\n message (str): The error message to be logged.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module", "modulename": "smbclientng.core.Module", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.Module.Module", "modulename": "smbclientng.core.Module", "qualname": "Module", "kind": "class", "doc": "

A parent class for all modules in the smbclient-ng tool.

\n\n

This class provides common attributes and methods that are shared among different modules.

\n"}, {"fullname": "smbclientng.core.Module.Module.__init__", "modulename": "smbclientng.core.Module", "qualname": "Module.__init__", "kind": "function", "doc": "

\n", "signature": "(smbSession, config, logger)"}, {"fullname": "smbclientng.core.Module.Module.name", "modulename": "smbclientng.core.Module", "qualname": "Module.name", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Module.Module.description", "modulename": "smbclientng.core.Module", "qualname": "Module.description", "kind": "variable", "doc": "

\n", "default_value": "''"}, {"fullname": "smbclientng.core.Module.Module.smbSession", "modulename": "smbclientng.core.Module", "qualname": "Module.smbSession", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Module.Module.options", "modulename": "smbclientng.core.Module", "qualname": "Module.options", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.Module.Module.config", "modulename": "smbclientng.core.Module", "qualname": "Module.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Module.Module.logger", "modulename": "smbclientng.core.Module", "qualname": "Module.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.Module.Module.parseArgs", "modulename": "smbclientng.core.Module", "qualname": "Module.parseArgs", "kind": "function", "doc": "

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module.Module.run", "modulename": "smbclientng.core.Module", "qualname": "Module.run", "kind": "function", "doc": "

Placeholder method for running the module.

\n\n

This method should be implemented by subclasses to define the specific behavior of the module.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.Module.Module.processArguments", "modulename": "smbclientng.core.Module", "qualname": "Module.processArguments", "kind": "function", "doc": "

\n", "signature": "(self, parser, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.core.ModuleArgumentParser", "modulename": "smbclientng.core.ModuleArgumentParser", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.ModuleArgumentParser.ModuleArgumentParser", "modulename": "smbclientng.core.ModuleArgumentParser", "qualname": "ModuleArgumentParser", "kind": "class", "doc": "

A custom argument parser for handling module-specific command-line arguments in the smbclientng application.

\n\n

This class extends the argparse.ArgumentParser and provides custom error handling specific to the needs of smbclientng modules.\nIt is designed to provide clear and user-friendly command-line interfaces for various modules within the smbclientng suite.

\n\n

Attributes:\n None

\n\n

Methods:\n error(message: str):\n Overrides the default error handling to provide a more informative error message and display the help text.

\n", "bases": "argparse.ArgumentParser"}, {"fullname": "smbclientng.core.ModuleArgumentParser.ModuleArgumentParser.exit_on_error", "modulename": "smbclientng.core.ModuleArgumentParser", "qualname": "ModuleArgumentParser.exit_on_error", "kind": "variable", "doc": "

\n", "default_value": "False"}, {"fullname": "smbclientng.core.ModuleArgumentParser.ModuleArgumentParser.error", "modulename": "smbclientng.core.ModuleArgumentParser", "qualname": "ModuleArgumentParser.error", "kind": "function", "doc": "

Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display.

\n\n

This method is called when ArgumentParser encounters an error. It writes the error message to stderr,\ndisplays the help message, and then exits the program with a status code of 2.

\n\n

Args:\n message (str): The error message to be displayed.

\n", "signature": "(self, message):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession", "modulename": "smbclientng.core.SMBSession", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession", "kind": "class", "doc": "

Represents an SMB session for interacting with an SMB server.

\n\n

This class provides methods to manage and interact with an SMB server, including\nconnecting to the server, listing shares, uploading and downloading files, and\nmanaging directories and files on the server. It handles session initialization,\nauthentication, and cleanup.

\n\n

Attributes:\n host (str): The hostname or IP address of the SMB server.\n port (int): The port number on which the SMB server is listening.\n credentials (dict): Authentication credentials for the SMB server.\n config (dict, optional): Configuration options for the SMB session.\n smbClient (impacket.smbconnection.SMBConnection): The SMB connection instance.\n connected (bool): Connection status to the SMB server.\n available_shares (dict): A dictionary of available SMB shares.\n smb_share (str): The current SMB share in use.\n smb_cwd (str): The current working directory on the SMB share.\n smb_tree_id (int): The tree ID of the connected SMB share.

\n\n

Methods:\n close_smb_session(): Closes the current SMB session.\n init_smb_session(): Initializes the SMB session with the server.\n list_shares(): Lists all shares available on the SMB server.\n set_share(shareName): Sets the current SMB share.\n set_cwd(path): Sets the current working directory on the SMB share.\n put_file(localpath): Uploads a file to the current SMB share.\n get_file(remotepath, localpath): Downloads a file from the SMB share.\n mkdir(path): Creates a directory on the SMB share.\n rmdir(path): Removes a directory from the SMB share.\n rm(path): Removes a file from the SMB share.\n read_file(path): Reads a file from the SMB share.\n test_rights(sharename): Tests read and write access rights on a share.

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.__init__", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.__init__", "kind": "function", "doc": "

\n", "signature": "(host, port, credentials, config=None, logger=None)"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.config", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.logger", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.host", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.host", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.port", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.port", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.credentials", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.credentials", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smbClient", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smbClient", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.connected", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.connected", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.available_shares", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.available_shares", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_share", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_share", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_cwd", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_cwd", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.smb_tree_id", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.smb_tree_id", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.close_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.close_smb_session", "kind": "function", "doc": "

Closes the current SMB session by disconnecting the SMB client.

\n\n

This method ensures that the SMB client connection is properly closed. It checks if the client is connected\nand if so, it closes the connection and resets the connection status.

\n\n

Raises:\n Exception: If the SMB client is not initialized or if there's an error during the disconnection process.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.init_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.init_smb_session", "kind": "function", "doc": "

Initializes and establishes a session with the SMB server.

\n\n

This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.\nIt attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.

\n\n

The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.

\n\n

Returns:\n bool: True if the connection and authentication are successful, False otherwise.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.ping_smb_session", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.ping_smb_session", "kind": "function", "doc": "

Tests the connectivity to the SMB server by sending an echo command.

\n\n

This method attempts to send an echo command to the SMB server to check if the session is still active.\nIt updates the connected attribute of the class based on the success or failure of the echo command.

\n\n

Returns:\n bool: True if the echo command succeeds (indicating the session is active), False otherwise.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.find", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.find", "kind": "function", "doc": "

Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry.

\n\n

This method traverses the specified paths on the SMB share, recursively exploring directories and invoking the callback\nfunction on each file or directory found. The callback function is called with three arguments: the entry object, the\nfull path of the entry, and the current depth of recursion.

\n\n

Args:\n paths (list, optional): A list of paths to start the search from. Defaults to an empty list.\n callback (function, optional): A function to be called on each entry found. The function should accept three arguments:\n the entry object, the full path of the entry, and the current depth of recursion. Defaults to None.

\n\n

Note:\n If the callback function is None, the method will print an error message and return without performing any action.

\n", "signature": "(self, paths=[], callback=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_file", "kind": "function", "doc": "

Retrieves a file from the specified path on the SMB share.

\n\n

This method attempts to retrieve a file from the given path within the currently connected SMB share.\nIf the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local\nfile object and writing the contents of the remote file to it using the SMB client's getFile method.

\n\n

Parameters:\n path (str): The path of the file to retrieve. If None, uses the current smb_path.

\n\n

Returns:\n None

\n", "signature": "(self, path=None, keepRemotePath=False):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_file_recursively", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_file_recursively", "kind": "function", "doc": "

Recursively retrieves files from a specified path on the SMB share.

\n\n

This method navigates through all directories starting from the given path,\nand downloads all files found. It handles directories recursively, ensuring\nthat all nested files are retrieved. The method skips over directory entries\nand handles errors gracefully, attempting to continue the operation where possible.

\n\n

Parameters:\n path (str): The initial directory path from which to start the recursive file retrieval.\n If None, it starts from the root of the configured SMB share.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.get_entry", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.get_entry", "kind": "function", "doc": "

Retrieves information about a specific entry located at the provided path on the SMB share.

\n\n

This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.

\n\n

Args:\n path (str): The path of the entry to retrieve information about.

\n\n

Returns:\n Entry: An object representing the entry at the specified path, or None if the entry is not found.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.info", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.info", "kind": "function", "doc": "

Displays information about the server and optionally the shares.

\n\n

This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the share parameter is set to True and a share is currently set, it will also attempt to display information about the share.

\n\n

Parameters:\n share (bool): If True, display information about the current share.\n server (bool): If True, display information about the server.

\n\n

Returns:\n None

\n", "signature": "(self, share=True, server=True):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.list_contents", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.list_contents", "kind": "function", "doc": "

Lists the contents of a specified directory on the SMB share.

\n\n

This method retrieves the contents of a directory specified by shareName and path. If shareName or path\nis not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with\nthe long names of the files and directories as keys and their respective SMB entry objects as values.

\n\n

Args:\n shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.\n path (str, optional): The directory path to list contents from. Defaults to the current path if None.

\n\n

Returns:\n dict: A dictionary with file and directory names as keys and their SMB entry objects as values.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.list_shares", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.list_shares", "kind": "function", "doc": "

Lists all the shares available on the connected SMB server.

\n\n

This method queries the SMB server to retrieve a list of all available shares. It populates the shares dictionary\nwith key-value pairs where the key is the share name and the value is a dictionary containing details about the share\nsuch as its name, type, raw type, and any comments associated with the share.

\n\n

Returns:\n dict: A dictionary containing information about each share available on the server.

\n", "signature": "(self):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.mkdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.mkdir", "kind": "function", "doc": "

Creates a directory at the specified path on the SMB share.

\n\n

This method takes a path and attempts to create the directory structure on the SMB share. If the path includes\nnested directories, it will create each directory in the sequence. If a directory already exists, it will skip\nthe creation for that directory without raising an error.

\n\n

Args:\n path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.

\n\n

Note:\n The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.mount", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.mount", "kind": "function", "doc": "

Generates the command to mount an SMB share on different platforms.

\n\n

This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.\nIt constructs the mount command using the provided parameters and executes it using the os.system() function.

\n\n

Args:\n local_mount_point (str): The local directory where the SMB share will be mounted.\n remote_path (str): The remote path on the SMB share to be mounted.

\n\n

Note:\n - For Windows platform, the command uses 'net use' to mount the share.\n - For Linux platform, the command uses 'mount' to mount the share.\n - For macOS platform, the command uses 'mount_smbfs' to mount the share.\n - If the platform is not supported, an error message is displayed.

\n\n

Returns:\n None

\n", "signature": "(self, local_mount_point, remote_path):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_exists", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_exists", "kind": "function", "doc": "

Checks if the specified path exists on the SMB share.

\n\n

This method determines if a given path exists on the SMB share by attempting to list the contents of the path.\nIf the path listing is successful and returns one or more entries, the path is considered to exist.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path exists, False otherwise or if an error occurs.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_isdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_isdir", "kind": "function", "doc": "

Checks if the specified path is a directory on the SMB share.

\n\n

This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the\ncontents of the path and filtering for entries that match the basename of the path and are marked as directories.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path is a directory, False otherwise or if an error occurs.

\n", "signature": "(self, pathFromRoot=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.path_isfile", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.path_isfile", "kind": "function", "doc": "

Checks if the specified path is a file on the SMB share.

\n\n

This method determines if a given path corresponds to a file on the SMB share. It does this by listing the\ncontents of the path and filtering for entries that match the basename of the path and are not marked as directories.

\n\n

Args:\n path (str, optional): The path to check on the SMB share. Defaults to None.

\n\n

Returns:\n bool: True if the path is a file, False otherwise or if an error occurs.

\n", "signature": "(self, pathFromRoot=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.put_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.put_file", "kind": "function", "doc": "

Uploads a single file to the SMB share.

\n\n

This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.\nIt handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.\nGeneral exceptions are caught and logged, with a traceback provided if debugging is enabled.

\n\n

Args:\n localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.

\n", "signature": "(self, localpath=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.put_file_recursively", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.put_file_recursively", "kind": "function", "doc": "

Recursively uploads files from a specified local directory to the SMB share.

\n\n

This method walks through the given local directory and all its subdirectories, uploading each file to the\ncorresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,\nit iterates over all files and directories within the local path, creating necessary directories on the SMB share\nand uploading files. If the local path is not a directory, it prints an error message.

\n\n

Args:\n localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.

\n", "signature": "(self, localpath=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.read_file", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.read_file", "kind": "function", "doc": "

Reads a file from the SMB share.

\n\n

This method attempts to read the contents of a file specified by the path parameter from the SMB share.\nIt constructs the full path to the file, checks if the path is a valid file, and then reads the file content\ninto a byte stream which is returned to the caller.

\n\n

Args:\n path (str, optional): The path of the file to be read from the SMB share. Defaults to None.

\n\n

Returns:\n bytes: The content of the file as a byte stream, or None if the file does not exist or an error occurs.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.rmdir", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.rmdir", "kind": "function", "doc": "

Removes a directory from the SMB share at the specified path.

\n\n

This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,\nit prints an error message indicating the failure and the reason. If debugging is enabled, it also prints\nthe stack trace of the exception.

\n\n

Args:\n path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.rm", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.rm", "kind": "function", "doc": "

Removes a file from the SMB share at the specified path.

\n\n

This method attempts to delete a file located at the given path on the SMB share. If the operation fails,\nit prints an error message indicating the failure and the reason. If debugging is enabled, it also prints\nthe stack trace of the exception.

\n\n

Args:\n path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.tree", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.tree", "kind": "function", "doc": "

Recursively lists the directory structure of the SMB share starting from the specified path.

\n\n

This function prints a visual representation of the directory tree of the remote SMB share. It uses\nrecursion to navigate through directories and lists all files and subdirectories in each directory.\nThe output is color-coded and formatted to enhance readability, with directories highlighted in cyan.

\n\n

Args:\n path (str, optional): The starting path on the SMB share from which to begin listing the tree.\n Defaults to the root of the current share.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.umount", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.umount", "kind": "function", "doc": "

Unmounts the specified local mount point of the remote share.

\n\n

This method unmounts the specified local mount point of the remote share based on the platform.\nIt supports Windows, Linux, and macOS platforms for unmounting.

\n\n

Parameters:\n local_mount_point (str): The local mount point to unmount.

\n\n

Raises:\n None

\n", "signature": "(self, local_mount_point):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.test_rights", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.test_rights", "kind": "function", "doc": "

Tests the read and write access rights of the current SMB session.

\n\n

This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.

\n\n

Returns:\n dict: A dictionary containing the read and write access rights status.\n - \"readable\" (bool): Indicates if the session has read access rights.\n - \"writable\" (bool): Indicates if the session has write access rights.

\n", "signature": "(self, sharename):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.set_share", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.set_share", "kind": "function", "doc": "

Sets the current SMB share to the specified share name.

\n\n

This method updates the SMB session to use the specified share name. It checks if the share name is valid\nand updates the smb_share attribute of the SMBSession instance.

\n\n

Parameters:\n shareName (str): The name of the share to set as the current SMB share.

\n\n

Raises:\n ValueError: If the shareName is None or an empty string.

\n", "signature": "(self, shareName):", "funcdef": "def"}, {"fullname": "smbclientng.core.SMBSession.SMBSession.set_cwd", "modulename": "smbclientng.core.SMBSession", "qualname": "SMBSession.set_cwd", "kind": "function", "doc": "

Sets the current working directory on the SMB share to the specified path.

\n\n

This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.\nIf the specified path is not a directory, the cwd remains unchanged.

\n\n

Parameters:\n path (str): The path to set as the current working directory.

\n\n

Raises:\n ValueError: If the specified path is not a directory.

\n", "signature": "(self, path=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.SessionsManager", "modulename": "smbclientng.core.SessionsManager", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager", "kind": "class", "doc": "

A class to manage SMB sessions.

\n\n

This class is responsible for creating, managing, and switching between multiple SMB sessions. It allows for the creation of new sessions with specified credentials and hosts, and provides methods to switch between existing sessions. It also keeps track of the current session and its ID.

\n\n

Attributes:\n next_session_id (int): The next available session ID.\n current_session (SMBSession): The currently active SMB session.\n current_session_id (int): The ID of the currently active session.\n sessions (dict): A dictionary of all active sessions, keyed by their session ID.

\n"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.__init__", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.__init__", "kind": "function", "doc": "

\n", "signature": "(config, logger)"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.next_session_id", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.next_session_id", "kind": "variable", "doc": "

\n", "default_value": "1"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.current_session", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.current_session", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.current_session_id", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.current_session_id", "kind": "variable", "doc": "

\n", "default_value": "None"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.sessions", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.sessions", "kind": "variable", "doc": "

\n", "default_value": "{}"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.config", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.config", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.logger", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.logger", "kind": "variable", "doc": "

\n"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.create_new_session", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.create_new_session", "kind": "function", "doc": "

Creates a new session with the given session information.

\n\n

Args:\n session_info (dict): Information necessary to start a new session.

\n\n

Returns:\n None

\n", "signature": "(self, credentials, host, port=445):", "funcdef": "def"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.switch_session", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.switch_session", "kind": "function", "doc": "

Switches the current session to the session with the specified ID.

\n\n

Args:\n session_id (int): The ID of the session to switch to.

\n\n

Returns:\n bool: True if the session was successfully switched, False otherwise.

\n", "signature": "(self, session_id):", "funcdef": "def"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.delete_session", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.delete_session", "kind": "function", "doc": "

Deletes a session with the given session ID.

\n\n

Args:\n session_id (int): The ID of the session to delete.

\n\n

Returns:\n bool: True if the session was successfully deleted, False otherwise.

\n", "signature": "(self, session_id):", "funcdef": "def"}, {"fullname": "smbclientng.core.SessionsManager.SessionsManager.process_command_line", "modulename": "smbclientng.core.SessionsManager", "qualname": "SessionsManager.process_command_line", "kind": "function", "doc": "

Processes command line arguments to manage SMB sessions.

\n\n

This function parses the command line arguments provided to the application and determines the appropriate action to take,\nsuch as creating, interacting, deleting, or listing SMB sessions, or executing a command in one or more sessions.

\n\n

Args:\n arguments (list of str): The command line arguments.

\n\n

Returns:\n None

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils", "modulename": "smbclientng.core.utils", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.core.utils.parse_lm_nt_hashes", "modulename": "smbclientng.core.utils", "qualname": "parse_lm_nt_hashes", "kind": "function", "doc": "

Parse the input string containing LM and NT hash values and return them separately.

\n\n

This function takes a string containing LM and NT hash values, typically separated by a colon (:).\nIt returns the LM and NT hash values as separate strings. If only one hash value is provided, it is\nassumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are\nfound, both return values are empty strings.

\n\n

Args:\n lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon.

\n\n

Returns:\n tuple: A tuple containing two strings (lm_hash_value, nt_hash_value).\n - lm_hash_value: The LM hash value or its default if not provided.\n - nt_hash_value: The NT hash value or its default if not provided.

\n\n

Extracted from p0dalirius/sectools library\nSrc: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24

\n", "signature": "(lm_nt_hashes_string):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.b_filesize", "modulename": "smbclientng.core.utils", "qualname": "b_filesize", "kind": "function", "doc": "

Convert a file size from bytes to a more readable format using the largest appropriate unit.

\n\n

This function takes an integer representing a file size in bytes and converts it to a human-readable\nstring using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to\ntwo decimal places.

\n\n

Args:\n l (int): The file size in bytes.

\n\n

Returns:\n str: A string representing the file size in a more readable format, including the appropriate unit.

\n", "signature": "(l):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.unix_permissions", "modulename": "smbclientng.core.utils", "qualname": "unix_permissions", "kind": "function", "doc": "

Generate a string representing the Unix-style permissions for a given file or directory.

\n\n

This function uses the os.lstat() method to retrieve the status of the specified file or directory,\nthen constructs a string that represents the Unix-style permissions based on the mode of the file.

\n\n

Args:\n entryname (str): The path to the file or directory for which permissions are being determined.

\n\n

Returns:\n str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--').\n The first character is either 'd' (directory), '-' (not a directory), followed by\n three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group,\n and others respectively.

\n", "signature": "(entryname):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.STYPE_MASK", "modulename": "smbclientng.core.utils", "qualname": "STYPE_MASK", "kind": "function", "doc": "

Extracts the share type flags from a given share type value.

\n\n

This function uses bitwise operations to determine which share type flags are set in the provided stype_value.\nIt checks against known share type flags and returns a list of the flags that are set.

\n\n

Parameters:\n stype_value (int): The share type value to analyze, typically obtained from SMB share properties.

\n\n

Returns:\n list: A list of strings, where each string represents a share type flag that is set in the input value.

\n", "signature": "(stype_value):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.windows_ls_entry", "modulename": "smbclientng.core.utils", "qualname": "windows_ls_entry", "kind": "function", "doc": "

This function generates a metadata string based on the attributes of the provided entry object.

\n\n

Parameters:\n entry (object): An object representing a file or directory entry.

\n\n

Returns:\n str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary.

\n", "signature": "(entry, config, pathToPrint=None):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.local_tree", "modulename": "smbclientng.core.utils", "qualname": "local_tree", "kind": "function", "doc": "

This function recursively lists the contents of a directory in a tree-like format.

\n\n

Parameters:\n path (str): The path to the directory to list.\n config (object): Configuration settings which may affect the output, such as whether to use colors.

\n\n

Returns:\n None: This function does not return anything but prints the directory tree to the console.

\n", "signature": "(path, config):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.resolve_local_files", "modulename": "smbclientng.core.utils", "qualname": "resolve_local_files", "kind": "function", "doc": "

Resolves local file paths based on the provided arguments.

\n\n

This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual file paths.\nIf an argument contains a wildcard ('*'), it attempts to match files in the specified directory against the pattern.\nIf the argument does not contain a wildcard, it is added to the list of resolved files as is.

\n\n

Args:\n arguments (list): A list of file path arguments, which may include wildcard patterns.

\n\n

Returns:\n list: A list of resolved file paths that match the provided arguments.

\n", "signature": "(arguments):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.resolve_remote_files", "modulename": "smbclientng.core.utils", "qualname": "resolve_remote_files", "kind": "function", "doc": "

Resolves remote file paths based on the provided arguments using an SMB session.

\n\n

This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual remote file paths.\nIf an argument contains a wildcard ('*'), it attempts to match files in the specified remote directory against the pattern.\nIf the argument does not contain a wildcard, it is added to the list of resolved files as is.

\n\n

Args:\n smbsession (SMBSession): The SMB session through which to access the files.\n arguments (list): A list of file path arguments, which may include wildcard patterns.

\n\n

Returns:\n list: A list of resolved remote file paths that match the provided arguments.

\n", "signature": "(smbSession, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.core.utils.is_port_open", "modulename": "smbclientng.core.utils", "qualname": "is_port_open", "kind": "function", "doc": "

Check if a specific port on a target host is open.

\n\n

This function attempts to establish a TCP connection to the specified port on the target host.\nIf the connection is successful, it indicates that the port is open. If the connection fails,\nit indicates that the port is closed or the host is unreachable.

\n\n

Args:\n target (str): The hostname or IP address of the target host.\n port (int): The port number to check.

\n\n

Returns:\n bool: True if the port is open, False otherwise.

\n", "signature": "(target, port) -> bool:", "funcdef": "def"}, {"fullname": "smbclientng.modules", "modulename": "smbclientng.modules", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.Find", "modulename": "smbclientng.modules.Find", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.Find.Find", "modulename": "smbclientng.modules.Find", "qualname": "Find", "kind": "class", "doc": "

A class to search for files in a directory hierarchy.

\n\n

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

\n", "bases": "smbclientng.core.Module.Module"}, {"fullname": "smbclientng.modules.Find.Find.name", "modulename": "smbclientng.modules.Find", "qualname": "Find.name", "kind": "variable", "doc": "

\n", "default_value": "'find'"}, {"fullname": "smbclientng.modules.Find.Find.description", "modulename": "smbclientng.modules.Find", "qualname": "Find.description", "kind": "variable", "doc": "

\n", "default_value": "'Search for files in a directory hierarchy'"}, {"fullname": "smbclientng.modules.Find.Find.parseArgs", "modulename": "smbclientng.modules.Find", "qualname": "Find.parseArgs", "kind": "function", "doc": "

Parses the command line arguments provided to the module.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.Find.Find.run", "modulename": "smbclientng.modules.Find", "qualname": "Find.run", "kind": "function", "doc": "

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords", "modulename": "smbclientng.modules.GPPPasswords", "kind": "module", "doc": "

\n"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords", "kind": "class", "doc": "

GPPPasswords is a module designed to search and retrieve stored Group Policy Preferences (GPP) passwords from specified network shares. \nIt leverages the SMB protocol to access files across the network, parse them, and extract credentials that are often stored within Group Policy Preferences files.

\n\n

This module is particularly useful in penetration testing scenarios where discovering stored credentials can lead to further system access or reveal poor security practices.

\n\n

Attributes:\n name (str): The name of the module, used in command line invocation.\n description (str): A brief description of what the module does.

\n\n

Methods:\n parseArgs(arguments): Parses and handles command line arguments for the module.\n parse_xmlfile_content(pathtofile): Parses the content of an XML file to extract credentials.

\n", "bases": "smbclientng.core.Module.Module"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.name", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.name", "kind": "variable", "doc": "

\n", "default_value": "'gpppasswords'"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.description", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.description", "kind": "variable", "doc": "

\n", "default_value": "'Searches for Group Policy Preferences Passwords in a share.'"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.parseArgs", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.parseArgs", "kind": "function", "doc": "

Parses the command line arguments provided to the module.

\n\n

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

\n\n

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

\n\n

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

\n", "signature": "(self, arguments):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.parse_xmlfile_content", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.parse_xmlfile_content", "kind": "function", "doc": "

Parses the content of an XML file to extract credentials related to Group Policy Preferences.

\n\n

This method attempts to retrieve and parse the content of the specified XML file from the SMB share. It looks for credentials stored within the XML structure, specifically targeting the 'cpassword' attribute which is commonly used for storing encrypted passwords in Group Policy Preferences files.

\n\n

Args:\n pathtofile (str): The path to the XML file on the SMB share.

\n\n

Returns:\n list: A list of dictionaries, each containing details about found credentials such as username, encrypted and decrypted passwords, and other relevant attributes.

\n", "signature": "(self, pathtofile):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.decrypt_password", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.decrypt_password", "kind": "function", "doc": "

Decrypts a password from its Base64 encoded form using a known AES key and IV.

\n\n

This method takes a Base64 encoded string which is encrypted using AES-CBC with a fixed key and IV as per Microsoft's published details. It decodes the Base64 string, decrypts it using the AES key and IV, and returns the plaintext password.

\n\n

Args:\n pw_enc_b64 (str): The Base64 encoded string of the encrypted password.

\n\n

Returns:\n str: The decrypted password in plaintext, or an empty string if input is empty or decryption fails.

\n", "signature": "(self, pw_enc_b64):", "funcdef": "def"}, {"fullname": "smbclientng.modules.GPPPasswords.GPPPasswords.run", "modulename": "smbclientng.modules.GPPPasswords", "qualname": "GPPPasswords.run", "kind": "function", "doc": "

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

\n\n

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

\n\n

Returns:\n None

\n", "signature": "(self, arguments):", "funcdef": "def"}]; // mirrored in build-search-index.js (part 1) // Also split on html tags. this is a cheap heuristic, but good enough. diff --git a/documentation/smbclientng/core.html b/documentation/smbclientng/core.html index dc79d1e..de95d0d 100644 --- a/documentation/smbclientng/core.html +++ b/documentation/smbclientng/core.html @@ -31,11 +31,14 @@

Submodules

diff --git a/documentation/smbclientng/core/CommandCompleter.html b/documentation/smbclientng/core/CommandCompleter.html index 9a8ffe3..fc3cbd2 100644 --- a/documentation/smbclientng/core/CommandCompleter.html +++ b/documentation/smbclientng/core/CommandCompleter.html @@ -45,6 +45,9 @@

API Documentation

  • config
  • +
  • + logger +
  • complete
  • @@ -87,526 +90,589 @@

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

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

    - CommandCompleter(smbSession, config) + CommandCompleter(smbSession, config, logger)
    -
    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")
    +            
    329    def __init__(self, smbSession, config, logger):
    +330        # Objects
    +331        self.smbSession = smbSession
    +332        self.config = config
    +333        self.logger = logger
    +334        # Pre computing for some commands 
    +335        self.commands["help"]["subcommands"] = ["format"] + list(self.commands.keys())
    +336        self.commands["help"]["subcommands"].remove("help")
     
    @@ -1185,7 +1314,7 @@

    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': ['rights']}, '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': []}} + {'bat': {'description': ['Pretty prints the contents of a remote file.', "Syntax: 'bat <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'cat': {'description': ['Get the contents of a remote file.', "Syntax: 'cat <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'cd': {'description': ['Change the current working directory.', "Syntax: 'cd <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'close': {'description': ['Closes the SMB connection to the remote machine.', "Syntax: 'close'"], 'subcommands': [], 'autocomplete': []}, 'connect': {'description': ['Connect to the remote machine (useful if connection timed out).', "Syntax: 'connect'"], 'subcommands': [], 'autocomplete': []}, 'debug': {'description': ['Command for dev debugging.', "Syntax: 'debug'"], 'subcommands': [], 'autocomplete': []}, 'dir': {'description': ['List the contents of the current working directory.', "Syntax: 'dir'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'exit': {'description': ['Exits the smbclient-ng script.', "Syntax: 'exit'"], 'subcommands': [], 'autocomplete': []}, 'get': {'description': ['Get a remote file.', "Syntax: 'get [-r] <directory or file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'help': {'description': ['Displays this help message.', "Syntax: 'help'"], 'subcommands': ['format'], 'autocomplete': []}, 'info': {'description': ['Get information about the server and or the share.', "Syntax: 'info [server|share]'"], 'subcommands': ['server', 'share'], 'autocomplete': []}, 'lbat': {'description': ['Pretty prints the contents of a local file.', "Syntax: 'lbat <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lcat': {'description': ['Print the contents of a local file.', "Syntax: 'lcat <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lcd': {'description': ['Changes the current local directory.', "Syntax: 'lcd <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lcp': {'description': ['Create a copy of a local file.', "Syntax: 'lcp <srcfile> <dstfile>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'lls': {'description': ['Lists the contents of the current local directory.', "Syntax: 'lls'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lmkdir': {'description': ['Creates a new local directory.', "Syntax: 'lmkdir <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'lpwd': {'description': ['Shows the current local directory.', "Syntax: 'lpwd'"], 'subcommands': [], 'autocomplete': []}, 'lrename': {'description': ['Renames a local file.', "Syntax: 'lrename <oldfilename> <newfilename>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lrm': {'description': ['Removes a local file.', "Syntax: 'lrm <file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'lrmdir': {'description': ['Removes a local directory.', "Syntax: 'lrmdir <directory>'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'ls': {'description': ['List the contents of the current remote working directory.', "Syntax: 'ls'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'ltree': {'description': ['Displays a tree view of the local directories.', "Syntax: 'ltree [directory]'"], 'subcommands': [], 'autocomplete': ['local_directory']}, 'mkdir': {'description': ['Creates a new remote directory.', "Syntax: 'mkdir <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'module': {'description': ['Loads a specific module for additional functionalities.', "Syntax: 'module <name>'"], 'subcommands': [], 'autocomplete': []}, 'mount': {'description': ['Creates a mount point of the remote share on the local machine.', "Syntax: 'mount <remote_path> <local_mountpoint>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'put': {'description': ['Put a local file or directory in a remote directory.', "Syntax: 'put [-r] <directory or file>'"], 'subcommands': [], 'autocomplete': ['local_file']}, 'reconnect': {'description': ['Reconnect to the remote machine (useful if connection timed out).', "Syntax: 'reconnect'"], 'subcommands': [], 'autocomplete': []}, 'reset': {'description': ['Reset the TTY output, useful if it was broken after printing a binary file on stdout.', "Syntax: 'reset'"], 'subcommands': [], 'autocomplete': []}, 'rmdir': {'description': ['Removes a remote directory.', "Syntax: 'rmdir <directory>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'rm': {'description': ['Removes a remote file.', "Syntax: 'rm <file>'"], 'subcommands': [], 'autocomplete': ['remote_file']}, 'sizeof': {'description': ['Recursively compute the size of a folder.', "Syntax: 'sizeof [directory|file]'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'sessions': {'description': ['Manage the SMB sessions.', "Syntax: 'sessions [access|create|delete|execute|list]'"], 'subcommands': ['create', 'delete', 'execute', 'interact', 'list'], 'autocomplete': []}, 'shares': {'description': ['Lists the SMB shares served by the remote machine.', "Syntax: 'shares'"], 'subcommands': ['rights'], 'autocomplete': []}, 'tree': {'description': ['Displays a tree view of the remote directories.', "Syntax: 'tree [directory]'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'umount': {'description': ['Removes a mount point of the remote share on the local machine.', "Syntax: 'umount <local_mount_point>'"], 'subcommands': [], 'autocomplete': ['remote_directory']}, 'use': {'description': ['Use a SMB share.', "Syntax: 'use <sharename>'"], 'subcommands': [], 'autocomplete': ['share']}}
    @@ -1215,6 +1344,17 @@

    +

    +
    +
    + logger + + +
    + + + +
    @@ -1227,141 +1367,158 @@

    -
    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                            ]
    +            
    338    def complete(self, text, state):
    +339        """
    +340        Function to handle command completion in the LDAP console.
    +341
    +342        This function completes the user"s input based on the available options for commands in the LDAP console.
    +343
    +344        Args:
    +345            text (str): The current text input by the user.
    +346            state (int): The current state of completion.
    +347
    +348        Returns:
    +349            str: The next completion suggestion based on the user"s input state.
    +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)
    +352        if state == 0:
    +353            
    +354            # No text typed yet, need the list of commands available
    +355            if len(text) == 0:
    +356                self.matches = [s for s in self.commands.keys()]
    +357            
    +358            # Parsing a command
    +359            elif len(text) != 0:
    +360                # This is for the main command
    +361                if text.count(" ") == 0:
    +362                    self.matches = [s for s in self.commands.keys() if s and s.startswith(text)]
    +363                
    +364                # This is for subcommands
    +365                elif text.count(" ") >= 1:
    +366                    command, remainder = text.split(" ", 1)
    +367
    +368                    if command in self.commands.keys():
    +369                        self.matches = []
    +370
    +371                        # Autocomplete shares
    +372                        if "share" in self.commands[command]["autocomplete"]:
    +373                            # Choose SMB Share to connect to
    +374                            shares = self.smbSession.list_shares()
    +375                            matching_entries = []
    +376                            for sharename in shares.keys():
    +377                                if sharename.lower().startswith(remainder.lower()):
    +378                                    matching_entries.append(shares[sharename]["name"])
    +379                            # Final matches
    +380                            for m in matching_entries:
    +381                                self.matches.append(command + " " + shlex.quote(m))
    +382
    +383                        # Autocomplete directory
    +384                        if "remote_directory" in self.commands[command]["autocomplete"]:
    +385                            # Choose remote directory
    +386                            path = ""
    +387                            if '\\' in remainder.strip() or '/' in remainder.strip():
    +388                                path = remainder.strip().replace(ntpath.sep, '/')
    +389                                path = '/'.join(path.split('/')[:-1]) 
    +390                            # Get remote directory contents
    +391                            directory_contents = self.smbSession.list_contents(path=path).items()
    +392                            # 
     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
    +394                            for _, entry in directory_contents:
    +395                                if entry.is_directory() and entry.get_longname() not in [".",".."]:
    +396                                    if len(path) != 0:
    +397                                        matching_entries.append(path + '/' + entry.get_longname() + '/')
    +398                                    else:
    +399                                        matching_entries.append(entry.get_longname() + '/')
    +400                            #
    +401                            for m in matching_entries:
    +402                                if m.lower().startswith(remainder.lower()) or shlex.quote(m).lower().startswith(remainder.lower()):
    +403                                    self.matches.append(command + " " + shlex.quote(m))
    +404
    +405                        # Autocomplete file
    +406                        if "remote_file" in self.commands[command]["autocomplete"]:
    +407                            # Choose remote file
    +408                            path = ""
    +409                            if '\\' in remainder.strip() or '/' in remainder.strip():
    +410                                path = remainder.strip().replace(ntpath.sep, '/')
    +411                                path = '/'.join(path.split('/')[:-1])
    +412                            # Get remote directory contents
    +413                            directory_contents = self.smbSession.list_contents(path=path).items()
    +414                            # 
    +415                            matching_entries = []
    +416                            for _, entry in directory_contents:
    +417                                if (not entry.is_directory()) and entry.get_longname() not in [".",".."]:
    +418                                    if len(path) != 0:
    +419                                        matching_entries.append(path + '/' + entry.get_longname())
    +420                                    else:
    +421                                        matching_entries.append(entry.get_longname())
    +422                            # 
    +423                            for m in matching_entries:
    +424                                if m.lower().startswith(remainder.lower()) or shlex.quote(m).lower().startswith(remainder.lower()):
    +425                                    self.matches.append(command + " " + shlex.quote(m))
    +426
    +427                        # Autocomplete local_directory
    +428                        if "local_directory" in self.commands[command]["autocomplete"]:
    +429                            # Choose directory
    +430                            path = ""
    +431                            if os.path.sep in remainder.strip():
    +432                                path = path.split(os.path.sep)[:-1]
    +433                                path = os.path.sep.join(path)
    +434                            # Current dir
    +435                            if len(path.strip()) == 0:
    +436                                path = "."
    +437                            #
    +438                            directory_contents = os.listdir(path=path + os.path.sep)
    +439                            matching_entries = []
    +440                            for entry in directory_contents:
    +441                                if entry not in [".",".."]:
    +442                                    entry_path = path + os.path.sep + entry
    +443                                    if os.path.isdir(entry_path):
    +444                                        matching_entries.append(entry_path + os.path.sep)
    +445                            #
    +446                            for m in matching_entries:
    +447                                if m.lower().startswith(remainder.lower()) or shlex.quote(m).lower().startswith(remainder.lower()):
    +448                                    self.matches.append(command + " " + shlex.quote(m))
    +449
    +450                        # Autocomplete local_file
    +451                        if "local_file" in self.commands[command]["autocomplete"]:
    +452                            # Choose file
    +453                            path = ""
    +454                            if os.path.sep in remainder.strip():
    +455                                path = path.split(os.path.sep)[:-1]
    +456                                path = os.path.sep.join(path)
    +457                            # Current dir
    +458                            if len(path.strip()) == 0:
    +459                                path = "."
    +460                            # 
    +461                            directory_contents = os.listdir(path=(path + os.path.sep))
    +462                            matching_entries = []
    +463                            for entry in directory_contents:
    +464                                if entry not in [".",".."]:
    +465                                    entry_path = path + os.path.sep + entry
    +466                                    if not os.path.isdir(entry_path):
    +467                                        matching_entries.append(entry_path)
    +468                            # 
    +469                            for m in matching_entries:
    +470                                if m.lower().startswith(remainder.lower()) or shlex.quote(m).lower().startswith(remainder.lower()):
    +471                                    self.matches.append(command + " " + shlex.quote(m))
    +472
    +473                        else:
    +474                            # Generic case for subcommands
    +475                            for m in self.commands[command]["subcommands"]:
    +476                                if m.startswith(remainder):
    +477                                    self.matches.append(command + " " + m)
    +478                    else:
    +479                        # Unknown subcommand, skipping autocomplete
    +480                        pass
    +481                else:
    +482                    self.matches = []
    +483            else:
    +484                self.matches = self.commands.keys()[:]
    +485
    +486        try:
    +487            return self.matches[state] + " "
    +488        except IndexError:
    +489            return None
     
    @@ -1390,79 +1547,79 @@

    -
    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("│")
    +            
    491    def print_help(self, command=None):
    +492        """
    +493        Prints help information for a specific command or all commands if no command is specified.
    +494
    +495        This method displays the help information for the command passed as an argument. If no command is specified,
    +496        it prints the help information for all available commands. The help information includes the command syntax,
    +497        description, and any subcommands associated with it. This method is designed to provide users with the necessary
    +498        guidance on how to use the commands in the smbclient-ng shell.
    +499
    +500        Args:
    +501            command (str, optional): The command to display help information for. If None, help for all commands is displayed.
    +502
    +503        Returns:
    +504            None
    +505        """
    +506
    +507        if command is not None:
    +508            if command not in list(self.commands.keys())+["format"]:
    +509                command = None
    +510        
    +511        # Print help for a specific command
    +512        if command is not None:
    +513            if command == "format":
    +514                self.print_help_format()
    +515            else:
    +516                self.logger.print("│")
    +517                if self.config.no_colors:
    +518                    command_str = command + "─"* (15 - len(command))
    +519                    if len(self.commands[command]["description"]) == 0:
    +520                        self.logger.print("│ ■ %s┤  " % command_str)
    +521                    elif len(self.commands[command]["description"]) == 1:
    +522                        self.logger.print("│ ■ %s%s " % (command_str, self.commands[command]["description"][0]))
    +523                    else:
    +524                        self.logger.print("│ ■ %s%s " % (command_str, self.commands[command]["description"][0]))
    +525                        for line in self.commands[command]["description"][1:]:
    +526                            self.logger.print("│ %s%s " % (" "*(15+2), line))
    +527                else:
    +528                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
    +529                    if len(self.commands[command]["description"]) == 0:
    +530                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
    +531                    elif len(self.commands[command]["description"]) == 1:
    +532                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
    +533                    else:
    +534                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
    +535                        for line in self.commands[command]["description"][1:]:
    +536                            self.logger.print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
    +537                self.logger.print("│")
    +538        # Generic help
    +539        else:
    +540            self.logger.print("│")
    +541            commands = sorted(self.commands.keys())
    +542            for command in commands:
    +543                if self.config.no_colors:
    +544                    command_str = command + "─"* (15 - len(command))
    +545                    if len(self.commands[command]["description"]) == 0:
    +546                        self.logger.print("│ ■ %s┤  " % command_str)
    +547                    elif len(self.commands[command]["description"]) == 1:
    +548                        self.logger.print("│ ■ %s%s " % (command_str, self.commands[command]["description"][0]))
    +549                    else:
    +550                        self.logger.print("│ ■ %s%s " % (command_str, self.commands[command]["description"][0]))
    +551                        for line in self.commands[command]["description"][1:]:
    +552                            self.logger.print("│ %s%s " % (" "*(15+2), line))
    +553                else:
    +554                    command_str = command + " \x1b[90m" + "─"* (15 - len(command)) + "\x1b[0m"
    +555                    if len(self.commands[command]["description"]) == 0:
    +556                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m  " % command_str)
    +557                    elif len(self.commands[command]["description"]) == 1:
    +558                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
    +559                    else:
    +560                        self.logger.print("│ ■ %s\x1b[90m┤\x1b[0m %s " % (command_str, self.commands[command]["description"][0]))
    +561                        for line in self.commands[command]["description"][1:]:
    +562                            self.logger.print("│ %s\x1b[90m│\x1b[0m %s " % (" "*(15+3), line))
    +563                self.logger.print("│")
     
    @@ -1493,35 +1650,35 @@

    -
    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        if self.config.no_colors:
    -510            print("File attributes format:\n")
    -511            print("dachnrst")
    -512            print("│││││││└──> Temporary")
    -513            print("││││││└───> System")
    -514            print("│││││└────> Read-Only")
    -515            print("││││└─────> Normal")
    -516            print("│││└──────> Hidden")
    -517            print("││└───────> Compressed")
    -518            print("│└────────> Archived")
    -519            print("└─────────> Directory")
    -520        else:
    -521            print("File attributes format:\n")
    -522            print("dachnrst")
    -523            print("\x1b[90m│││││││└──>\x1b[0m Temporary")
    -524            print("\x1b[90m││││││└───>\x1b[0m System")
    -525            print("\x1b[90m│││││└────>\x1b[0m Read-Only")
    -526            print("\x1b[90m││││└─────>\x1b[0m Normal")
    -527            print("\x1b[90m│││└──────>\x1b[0m Hidden")
    -528            print("\x1b[90m││└───────>\x1b[0m Compressed")
    -529            print("\x1b[90m│└────────>\x1b[0m Archived")
    -530            print("\x1b[90m└─────────>\x1b[0m Directory")
    +            
    565    def print_help_format(self):
    +566        """
    +567        Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.
    +568
    +569        This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning
    +570        of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.
    +571        """
    +572        if self.config.no_colors:
    +573            self.logger.print("File attributes format:\n")
    +574            self.logger.print("dachnrst")
    +575            self.logger.print("│││││││└──> Temporary")
    +576            self.logger.print("││││││└───> System")
    +577            self.logger.print("│││││└────> Read-Only")
    +578            self.logger.print("││││└─────> Normal")
    +579            self.logger.print("│││└──────> Hidden")
    +580            self.logger.print("││└───────> Compressed")
    +581            self.logger.print("│└────────> Archived")
    +582            self.logger.print("└─────────> Directory")
    +583        else:
    +584            self.logger.print("File attributes format:\n")
    +585            self.logger.print("dachnrst")
    +586            self.logger.print("\x1b[90m│││││││└──>\x1b[0m Temporary")
    +587            self.logger.print("\x1b[90m││││││└───>\x1b[0m System")
    +588            self.logger.print("\x1b[90m│││││└────>\x1b[0m Read-Only")
    +589            self.logger.print("\x1b[90m││││└─────>\x1b[0m Normal")
    +590            self.logger.print("\x1b[90m│││└──────>\x1b[0m Hidden")
    +591            self.logger.print("\x1b[90m││└───────>\x1b[0m Compressed")
    +592            self.logger.print("\x1b[90m│└────────>\x1b[0m Archived")
    +593            self.logger.print("\x1b[90m└─────────>\x1b[0m Directory")
     
    diff --git a/documentation/smbclientng/core/Config.html b/documentation/smbclientng/core/Config.html index 1ee9edb..a3fe8a1 100644 --- a/documentation/smbclientng/core/Config.html +++ b/documentation/smbclientng/core/Config.html @@ -36,6 +36,12 @@

    API Documentation

  • Config
  • +
  • + not_interactive +
  • +
  • + startup_script +
  • debug
  • @@ -91,38 +97,41 @@

    23 no_colors: Property to get or set the colored output preference. 24 """ 25 -26 def __init__(self, debug=False, no_colors=None): -27 self._debug = debug +26 not_interactive = False +27 startup_script = None 28 -29 if no_colors is not None: -30 self._no_colors = no_colors -31 else: -32 if platform.system() == "Windows": -33 self._no_colors = False -34 else: -35 self._no_colors = True -36 -37 @property -38 def debug(self): -39 return self._debug -40 -41 @debug.setter -42 def debug(self, value): -43 if isinstance(value, bool): -44 self._debug = value -45 else: -46 raise ValueError("Debug must be a boolean value") -47 -48 @property -49 def no_colors(self): -50 return self._no_colors -51 -52 @no_colors.setter -53 def no_colors(self, value): -54 if isinstance(value, bool): -55 self._no_colors = value -56 else: -57 raise ValueError("Colored output must be a boolean value") +29 def __init__(self, debug=False, no_colors=None): +30 self._debug = debug +31 +32 if no_colors is not None: +33 self._no_colors = no_colors +34 else: +35 if platform.system() == "Windows": +36 self._no_colors = False +37 else: +38 self._no_colors = True +39 +40 @property +41 def debug(self): +42 return self._debug +43 +44 @debug.setter +45 def debug(self, value): +46 if isinstance(value, bool): +47 self._debug = value +48 else: +49 raise ValueError("Debug must be a boolean value") +50 +51 @property +52 def no_colors(self): +53 return self._no_colors +54 +55 @no_colors.setter +56 def no_colors(self, value): +57 if isinstance(value, bool): +58 self._no_colors = value +59 else: +60 raise ValueError("Colored output must be a boolean value")

    @@ -154,38 +163,41 @@

    24 no_colors: Property to get or set the colored output preference. 25 """ 26 -27 def __init__(self, debug=False, no_colors=None): -28 self._debug = debug +27 not_interactive = False +28 startup_script = None 29 -30 if no_colors is not None: -31 self._no_colors = no_colors -32 else: -33 if platform.system() == "Windows": -34 self._no_colors = False -35 else: -36 self._no_colors = True -37 -38 @property -39 def debug(self): -40 return self._debug -41 -42 @debug.setter -43 def debug(self, value): -44 if isinstance(value, bool): -45 self._debug = value -46 else: -47 raise ValueError("Debug must be a boolean value") -48 -49 @property -50 def no_colors(self): -51 return self._no_colors -52 -53 @no_colors.setter -54 def no_colors(self, value): -55 if isinstance(value, bool): -56 self._no_colors = value -57 else: -58 raise ValueError("Colored output must be a boolean value") +30 def __init__(self, debug=False, no_colors=None): +31 self._debug = debug +32 +33 if no_colors is not None: +34 self._no_colors = no_colors +35 else: +36 if platform.system() == "Windows": +37 self._no_colors = False +38 else: +39 self._no_colors = True +40 +41 @property +42 def debug(self): +43 return self._debug +44 +45 @debug.setter +46 def debug(self, value): +47 if isinstance(value, bool): +48 self._debug = value +49 else: +50 raise ValueError("Debug must be a boolean value") +51 +52 @property +53 def no_colors(self): +54 return self._no_colors +55 +56 @no_colors.setter +57 def no_colors(self, value): +58 if isinstance(value, bool): +59 self._no_colors = value +60 else: +61 raise ValueError("Colored output must be a boolean value")

    @@ -214,21 +226,45 @@

    -
    27    def __init__(self, debug=False, no_colors=None):
    -28        self._debug = debug
    -29
    -30        if no_colors is not None:
    -31            self._no_colors = no_colors
    -32        else:
    -33            if platform.system() == "Windows":
    -34                self._no_colors = False
    -35            else:
    -36                self._no_colors = True
    +            
    30    def __init__(self, debug=False, no_colors=None):
    +31        self._debug = debug
    +32
    +33        if no_colors is not None:
    +34            self._no_colors = no_colors
    +35        else:
    +36            if platform.system() == "Windows":
    +37                self._no_colors = False
    +38            else:
    +39                self._no_colors = True
     
    +
    +
    +
    + not_interactive = +False + + +
    + + + + +
    +
    +
    + startup_script = +None + + +
    + + + +
    @@ -239,9 +275,9 @@

    -
    38    @property
    -39    def debug(self):
    -40        return self._debug
    +            
    41    @property
    +42    def debug(self):
    +43        return self._debug
     
    @@ -257,9 +293,9 @@

    -
    49    @property
    -50    def no_colors(self):
    -51        return self._no_colors
    +            
    52    @property
    +53    def no_colors(self):
    +54        return self._no_colors
     
    diff --git a/documentation/smbclientng/core/Credentials.html b/documentation/smbclientng/core/Credentials.html new file mode 100644 index 0000000..a8121a8 --- /dev/null +++ b/documentation/smbclientng/core/Credentials.html @@ -0,0 +1,776 @@ + + + + + + + smbclientng.core.Credentials API documentation + + + + + + + + + +
    +
    +

    +smbclientng.core.Credentials

    + + + + + + +
      1#!/usr/bin/env python3
    +  2# -*- coding: utf-8 -*-
    +  3# File name          : Credentials.py
    +  4# Author             : Podalirius (@podalirius_)
    +  5# Date created       : 22 June 2024
    +  6
    +  7
    +  8from smbclientng.core.utils import parse_lm_nt_hashes
    +  9import re
    + 10import binascii
    + 11 
    + 12
    + 13class Credentials(object):
    + 14    """
    + 15    Documentation for class Credentials
    + 16    """
    + 17
    + 18    # Identity
    + 19    domain = None
    + 20    username = None
    + 21    password = None
    + 22    # Hashes
    + 23    nt_hex = ""
    + 24    nt_raw = ""
    + 25    lm_hex = ""
    + 26    lm_raw = ""
    + 27    # Kerberos
    + 28    use_kerberos = False
    + 29    aesKey = None
    + 30    kdcHost = None
    + 31
    + 32    def __init__(self, domain, username, password, hashes=None, use_kerberos=False, aesKey=None, kdcHost=None):
    + 33        super(Credentials, self).__init__()
    + 34        # Identity
    + 35        self.domain = domain
    + 36        self.username = username
    + 37        self.password = password
    + 38        # Hashes
    + 39        self.nt_hex = ""
    + 40        self.nt_raw = ""
    + 41        self.lm_hex = ""
    + 42        self.lm_raw = ""
    + 43        self.set_hashes(hashes=hashes)
    + 44        # Kerberos
    + 45        self.use_kerberos = use_kerberos
    + 46        self.kdcHost = kdcHost
    + 47        self.aesKey = aesKey
    + 48
    + 49    def set_hashes(self, hashes):
    + 50        """
    + 51        Sets the LM and NT hashes for the credentials.
    + 52
    + 53        This method parses the provided hash string and sets the LM and NT hash values accordingly.
    + 54        If the hash string is valid and contains both LM and NT hashes, they are set directly.
    + 55        If only one hash is provided, the other is set to its default value.
    + 56        If the hash string is None or invalid, both hashes are set to None.
    + 57
    + 58        Args:
    + 59            hashes (str): A string containing LM and NT hashes separated by a colon.
    + 60        """
    + 61
    + 62        self.nt_hex = ""
    + 63        self.nt_raw = ""
    + 64        self.lm_hex = ""
    + 65        self.lm_raw = ""
    + 66
    + 67        lmhash, nthash = None, None
    + 68        if hashes is not None:
    + 69            matched = re.search("([0-9a-f]{32})(:)?([0-9a-f]{32})?", hashes.lower(), re.IGNORECASE)
    + 70            if matched is not None:
    + 71                lmhash = matched.groups()[0]
    + 72                nthash = matched.groups()[2]
    + 73                if lmhash is None:
    + 74                    lmhash = "aad3b435b51404eeaad3b435b51404ee"
    + 75                if nthash is None:
    + 76                    nthash = "31d6cfe0d16ae931b73c59d7e0c089c0"
    + 77                self.lm_hex = lmhash
    + 78                self.lm_raw = binascii.unhexlify(lmhash)
    + 79                self.nt_hex = nthash
    + 80                self.nt_raw = binascii.unhexlify(nthash)
    + 81
    + 82    def canPassTheHash(self):
    + 83        """
    + 84        Determines if the current credentials can be used for a pass-the-hash attack.
    + 85
    + 86        This method checks if both LM and NT hashes are available and not None. If both hashes are set,
    + 87        it indicates that the credentials may be used for a pass-the-hash attack.
    + 88
    + 89        Returns:
    + 90            bool: True if both LM and NT hashes are available, False otherwise.
    + 91        """
    + 92
    + 93        return bool(
    + 94            (self.nt_hex is not None)
    + 95            and (self.nt_raw is not None)
    + 96            and (self.lm_hex is not None)
    + 97            and (self.lm_raw is not None)
    + 98        )
    + 99
    +100    def __dict__(self):
    +101        return {
    +102            "domain": self.domain,
    +103            "username": self.username,
    +104            "password": self.password,
    +105            "hashes": {
    +106                "lm_hash": self.lm_hex,
    +107                "nt_hash": self.nt_hex
    +108            },
    +109            "use_kerberos": self.use_kerberos,
    +110            "aesKey": self.aesKey,
    +111            "kdcHost": self.kdcHost
    +112        }
    +113    
    +114    def __repr__(self):
    +115        return f"<Credentials for '{self.domain}\\{self.username}'>"
    +
    + + +
    +
    + +
    + + class + Credentials: + + + +
    + +
     14class Credentials(object):
    + 15    """
    + 16    Documentation for class Credentials
    + 17    """
    + 18
    + 19    # Identity
    + 20    domain = None
    + 21    username = None
    + 22    password = None
    + 23    # Hashes
    + 24    nt_hex = ""
    + 25    nt_raw = ""
    + 26    lm_hex = ""
    + 27    lm_raw = ""
    + 28    # Kerberos
    + 29    use_kerberos = False
    + 30    aesKey = None
    + 31    kdcHost = None
    + 32
    + 33    def __init__(self, domain, username, password, hashes=None, use_kerberos=False, aesKey=None, kdcHost=None):
    + 34        super(Credentials, self).__init__()
    + 35        # Identity
    + 36        self.domain = domain
    + 37        self.username = username
    + 38        self.password = password
    + 39        # Hashes
    + 40        self.nt_hex = ""
    + 41        self.nt_raw = ""
    + 42        self.lm_hex = ""
    + 43        self.lm_raw = ""
    + 44        self.set_hashes(hashes=hashes)
    + 45        # Kerberos
    + 46        self.use_kerberos = use_kerberos
    + 47        self.kdcHost = kdcHost
    + 48        self.aesKey = aesKey
    + 49
    + 50    def set_hashes(self, hashes):
    + 51        """
    + 52        Sets the LM and NT hashes for the credentials.
    + 53
    + 54        This method parses the provided hash string and sets the LM and NT hash values accordingly.
    + 55        If the hash string is valid and contains both LM and NT hashes, they are set directly.
    + 56        If only one hash is provided, the other is set to its default value.
    + 57        If the hash string is None or invalid, both hashes are set to None.
    + 58
    + 59        Args:
    + 60            hashes (str): A string containing LM and NT hashes separated by a colon.
    + 61        """
    + 62
    + 63        self.nt_hex = ""
    + 64        self.nt_raw = ""
    + 65        self.lm_hex = ""
    + 66        self.lm_raw = ""
    + 67
    + 68        lmhash, nthash = None, None
    + 69        if hashes is not None:
    + 70            matched = re.search("([0-9a-f]{32})(:)?([0-9a-f]{32})?", hashes.lower(), re.IGNORECASE)
    + 71            if matched is not None:
    + 72                lmhash = matched.groups()[0]
    + 73                nthash = matched.groups()[2]
    + 74                if lmhash is None:
    + 75                    lmhash = "aad3b435b51404eeaad3b435b51404ee"
    + 76                if nthash is None:
    + 77                    nthash = "31d6cfe0d16ae931b73c59d7e0c089c0"
    + 78                self.lm_hex = lmhash
    + 79                self.lm_raw = binascii.unhexlify(lmhash)
    + 80                self.nt_hex = nthash
    + 81                self.nt_raw = binascii.unhexlify(nthash)
    + 82
    + 83    def canPassTheHash(self):
    + 84        """
    + 85        Determines if the current credentials can be used for a pass-the-hash attack.
    + 86
    + 87        This method checks if both LM and NT hashes are available and not None. If both hashes are set,
    + 88        it indicates that the credentials may be used for a pass-the-hash attack.
    + 89
    + 90        Returns:
    + 91            bool: True if both LM and NT hashes are available, False otherwise.
    + 92        """
    + 93
    + 94        return bool(
    + 95            (self.nt_hex is not None)
    + 96            and (self.nt_raw is not None)
    + 97            and (self.lm_hex is not None)
    + 98            and (self.lm_raw is not None)
    + 99        )
    +100
    +101    def __dict__(self):
    +102        return {
    +103            "domain": self.domain,
    +104            "username": self.username,
    +105            "password": self.password,
    +106            "hashes": {
    +107                "lm_hash": self.lm_hex,
    +108                "nt_hash": self.nt_hex
    +109            },
    +110            "use_kerberos": self.use_kerberos,
    +111            "aesKey": self.aesKey,
    +112            "kdcHost": self.kdcHost
    +113        }
    +114    
    +115    def __repr__(self):
    +116        return f"<Credentials for '{self.domain}\\{self.username}'>"
    +
    + + +

    Documentation for class Credentials

    +
    + + +
    + +
    + + Credentials( domain, username, password, hashes=None, use_kerberos=False, aesKey=None, kdcHost=None) + + + +
    + +
    33    def __init__(self, domain, username, password, hashes=None, use_kerberos=False, aesKey=None, kdcHost=None):
    +34        super(Credentials, self).__init__()
    +35        # Identity
    +36        self.domain = domain
    +37        self.username = username
    +38        self.password = password
    +39        # Hashes
    +40        self.nt_hex = ""
    +41        self.nt_raw = ""
    +42        self.lm_hex = ""
    +43        self.lm_raw = ""
    +44        self.set_hashes(hashes=hashes)
    +45        # Kerberos
    +46        self.use_kerberos = use_kerberos
    +47        self.kdcHost = kdcHost
    +48        self.aesKey = aesKey
    +
    + + + + +
    +
    +
    + domain = +None + + +
    + + + + +
    +
    +
    + username = +None + + +
    + + + + +
    +
    +
    + password = +None + + +
    + + + + +
    +
    +
    + nt_hex = +'' + + +
    + + + + +
    +
    +
    + nt_raw = +'' + + +
    + + + + +
    +
    +
    + lm_hex = +'' + + +
    + + + + +
    +
    +
    + lm_raw = +'' + + +
    + + + + +
    +
    +
    + use_kerberos = +False + + +
    + + + + +
    +
    +
    + aesKey = +None + + +
    + + + + +
    +
    +
    + kdcHost = +None + + +
    + + + + +
    +
    + +
    + + def + set_hashes(self, hashes): + + + +
    + +
    50    def set_hashes(self, hashes):
    +51        """
    +52        Sets the LM and NT hashes for the credentials.
    +53
    +54        This method parses the provided hash string and sets the LM and NT hash values accordingly.
    +55        If the hash string is valid and contains both LM and NT hashes, they are set directly.
    +56        If only one hash is provided, the other is set to its default value.
    +57        If the hash string is None or invalid, both hashes are set to None.
    +58
    +59        Args:
    +60            hashes (str): A string containing LM and NT hashes separated by a colon.
    +61        """
    +62
    +63        self.nt_hex = ""
    +64        self.nt_raw = ""
    +65        self.lm_hex = ""
    +66        self.lm_raw = ""
    +67
    +68        lmhash, nthash = None, None
    +69        if hashes is not None:
    +70            matched = re.search("([0-9a-f]{32})(:)?([0-9a-f]{32})?", hashes.lower(), re.IGNORECASE)
    +71            if matched is not None:
    +72                lmhash = matched.groups()[0]
    +73                nthash = matched.groups()[2]
    +74                if lmhash is None:
    +75                    lmhash = "aad3b435b51404eeaad3b435b51404ee"
    +76                if nthash is None:
    +77                    nthash = "31d6cfe0d16ae931b73c59d7e0c089c0"
    +78                self.lm_hex = lmhash
    +79                self.lm_raw = binascii.unhexlify(lmhash)
    +80                self.nt_hex = nthash
    +81                self.nt_raw = binascii.unhexlify(nthash)
    +
    + + +

    Sets the LM and NT hashes for the credentials.

    + +

    This method parses the provided hash string and sets the LM and NT hash values accordingly. +If the hash string is valid and contains both LM and NT hashes, they are set directly. +If only one hash is provided, the other is set to its default value. +If the hash string is None or invalid, both hashes are set to None.

    + +

    Args: + hashes (str): A string containing LM and NT hashes separated by a colon.

    +
    + + +
    +
    + +
    + + def + canPassTheHash(self): + + + +
    + +
    83    def canPassTheHash(self):
    +84        """
    +85        Determines if the current credentials can be used for a pass-the-hash attack.
    +86
    +87        This method checks if both LM and NT hashes are available and not None. If both hashes are set,
    +88        it indicates that the credentials may be used for a pass-the-hash attack.
    +89
    +90        Returns:
    +91            bool: True if both LM and NT hashes are available, False otherwise.
    +92        """
    +93
    +94        return bool(
    +95            (self.nt_hex is not None)
    +96            and (self.nt_raw is not None)
    +97            and (self.lm_hex is not None)
    +98            and (self.lm_raw is not None)
    +99        )
    +
    + + +

    Determines if the current credentials can be used for a pass-the-hash attack.

    + +

    This method checks if both LM and NT hashes are available and not None. If both hashes are set, +it indicates that the credentials may be used for a pass-the-hash attack.

    + +

    Returns: + bool: True if both LM and NT hashes are available, False otherwise.

    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/documentation/smbclientng/core/InteractiveShell.html b/documentation/smbclientng/core/InteractiveShell.html index 62b6897..181a535 100644 --- a/documentation/smbclientng/core/InteractiveShell.html +++ b/documentation/smbclientng/core/InteractiveShell.html @@ -46,22 +46,28 @@

    API Documentation

    InteractiveShell
  • - smbSession + running +
  • +
  • + modules +
  • +
  • + sessionsManager
  • config
  • - commandCompleterObject + logger
  • - modules + commandCompleterObject
  • run
  • - process_command + process_line
  • command_debug @@ -87,6 +93,9 @@

    API Documentation

  • command_info
  • +
  • + command_lbat +
  • command_lcat
  • @@ -96,9 +105,6 @@

    API Documentation

  • command_lcp
  • -
  • - command_lbat -
  • command_lls
  • @@ -186,1002 +192,1148 @@

    -
      1#!/usr/bin/env python3
    -  2# -*- coding: utf-8 -*-
    -  3# File name          : InteractiveShell.py
    -  4# Author             : Podalirius (@podalirius_)
    -  5# Date created       : 23 may 2024
    -  6
    -  7
    -  8import charset_normalizer
    -  9import datetime
    - 10import impacket
    - 11from importlib import import_module
    - 12import ntpath
    - 13import os
    - 14import readline
    - 15import shutil
    - 16import sys
    - 17import traceback
    - 18from rich.console import Console
    - 19from rich.table import Table
    - 20from rich.syntax import Syntax
    - 21from smbclientng.core.CommandCompleter import CommandCompleter
    - 22from smbclientng.core.utils import b_filesize, unix_permissions, windows_ls_entry, local_tree
    - 23
    - 24
    - 25## Decorators
    - 26
    - 27def command_arguments_required(func):
    - 28    def wrapper(*args, **kwargs):
    - 29        self, arguments,command  = args[0], args[1], args[2]
    - 30        if len(arguments) != 0:
    - 31            return func(*args, **kwargs)
    - 32        else:
    - 33            self.commandCompleterObject.print_help(command=command)
    - 34            return None
    - 35    return wrapper
    - 36
    - 37def active_smb_connection_needed(func):
    - 38    def wrapper(*args, **kwargs):
    - 39        self, arguments,command  = args[0], args[1], args[2]
    - 40        #
    - 41        self.smbSession.ping_smb_session()
    - 42        if self.smbSession.connected:
    - 43            return func(*args, **kwargs)
    - 44        else:
    - 45            print("[!] SMB Session is disconnected.")
    - 46            return None
    - 47    return wrapper
    - 48
    - 49def smb_share_is_set(func):
    - 50    def wrapper(*args, **kwargs):
    - 51        self, arguments,command  = args[0], args[1], args[2]
    - 52        if self.smbSession.smb_share is not None:
    - 53            return func(*args, **kwargs)
    - 54        else:
    - 55            print("[!] You must open a share first, try the 'use <share>' command.")
    - 56            return None
    - 57    return wrapper
    - 58
    - 59
    - 60class InteractiveShell(object):
    - 61    """
    - 62    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
    - 63    
    - 64    This class handles user input, executes commands, and manages the state of the SMB session. It provides
    - 65    a command line interface for users to interact with SMB shares, execute commands like directory listing,
    - 66    file transfer, and more.
    - 67
    - 68    Attributes:
    - 69        smbSession (SMBConnection): The active SMB connection session.
    - 70        debug (bool): Flag to enable or disable debug mode.
    - 71        smb_share (str): The current SMB share in use.
    - 72        smb_path (str): The current path within the SMB share.
    - 73        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
    - 74
    - 75    Methods:
    - 76        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
    - 77        run(self): Starts the command line interface loop, processing user input until exit.
    - 78    """
    - 79    
    - 80    def __init__(self, smbSession, config):
    - 81        # Objects
    - 82        self.smbSession = smbSession
    - 83        self.config = config
    - 84        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
    - 85        readline.set_completer(self.commandCompleterObject.complete)
    - 86        readline.parse_and_bind("tab: complete")
    - 87        readline.set_completer_delims("\n")
    - 88        # Additional modules
    - 89        self.modules = {}
    - 90        self.__load_modules()
    - 91
    - 92    def run(self):
    - 93        running = True
    - 94        while running:
    - 95            try:
    - 96                user_input = input(self.__prompt()).strip().split(" ")
    - 97                command, arguments = user_input[0].lower(), user_input[1:]
    - 98                
    - 99                # Exit the command line
    -100                if command == "exit":
    -101                    running = False
    -102
    -103                elif command in self.commandCompleterObject.commands.keys():
    -104                    self.process_command(
    -105                        command=command, 
    -106                        arguments=arguments
    -107                    )
    -108
    -109                elif command.strip() == "":
    -110                    pass
    -111
    -112                # Fallback to unknown command
    -113                else:
    -114                    print("Unknown command. Type \"help\" for help.")
    -115
    -116            except KeyboardInterrupt as e:
    -117                print()
    -118
    -119            except EOFError as e:
    -120                print()
    -121                running = False
    -122
    -123            except Exception as e:
    -124                if self.config.debug:
    -125                    traceback.print_exc()
    -126                print("[!] Error: %s" % str(e))
    -127
    -128    def process_command(self, command, arguments=[]):
    -129        # Skip
    -130        if command.strip() == "":
    -131            pass
    -132        
    -133        # Display help
    -134        elif command == "help":
    -135            self.command_help(arguments, command)
    -136
    -137        # Cat the contents of a file
    -138        elif command == "bat":
    -139            self.command_bat(arguments, command)
    -140
    -141        # Cat the contents of a file
    -142        elif command == "cat":
    -143            self.command_cat(arguments, command)
    -144
    -145        # Closes the current SMB session
    -146        elif command == "close":
    -147            self.command_close(arguments, command)
    -148               
    -149        # Change directory in the current share
    -150        elif command == "cd":
    -151            self.command_cd(arguments, command)
    -152        
    -153        # debug
    -154        elif command == "debug":
    -155            self.command_debug(arguments, command)
    -156
    -157        # Get a file
    -158        elif command == "get":
    -159            self.command_get(arguments, command)
    -160
    -161        # SMB server info
    -162        elif command == "info":
    -163            self.command_info(arguments, command)
    -164
    -165        # List directory contents in a share
    -166        elif command in ["ls", "dir"]:
    -167            self.command_ls(arguments, command)
    -168
    -169        # Creates a new remote directory
    -170        elif command == "mkdir":
    -171            self.command_mkdir(arguments, command)
    -172
    -173        # Put a file
    -174        elif command == "put":
    -175            self.command_put(arguments, command)
    -176
    -177        # Shows the content of a local file
    -178        elif command == "lcat":
    -179            self.command_lcat(arguments, command)
    -180
    -181        # Changes the current local directory
    -182        elif command == "lcd":
    -183            self.command_lcd(arguments, command)
    -184
    -185        # Creates a copy of a local file
    -186        elif command == "lcp":
    -187            self.command_lcp(arguments, command)
    -188
    -189        # Pretty prints the content of a local file
    -190        elif command == "lbat":
    -191            self.command_lbat(arguments, command)
    -192
    -193        # Lists the contents of the current local directory
    -194        elif command == "lls":
    -195            self.command_lls(arguments, command)
    -196
    -197        # Creates a new local directory
    -198        elif command == "lmkdir":
    -199            self.command_lmkdir(arguments, command)
    -200
    -201        # Shows the current local directory
    -202        elif command == "lpwd":
    -203            self.command_lpwd(arguments, command)
    -204
    -205        # Renames a local file
    -206        elif command == "lrename":
    -207            self.command_lrename(arguments, command)
    -208        
    -209        # Removes a local file
    -210        elif command == "lrm":
    -211            self.command_lrm(arguments, command)
    -212
    -213        # Removes a local directory
    -214        elif command == "lrmdir":
    -215            self.command_lrmdir(arguments, command)
    -216
    -217        # Shows the current local directory
    -218        elif command == "ltree":
    -219            self.command_ltree(arguments, command)
    -220
    -221        # Modules
    -222        elif command == "module":
    -223            self.command_module(arguments, command)
    -224
    -225        # Creates a mount point of the remote share on the local machine
    -226        elif command == "mount":
    -227            self.command_mount(arguments, command)
    -228
    -229        # Reconnects the current SMB session
    -230        elif command in ["connect", "reconnect"]:
    -231            self.command_reconnect(arguments, command)
    -232
    -233        # Reset the TTY output
    -234        elif command == "reset":
    -235            self.command_reset(arguments, command)
    -236
    -237        # Removes a remote file
    -238        elif command == "rm":
    -239            self.command_rm(arguments, command)
    -240            
    -241        # Removes a remote directory
    -242        elif command == "rmdir":
    -243            self.command_rmdir(arguments, command)
    -244
    -245        # List shares
    -246        elif command == "sizeof":
    -247            self.command_sizeof(arguments, command)
    -248
    -249        # List shares
    -250        elif command == "shares":
    -251            self.command_shares(arguments, command)
    -252        
    -253        # Displays a tree view of the CWD
    -254        elif command == "tree":
    -255            self.command_tree(arguments, command)
    -256        
    -257        # Use a share
    -258        elif command == "use":
    -259            self.command_use(arguments, command)
    -260
    -261    # Commands ================================================================
    -262
    -263    def command_debug(self, arguments, command):
    -264        try:
    -265            pass
    -266        except Exception as e:
    -267            traceback.print_exc()
    -268
    -269    @command_arguments_required
    -270    @active_smb_connection_needed
    -271    @smb_share_is_set
    -272    def command_bat(self, arguments, command):
    -273        # Command arguments required   : Yes
    -274        # Active SMB connection needed : Yes
    -275        # SMB share needed             : Yes
    -276
    -277        path = ' '.join(arguments)
    -278        try:
    -279            rawcontents = self.smbSession.read_file(path=path)
    -280            if rawcontents is not None:
    -281                encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -282                if encoding is not None:
    -283                    filecontent = rawcontents.decode(encoding).rstrip()
    -284                    lexer = Syntax.guess_lexer(path=ntpath.basename(path), code=filecontent)
    -285                    # Some trickery for the files undetected by the lexer
    -286                    if lexer == "default":
    -287                        if '<?xml' in filecontent:
    -288                            lexer = "xml"
    -289                        elif '<html>' in filecontent:
    -290                            lexer = "html"
    -291                    syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    -292                    Console().print(syntax)
    -293                else:
    -294                    print("[!] Could not detect charset of '%s'." % path)
    -295        except impacket.smbconnection.SessionError as e:
    -296            print("[!] SMB Error: %s" % e)
    -297
    -298    @command_arguments_required
    -299    @active_smb_connection_needed
    -300    @smb_share_is_set
    -301    def command_cd(self, arguments, command):
    -302        # Command arguments required   : Yes
    -303        # Active SMB connection needed : Yes
    -304        # SMB share needed             : Yes
    -305
    -306        path = ' '.join(arguments)
    -307        try:
    -308            self.smbSession.set_cwd(path=path)
    -309        except impacket.smbconnection.SessionError as e:
    -310            print("[!] SMB Error: %s" % e)
    -311
    -312    @command_arguments_required
    -313    @active_smb_connection_needed
    -314    @smb_share_is_set
    -315    def command_cat(self, arguments, command):
    -316        # Command arguments required   : Yes
    -317        # Active SMB connection needed : Yes
    -318        # SMB share needed             : Yes
    -319
    -320        path = ' '.join(arguments)
    -321        try:
    -322            rawcontents = self.smbSession.read_file(path=path)
    -323            if rawcontents is not None:
    -324                encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -325                if encoding is not None:
    -326                    filecontent = rawcontents.decode(encoding).rstrip()
    -327                    print(filecontent)
    -328                else:
    -329                    print("[!] Could not detect charset of '%s'." % path)
    -330        except impacket.smbconnection.SessionError as e:
    -331            print("[!] SMB Error: %s" % e)
    -332
    -333    def command_close(self, arguments, command):
    -334        # Command arguments required   : No
    -335        # Active SMB connection needed : No
    -336        # SMB share needed             : No
    -337
    -338        self.smbSession.ping_smb_session()
    -339        if self.smbSession.connected:
    -340            self.smbSession.close_smb_session()
    -341
    -342    @command_arguments_required
    -343    @active_smb_connection_needed
    -344    @smb_share_is_set
    -345    def command_get(self, arguments, command):
    -346        # Command arguments required   : Yes
    -347        # Active SMB connection needed : Yes
    -348        # SMB share needed             : Yes
    -349
    -350        # Get files recursively
    -351        if arguments[0] == "-r":
    -352            path = ' '.join(arguments[1:]).replace('/', ntpath.sep)
    -353            try:
    -354                self.smbSession.get_file_recursively(path=path)
    -355            except impacket.smbconnection.SessionError as e:
    -356                print("[!] SMB Error: %s" % e)
    -357        # Get a single file
    -358        else:
    -359            path = ' '.join(arguments).replace('/', ntpath.sep)
    -360            try:
    -361                self.smbSession.get_file(path=path)
    -362            except impacket.smbconnection.SessionError as e:
    -363                print("[!] SMB Error: %s" % e)
    -364
    -365    def command_help(self, arguments, command):
    -366        # Command arguments required   : No
    -367        # Active SMB connection needed : No
    -368        # SMB share needed             : No
    -369
    -370        if len(arguments) != 0:
    -371            self.commandCompleterObject.print_help(command=arguments[0])
    -372        else:
    -373            self.commandCompleterObject.print_help(command=None)
    -374
    -375    @active_smb_connection_needed
    -376    def command_info(self, arguments, command):
    -377        # Command arguments required   : No
    -378        # Active SMB connection needed : Yes
    -379        # SMB share needed             : No
    -380
    -381        print_server_info = False
    -382        print_share_info = False
    -383        if len(arguments) != 0:
    -384            print_server_info = (arguments[0].lower() == "server")
    -385            print_share_info = (arguments[0].lower() == "share")
    -386        else:
    -387            print_server_info = True
    -388            print_share_info = True
    -389
    -390        try:
    -391            self.smbSession.info(
    -392                share=print_share_info,
    -393                server=print_server_info
    -394            )
    -395        except impacket.smbconnection.SessionError as e:
    -396            print("[!] SMB Error: %s" % e)
    -397
    -398    @command_arguments_required
    -399    def command_lcat(self, arguments, command):
    -400        # Command arguments required   : Yes
    -401        # Active SMB connection needed : No
    -402        # SMB share needed             : No
    -403
    -404        path = ' '.join(arguments)
    -405        try:
    -406            if os.path.exists(path=path):
    -407                f = open(path, 'rb')
    -408                rawcontents = f.read()
    -409                if rawcontents is not None:
    -410                    encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -411                    if encoding is not None:
    -412                        filecontent = rawcontents.decode(encoding).rstrip()
    -413                        print(filecontent)
    -414                    else:
    -415                        print("[!] Could not detect charset of '%s'." % path)
    -416            else:
    -417                print("[!] Local file '%s' does not exist." % path)
    -418        except impacket.smbconnection.SessionError as e:
    -419            print("[!] SMB Error: %s" % e)
    -420
    -421    @command_arguments_required
    -422    def command_lcd(self, arguments, command):
    -423        # Command arguments required   : Yes
    -424        # Active SMB connection needed : No
    -425        # SMB share needed             : No
    -426
    -427        path = ' '.join(arguments)
    -428        if os.path.exists(path=path):
    -429            if os.path.isdir(s=path):
    -430                os.chdir(path=path)
    -431            else:
    -432                print("[!] Path '%s' is not a directory." % path)
    -433        else:
    -434            print("[!] Directory '%s' does not exists." % path)
    -435
    -436    @command_arguments_required
    -437    def command_lcp(self, arguments, command):
    -438        # Command arguments required   : Yes
    -439        # Active SMB connection needed : No
    -440        # SMB share needed             : No
    -441
    -442        if len(arguments) == 2:
    -443            src_path = arguments[0]
    -444            dst_path = arguments[1]
    -445            if os.path.exists(path=src_path):
    -446                try:
    -447                    shutil.copyfile(src=src_path, dst=dst_path)
    -448                except shutil.SameFileError as err:
    -449                    print("[!] Error: %s" % err)
    -450            else:
    -451                print("[!] File '%s' does not exists." % src_path)
    -452        else:
    -453            self.commandCompleterObject.print_help(command=command)
    -454
    -455    @command_arguments_required
    -456    def command_lbat(self, arguments, command):
    -457        # Command arguments required   : Yes
    -458        # Active SMB connection needed : No
    -459        # SMB share needed             : No
    -460
    -461        path = ' '.join(arguments)
    -462        try:
    -463            if os.path.exists(path=path):
    -464                f = open(path, 'rb')
    -465                rawcontents = f.read()
    -466                if rawcontents is not None:
    -467                    encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -468                    if encoding is not None:
    -469                        filecontent = rawcontents.decode(encoding).rstrip()
    -470                        lexer = Syntax.guess_lexer(path=ntpath.basename(path), code=filecontent)
    -471                        # Some trickery for the files undetected by the lexer
    -472                        if lexer == "default":
    -473                            if '<?xml' in filecontent:
    -474                                lexer = "xml"
    -475                            elif '<html>' in filecontent:
    -476                                lexer = "html"
    -477                        syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    -478                        Console().print(syntax)
    -479                    else:
    -480                        print("[!] Could not detect charset of '%s'." % path)
    -481            else:
    -482                print("[!] Local file '%s' does not exist." % path)
    -483        except impacket.smbconnection.SessionError as e:
    -484            print("[!] SMB Error: %s" % e)
    -485
    -486    def command_lls(self, arguments, command):
    -487        # Command arguments required   : No
    -488        # Active SMB connection needed : No
    -489        # SMB share needed             : No
    -490
    -491        if len(arguments) == 0:
    -492            path = '.'
    -493        else:
    -494            path = ' '.join(arguments)
    -495
    -496        # lls <directory>
    -497        if os.path.isdir(path):
    -498            directory_contents = os.listdir(path=path)
    -499            for entryname in sorted(directory_contents):
    -500                path_to_file = path + os.path.sep + entryname
    -501                rights_str = unix_permissions(path_to_file)
    -502                size_str = b_filesize(os.path.getsize(filename=path_to_file))
    -503                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    -504
    -505                if os.path.isdir(s=entryname):
    -506                    if self.config.no_colors:
    -507                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -508                    else:
    -509                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -510                else:
    -511                    if self.config.no_colors:
    -512                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    -513                    else:
    -514                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    -515        # lls <file>
    -516        elif os.path.isfile(path):
    -517            rights_str = unix_permissions(path)
    -518            size_str = b_filesize(os.path.getsize(filename=path))
    -519            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    -520            if self.config.no_colors:
    -521                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    -522            else:
    -523               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    -524        else:
    -525            print("[!] No such file or directory.")
    -526
    -527    @command_arguments_required
    -528    def command_lmkdir(self, arguments, command):
    -529        # Command arguments required   : Yes
    -530        # Active SMB connection needed : No
    -531        # SMB share needed             : No
    -532
    -533        path = ' '.join(arguments)
    -534
    -535        # Split each dir
    -536        if os.path.sep in path:
    -537            path = path.strip(os.path.sep).split(os.path.sep)
    -538        else:
    -539            path = [path]
    -540
    -541        # Create each dir in the path
    -542        for depth in range(1, len(path)+1):
    -543            tmp_path = os.path.sep.join(path[:depth])
    -544            if not os.path.exists(tmp_path):
    -545                os.mkdir(path=tmp_path)
    -546
    -547    def command_lpwd(self, arguments, command):
    -548        # Command arguments required   : No
    -549        # Active SMB connection needed : No
    -550        # SMB share needed             : No
    -551
    -552        print(os.getcwd())
    -553
    -554    @command_arguments_required
    -555    def command_lrename(self, arguments, command):
    -556        # Command arguments required   : Yes
    -557        # Active SMB connection needed : No
    -558        # SMB share needed             : No
    -559
    -560        if len(arguments) == 2:
    -561            os.rename(src=arguments[0], dst=arguments[1])
    -562        else:
    -563            self.commandCompleterObject.print_help(command=command)
    -564
    -565    @command_arguments_required
    -566    def command_lrm(self, arguments, command):
    -567        # Command arguments required   : Yes
    -568        # Active SMB connection needed : No
    -569        # SMB share needed             : No
    -570
    -571        path = ' '.join(arguments)
    -572        if os.path.exists(path):
    -573            if not os.path.isdir(s=path):
    -574                try:
    -575                    os.remove(path=path)
    -576                except Exception as e:
    -577                    print("[!] Error removing file '%s' : %s" % path)
    -578            else:
    -579                print("[!] Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
    -580        else:
    -581            print("[!] Path '%s' does not exist." % path)
    -582
    -583    @command_arguments_required
    -584    def command_lrmdir(self, arguments, command):
    -585        # Command arguments required   : Yes
    -586        # Active SMB connection needed : No
    -587        # SMB share needed             : No
    -588
    -589        path = ' '.join(arguments)
    -590        if os.path.exists(path):
    -591            if os.path.isdir(s=path):
    -592                try:
    -593                    shutil.rmtree(path=path)
    -594                except Exception as e:
    -595                    print("[!] Error removing directory '%s' : %s" % path)
    -596            else:
    -597                print("[!] Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
    -598        else:
    -599            print("[!] Path '%s' does not exist." % path)
    -600
    -601    def command_ltree(self, arguments, command):
    -602        # Command arguments required   : No
    -603        # Active SMB connection needed : No
    -604        # SMB share needed             : No
    -605
    -606        if len(arguments) == 0:
    -607            local_tree(path='.', config=self.config)
    -608        else:
    -609            local_tree(path=' '.join(arguments), config=self.config)
    -610
    -611    @active_smb_connection_needed
    -612    @smb_share_is_set
    -613    def command_ls(self, arguments, command):
    -614        # Command arguments required   : No
    -615        # Active SMB connection needed : Yes
    -616        # SMB share needed             : Yes
    -617
    -618        # Read the files
    -619        directory_contents = self.smbSession.list_contents(path=' '.join(arguments))
    -620
    -621        for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
    -622            windows_ls_entry(directory_contents[longname], self.config)
    -623            
    -624    @command_arguments_required
    -625    @active_smb_connection_needed
    -626    @smb_share_is_set
    -627    def command_mkdir(self, arguments, command):
    -628        # Command arguments required   : Yes
    -629        # Active SMB connection needed : Yes
    -630        # SMB share needed             : Yes
    -631
    -632        path = ' '.join(arguments)
    -633        self.smbSession.mkdir(path=path)
    -634
    -635    @command_arguments_required
    -636    @active_smb_connection_needed
    -637    @smb_share_is_set
    -638    def command_module(self, arguments, command):
    -639        module_name = arguments[0]
    -640
    -641        if module_name in self.modules.keys():
    -642            module = self.modules[module_name](self.smbSession, self.config)
    -643            module.run(' '.join(arguments[1:]))
    -644        else:
    -645            print("[!] Module '%s' does not exist." % module_name)
    -646
    -647    @command_arguments_required
    -648    @active_smb_connection_needed
    -649    @smb_share_is_set
    -650    def command_mount(self, arguments, command):
    -651        # Command arguments required   : Yes
    -652        # Active SMB connection needed : Yes
    -653        # SMB share needed             : Yes
    -654
    -655        if len(arguments) == 2:
    -656            remote_path = arguments[0]
    -657            if not remote_path.startswith(ntpath.sep):
    -658                remote_path = self.smbSession.smb_cwd + ntpath.sep + remote_path
    -659
    -660            local_mount_point = arguments[1]
    -661
    -662            if self.config.debug:
    -663                print("[debug] Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point))
    -664
    -665            try:
    -666                self.smbSession.mount(local_mount_point, remote_path)
    -667            except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e:
    -668                self.smbSession.umount(local_mount_point)
    -669        else:
    -670            self.commandCompleterObject.print_help(command=command)
    -671
    -672    @command_arguments_required
    -673    @active_smb_connection_needed
    -674    @smb_share_is_set
    -675    def command_put(self, arguments, command):
    -676        # Command arguments required   : Yes
    -677        # Active SMB connection needed : Yes
    -678        # SMB share needed             : Yes
    -679
    -680        # Put files recursively
    -681        if arguments[0] == "-r":
    -682            localpath = ' '.join(arguments[1:])
    -683            try:
    -684                self.smbSession.put_file_recursively(localpath=localpath)
    -685            except impacket.smbconnection.SessionError as e:
    -686                print("[!] SMB Error: %s" % e)
    -687
    -688        # Put a single file
    -689        else:
    -690            localpath = ' '.join(arguments)
    -691            try:
    -692                self.smbSession.put_file(localpath=localpath)
    -693            except impacket.smbconnection.SessionError as e:
    -694                print("[!] SMB Error: %s" % e)
    -695
    -696    def command_reconnect(self, arguments, command):
    -697        # Command arguments required   : No
    -698        # Active SMB connection needed : No
    -699        # SMB share needed             : No
    -700
    -701        self.smbSession.ping_smb_session()
    -702        if self.smbSession.connected:
    -703            self.smbSession.close_smb_session()
    -704            self.smbSession.init_smb_session()
    -705        else:
    -706            self.smbSession.init_smb_session()
    -707
    -708    def command_reset(self, arguments, command):
    -709        # Command arguments required   : No
    -710        # Active SMB connection needed : No
    -711        # SMB share needed             : No
    -712        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    -713        sys.stdout.write('\x1b[v')  
    -714        sys.stdout.write('\x1b[o') # Reset
    -715        sys.stdout.flush()
    -716
    -717    @command_arguments_required
    -718    @active_smb_connection_needed
    -719    @smb_share_is_set
    -720    def command_rm(self, arguments, command):
    -721        # Command arguments required   : Yes
    -722        # Active SMB connection needed : Yes
    -723        # SMB share needed             : Yes
    -724
    -725        path = ' '.join(arguments)
    -726        if '*' in path:
    -727            self.smbSession.rm(path=path)
    -728        elif self.smbSession.path_exists(path):
    -729            if self.smbSession.path_isfile(path):
    -730                try:
    -731                    self.smbSession.rm(path=path)
    -732                except Exception as e:
    -733                    print("[!] Error removing file '%s' : %s" % path)
    -734            else:
    -735                print("[!] Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path)
    -736        else:
    -737            print("[!] Remote file '%s' does not exist." % path)
    -738
    -739    @command_arguments_required
    -740    @active_smb_connection_needed
    -741    @smb_share_is_set
    -742    def command_rmdir(self, arguments, command):
    -743        # Command arguments required   : Yes
    -744        # Active SMB connection needed : Yes
    -745        # SMB share needed             : Yes
    -746
    -747        path = ' '.join(arguments)
    -748        if self.smbSession.path_exists(path):
    -749            if self.smbSession.path_isdir(path):
    -750                try:
    -751                    self.smbSession.rmdir(path=path)
    -752                except Exception as e:
    -753                    print("[!] Error removing directory '%s' : %s" % path)
    -754            else:
    -755                print("[!] Cannot delete '%s': This is a file, use 'rm <file>' instead." % path)
    -756        else:
    -757            print("[!] Remote directory '%s' does not exist." % path)
    -758
    -759    @active_smb_connection_needed
    -760    @smb_share_is_set
    -761    def command_sizeof(self, arguments, command):
    -762        # Command arguments required   : Yes
    -763        # Active SMB connection needed : Yes
    -764        # SMB share needed             : Yes
    -765
    -766        class RecursiveSizeOfPrint(object):
    -767            def __init__(self, entry, smbSession, config):
    -768                self.entry = entry
    -769                self.config = config
    -770                self.smbSession = smbSession
    -771                self.size = 0
    -772            
    -773            def update(self, entry, fullpath, depth):
    -774                self.size += entry.get_filesize()
    -775                self.print(end='\r')
    -776            
    -777            def print(self, end='\n'):
    -778                #
    -779                if self.entry.is_directory():
    -780                    if self.config.no_colors:
    -781                        path = "%s\\" % self.entry.get_longname()
    -782                    else:
    -783                        path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname()
    -784                # 
    -785                else:
    -786                    if self.config.no_colors:
    -787                        path = "%s" % self.entry.get_longname()
    -788                    else:
    -789                        path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname()
    -790                print("%10s  %s" % (b_filesize(self.size), path), end=end)
    -791
    -792        entries = []
    -793        if len(arguments) == 0:
    -794            entries = self.smbSession.list_contents()
    -795            entries = [entry for name, entry in entries.items() if name not in ['.', '..']]
    -796        else:
    -797            entry = self.smbSession.get_entry(path=' '.join(arguments))
    -798            entries = []
    -799            if entry is not None:
    -800                entries = [entry]
    -801            else:
    -802                print("[!] Path '%s' does not exist." % ' '.join(arguments))
    -803
    -804        total = 0
    -805        for entry in entries:
    -806            rsop = RecursiveSizeOfPrint(entry=entry, smbSession=self.smbSession, config=self.config)
    -807            # Directory
    -808            if entry.is_directory():
    -809                self.smbSession.find(
    -810                    paths=[entry.get_longname()],
    -811                    callback=rsop.update
    -812                )
    -813            # File
    -814            else:
    -815                rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0)
    -816            # Close the print
    -817            rsop.print()
    -818            total += rsop.size
    -819        
    -820        if len(entries) > 1:
    -821            print("──────────────────────")
    -822            print("     total  %s" % b_filesize(total))
    -823
    -824    @active_smb_connection_needed
    -825    def command_shares(self, arguments, command):
    -826        # Command arguments required   : No
    -827        # Active SMB connection needed : Yes
    -828        # SMB share needed             : No
    -829
    -830        do_check_rights = False
    -831        if len(arguments) != 0:
    -832            if arguments[0] == "rights":
    -833                do_check_rights = True
    -834
    -835        shares = self.smbSession.list_shares()
    -836        if len(shares.keys()) != 0:
    -837            table = Table(title=None)
    -838            table.add_column("Share")
    -839            table.add_column("Visibility")
    -840            table.add_column("Type")
    -841            table.add_column("Description", justify="left")
    -842            if do_check_rights:
    -843                table.add_column("Rights")
    -844
    -845            for sharename in sorted(shares.keys()):
    -846                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
    -847
    -848                is_hidden = bool(sharename.endswith('$'))
    -849                if is_hidden:
    -850                    str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]"
    -851                    str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]"
    -852                    str_types = "[bold bright_blue]" + types + "[/bold bright_blue]"
    -853                    str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]"
    -854                else:
    -855                    str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]"
    -856                    str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]"
    -857                    str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]"
    -858                    str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]"
    -859
    -860                if do_check_rights:
    -861                    access_rights = self.smbSession.test_rights(sharename=shares[sharename]["name"])
    -862                    str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    -863                    if access_rights["readable"] and access_rights["writable"]:
    -864                        str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]"
    -865                    elif access_rights["readable"]:
    -866                        str_access_rights = "[bold green]READ[/bold green]"
    -867                    elif access_rights["writable"]:
    -868                        # Without READ?? This should not happen IMHO
    -869                        str_access_rights = "[bold red]WRITE[/bold red]"
    -870                    else:
    -871                        str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    -872
    -873                if do_check_rights:
    -874                    table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights)
    -875                else:
    -876                    table.add_row(str_sharename, str_hidden, str_types, str_comment)
    -877
    -878            Console().print(table)
    -879        else:
    -880            print("[!] No share served on '%s'" % self.smbSession.address)
    -881
    -882    @active_smb_connection_needed
    -883    @smb_share_is_set
    -884    def command_tree(self, arguments, command):
    -885        # Command arguments required   : No
    -886        # Active SMB connection needed : Yes
    -887        # SMB share needed             : Yes
    -888
    -889        if len(arguments) == 0:
    -890            self.smbSession.tree(path='.')
    -891        else:
    -892            self.smbSession.tree(path=' '.join(arguments))
    -893
    -894    @command_arguments_required
    -895    @active_smb_connection_needed
    -896    @smb_share_is_set
    -897    def command_umount(self, arguments, command):
    -898        # Command arguments required   : Yes
    -899        # Active SMB connection needed : Yes
    -900        # SMB share needed             : Yes
    -901
    -902        local_mount_point = arguments[0]
    -903
    -904        if self.config.debug:
    -905            print("[debug] Trying to unmount local mount point '%s'" % (local_mount_point))
    -906        
    -907        self.smbSession.umount(local_mount_point)
    -908        
    -909    @command_arguments_required
    -910    @active_smb_connection_needed
    -911    def command_use(self, arguments, command):
    -912        # Command arguments required   : Yes
    -913        # Active SMB connection needed : Yes
    -914        # SMB share needed             : No
    -915
    -916        sharename = ' '.join(arguments)
    -917        # Reload the list of shares
    -918        shares = self.smbSession.list_shares()
    -919        shares = [s.lower() for s in shares.keys()]
    -920        if sharename.lower() in shares:
    -921            self.smbSession.set_share(sharename)
    -922        else:
    -923            print("[!] No share named '%s' on '%s'" % (sharename, self.smbSession.address))
    -924
    -925    # Private functions =======================================================
    -926
    -927    def __load_modules(self):
    -928
    -929        self.modules.clear()
    -930
    -931        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
    -932        if self.config.debug:
    -933            print("[debug] Loading modules from %s ..." % modules_dir)
    -934        sys.path.extend([modules_dir])
    -935
    -936        for file in os.listdir(modules_dir):
    -937            filepath = os.path.normpath(modules_dir + os.path.sep + file)
    -938            if file.endswith('.py'):
    -939                if os.path.isfile(filepath) and file not in ["__init__.py"]:
    -940                    try:
    -941                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
    -942                        module = module_file.__getattribute__(file[:-3])
    -943                        self.modules[module.name.lower()] = module
    -944                    except AttributeError as e:
    -945                        pass
    -946
    -947        if self.config.debug:
    -948            if len(self.modules.keys()) == 0:
    -949                print("[debug] Loaded 0 modules.")
    -950            elif len(self.modules.keys()) == 1:
    -951                print("[debug] Loaded 1 module:")
    -952            else:
    -953                print("[debug] Loaded %d modules:" % len(self.modules.keys()))
    -954            for modulename in sorted(self.modules.keys()):
    -955                print("[debug] %s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename]))
    -956
    -957        if self.commandCompleterObject is not None:
    -958            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
    -959
    -960    def __prompt(self):
    -961        """
    -962        Prints the command prompt for the interactive shell.
    -963
    -964        This method constructs and returns the command prompt string based on the current state of the SMB session.
    -965        The prompt indicates the connection status with a visual symbol and displays the current working directory
    -966        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
    -967
    -968        Returns:
    -969            str: The formatted command prompt string.
    -970        """
    -971
    -972        self.smbSession.ping_smb_session()
    -973        if self.smbSession.connected:
    -974            if self.config.no_colors:
    -975                connected_dot = "[v]"
    -976            else:
    -977                connected_dot = "\x1b[1;92m⏺\x1b[0m"
    -978        else:
    -979            if self.config.no_colors:
    -980                connected_dot = "[x]"
    -981            else:
    -982                connected_dot = "\x1b[1;91m⏺\x1b[0m"
    -983        
    -984        if self.smbSession.smb_share is None:
    -985            if self.config.no_colors:
    -986                str_prompt = "%s[\\\\%s\\]> " % (connected_dot, self.smbSession.address)
    -987            else:
    -988                str_prompt = "%s[\x1b[1;94m\\\\%s\\\x1b[0m]> " % (connected_dot, self.smbSession.address)
    -989        else:
    -990            str_path = "\\\\%s\\%s\\%s" % (self.smbSession.address, self.smbSession.smb_share, self.smbSession.smb_cwd.lstrip(ntpath.sep))
    -991            if self.config.no_colors:
    -992                str_prompt = "%s[%s]> " % (connected_dot, str_path)
    -993            else:
    -994                str_prompt = "%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, str_path)
    -995
    -996        return str_prompt
    +                        
       1#!/usr/bin/env python3
    +   2# -*- coding: utf-8 -*-
    +   3# File name          : InteractiveShell.py
    +   4# Author             : Podalirius (@podalirius_)
    +   5# Date created       : 23 may 2024
    +   6
    +   7
    +   8import charset_normalizer
    +   9import datetime
    +  10import impacket
    +  11from importlib import import_module
    +  12import ntpath
    +  13import os
    +  14import readline
    +  15import shutil
    +  16import shlex
    +  17import sys
    +  18import traceback
    +  19from rich.console import Console
    +  20from rich.table import Table
    +  21from rich.syntax import Syntax
    +  22from smbclientng.core.CommandCompleter import CommandCompleter
    +  23from smbclientng.core.utils import b_filesize, unix_permissions, windows_ls_entry, local_tree, resolve_local_files, resolve_remote_files
    +  24
    +  25
    +  26## Decorators
    +  27
    +  28def command_arguments_required(func):
    +  29    def wrapper(*args, **kwargs):
    +  30        self, arguments,command  = args[0], args[1], args[2]
    +  31        if len(arguments) != 0:
    +  32            return func(*args, **kwargs)
    +  33        else:
    +  34            self.commandCompleterObject.print_help(command=command)
    +  35            return None
    +  36    return wrapper
    +  37
    +  38def active_smb_connection_needed(func):
    +  39    def wrapper(*args, **kwargs):
    +  40        self, arguments,command  = args[0], args[1], args[2]
    +  41        #
    +  42        self.sessionsManager.current_session.ping_smb_session()
    +  43        if self.sessionsManager.current_session.connected:
    +  44            return func(*args, **kwargs)
    +  45        else:
    +  46            print("[!] SMB Session is disconnected.")
    +  47            return None
    +  48    return wrapper
    +  49
    +  50def smb_share_is_set(func):
    +  51    def wrapper(*args, **kwargs):
    +  52        self, arguments,command  = args[0], args[1], args[2]
    +  53        if self.sessionsManager.current_session.smb_share is not None:
    +  54            return func(*args, **kwargs)
    +  55        else:
    +  56            print("[!] You must open a share first, try the 'use <share>' command.")
    +  57            return None
    +  58    return wrapper
    +  59
    +  60
    +  61class InteractiveShell(object):
    +  62    """
    +  63    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
    +  64    
    +  65    This class handles user input, executes commands, and manages the state of the SMB session. It provides
    +  66    a command line interface for users to interact with SMB shares, execute commands like directory listing,
    +  67    file transfer, and more.
    +  68
    +  69    Attributes:
    +  70        smbSession (SMBConnection): The active SMB connection session.
    +  71        debug (bool): Flag to enable or disable debug mode.
    +  72        smb_share (str): The current SMB share in use.
    +  73        smb_path (str): The current path within the SMB share.
    +  74        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
    +  75
    +  76    Methods:
    +  77        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
    +  78        run(self): Starts the command line interface loop, processing user input until exit.
    +  79    """
    +  80
    +  81    running = True
    +  82    modules = {}
    +  83    
    +  84    def __init__(self, sessionsManager, config, logger):
    +  85        # Objects
    +  86        self.sessionsManager = sessionsManager
    +  87        self.config = config
    +  88        self.logger = logger
    +  89        # Internals
    +  90        self.commandCompleterObject = CommandCompleter(
    +  91            smbSession=self.sessionsManager.current_session,
    +  92            config=self.config,
    +  93            logger=self.logger,
    +  94        )
    +  95        readline.set_completer(self.commandCompleterObject.complete)
    +  96        readline.parse_and_bind("tab: complete")
    +  97        readline.set_completer_delims("\n")
    +  98        # Additional modules
    +  99        self.__load_modules()
    + 100
    + 101    def run(self):
    + 102        # Read commands from script file first
    + 103        if self.config.startup_script:
    + 104            f = open(self.config.startup_script, 'r')
    + 105            for line in f.readlines():
    + 106                try:
    + 107                    self.logger.print("%s%s" % (self.__prompt(), line.strip()))
    + 108                    readline.add_history(line.strip())
    + 109                    self.process_line(commandLine=line.strip())
    + 110                except KeyboardInterrupt as e:
    + 111                    self.logger.print()
    + 112
    + 113                except EOFError as e:
    + 114                    self.logger.print()
    + 115                    running = False
    + 116
    + 117                except Exception as err:
    + 118                    if self.config.debug:
    + 119                        traceback.print_exc()
    + 120                    self.logger.error(str(err))
    + 121
    + 122        # Then interactive console
    + 123        if not self.config.not_interactive:
    + 124            while self.running:
    + 125                try:
    + 126                    user_input = input(self.__prompt()).strip()
    + 127                    self.process_line(commandLine=user_input)
    + 128                except KeyboardInterrupt as e:
    + 129                    self.logger.print()
    + 130
    + 131                except EOFError as e:
    + 132                    self.logger.print()
    + 133                    running = False
    + 134
    + 135                except Exception as err:
    + 136                    if self.config.debug:
    + 137                        traceback.print_exc()
    + 138                    self.logger.error(str(err))
    + 139
    + 140    def process_line(self, commandLine):
    + 141        # Split and parse the commandLine
    + 142        tokens = shlex.split(commandLine)
    + 143        if len(tokens) == 0:
    + 144            command = ""
    + 145            arguments = []
    + 146        elif len(tokens) == 1:
    + 147            command = tokens[0].lower()
    + 148            arguments = []
    + 149        else:
    + 150            command = tokens[0].lower()
    + 151            arguments = tokens[1:]
    + 152        
    + 153        # Skip
    + 154        if command.strip() == "":
    + 155            pass
    + 156        # Execute the command
    + 157        elif command in self.commandCompleterObject.commands.keys():
    + 158
    + 159            # Exit the command line
    + 160            if command in ["exit", "quit"]:
    + 161                self.running = False
    + 162            
    + 163            # Display help
    + 164            elif command == "help":
    + 165                self.command_help(arguments, command)
    + 166
    + 167            # Cat the contents of a file
    + 168            elif command == "bat":
    + 169                self.command_bat(arguments, command)
    + 170
    + 171            # Cat the contents of a file
    + 172            elif command == "cat":
    + 173                self.command_cat(arguments, command)
    + 174
    + 175            # Closes the current SMB session
    + 176            elif command == "close":
    + 177                self.command_close(arguments, command)
    + 178                
    + 179            # Change directory in the current share
    + 180            elif command == "cd":
    + 181                self.command_cd(arguments, command)
    + 182            
    + 183            # debug
    + 184            elif command == "debug":
    + 185                self.command_debug(arguments, command)
    + 186
    + 187            # Get a file
    + 188            elif command == "get":
    + 189                self.command_get(arguments, command)
    + 190
    + 191            # SMB server info
    + 192            elif command == "info":
    + 193                self.command_info(arguments, command)
    + 194
    + 195            # List directory contents in a share
    + 196            elif command in ["ls", "dir"]:
    + 197                self.command_ls(arguments, command)
    + 198
    + 199            # Creates a new remote directory
    + 200            elif command == "mkdir":
    + 201                self.command_mkdir(arguments, command)
    + 202
    + 203            # Put a file
    + 204            elif command == "put":
    + 205                self.command_put(arguments, command)
    + 206
    + 207            # Shows the content of a local file
    + 208            elif command == "lcat":
    + 209                self.command_lcat(arguments, command)
    + 210
    + 211            # Changes the current local directory
    + 212            elif command == "lcd":
    + 213                self.command_lcd(arguments, command)
    + 214
    + 215            # Creates a copy of a local file
    + 216            elif command == "lcp":
    + 217                self.command_lcp(arguments, command)
    + 218
    + 219            # Pretty prints the content of a local file
    + 220            elif command == "lbat":
    + 221                self.command_lbat(arguments, command)
    + 222
    + 223            # Lists the contents of the current local directory
    + 224            elif command == "lls":
    + 225                self.command_lls(arguments, command)
    + 226
    + 227            # Creates a new local directory
    + 228            elif command == "lmkdir":
    + 229                self.command_lmkdir(arguments, command)
    + 230
    + 231            # Shows the current local directory
    + 232            elif command == "lpwd":
    + 233                self.command_lpwd(arguments, command)
    + 234
    + 235            # Renames a local file
    + 236            elif command == "lrename":
    + 237                self.command_lrename(arguments, command)
    + 238            
    + 239            # Removes a local file
    + 240            elif command == "lrm":
    + 241                self.command_lrm(arguments, command)
    + 242
    + 243            # Removes a local directory
    + 244            elif command == "lrmdir":
    + 245                self.command_lrmdir(arguments, command)
    + 246
    + 247            # Shows the current local directory
    + 248            elif command == "ltree":
    + 249                self.command_ltree(arguments, command)
    + 250
    + 251            # Modules
    + 252            elif command == "module":
    + 253                self.command_module(arguments, command)
    + 254
    + 255            # Creates a mount point of the remote share on the local machine
    + 256            elif command == "mount":
    + 257                self.command_mount(arguments, command)
    + 258
    + 259            # Reconnects the current SMB session
    + 260            elif command in ["connect", "reconnect"]:
    + 261                self.command_reconnect(arguments, command)
    + 262
    + 263            # Reset the TTY output
    + 264            elif command == "reset":
    + 265                self.command_reset(arguments, command)
    + 266
    + 267            # Removes a remote file
    + 268            elif command == "rm":
    + 269                self.command_rm(arguments, command)
    + 270                
    + 271            # Removes a remote directory
    + 272            elif command == "rmdir":
    + 273                self.command_rmdir(arguments, command)
    + 274
    + 275            # Sessions management
    + 276            elif command == "sessions":
    + 277                self.sessionsManager.process_command_line(arguments)
    + 278
    + 279            # List shares
    + 280            elif command == "sizeof":
    + 281                self.command_sizeof(arguments, command)
    + 282
    + 283            # List shares
    + 284            elif command == "shares":
    + 285                self.command_shares(arguments, command)
    + 286            
    + 287            # Displays a tree view of the CWD
    + 288            elif command == "tree":
    + 289                self.command_tree(arguments, command)
    + 290            
    + 291            # Use a share
    + 292            elif command == "use":
    + 293                self.command_use(arguments, command)
    + 294        
    + 295        # Fallback to unknown command   
    + 296        else:
    + 297            self.logger.print("Unknown command. Type \"help\" for help.")
    + 298
    + 299    # Commands ================================================================
    + 300
    + 301    def command_debug(self, arguments, command):
    + 302        try:
    + 303            self.logger.print("[debug] command    = '%s'" % command)
    + 304            self.logger.print("[debug] arguments  = %s" % arguments)
    + 305        except Exception as e:
    + 306            traceback.print_exc()
    + 307
    + 308    @command_arguments_required
    + 309    @active_smb_connection_needed
    + 310    @smb_share_is_set
    + 311    def command_bat(self, arguments, command):
    + 312        # Command arguments required   : Yes
    + 313        # Active SMB connection needed : Yes
    + 314        # SMB share needed             : Yes
    + 315
    + 316        # Parse wildcards
    + 317        files_and_directories = resolve_local_files(arguments)
    + 318
    + 319        for path_to_file in files_and_directories:
    + 320            if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file):
    + 321                # Read the file
    + 322                try:    
    + 323                    rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file)
    + 324                    if rawcontents is not None:
    + 325                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 326                        if encoding is not None:
    + 327                            filecontent = rawcontents.decode(encoding).rstrip()
    + 328                            lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent)
    + 329                            # Some trickery for the files undetected by the lexer
    + 330                            if lexer == "default":
    + 331                                if '<?xml' in filecontent:
    + 332                                    lexer = "xml"
    + 333                                elif '<html>' in filecontent:
    + 334                                    lexer = "html"
    + 335                            syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    + 336                            if len(files_and_directories) > 1:
    + 337                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 338                            Console().print(syntax)
    + 339                        else:
    + 340                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 341                except impacket.smbconnection.SessionError as e:
    + 342                    self.logger.error("[!] SMB Error: %s" % e)
    + 343
    + 344    @command_arguments_required
    + 345    @active_smb_connection_needed
    + 346    @smb_share_is_set
    + 347    def command_cd(self, arguments, command):
    + 348        # Command arguments required   : Yes
    + 349        # Active SMB connection needed : Yes
    + 350        # SMB share needed             : Yes
    + 351
    + 352        try:
    + 353            self.sessionsManager.current_session.set_cwd(path=arguments[0])
    + 354        except impacket.smbconnection.SessionError as e:
    + 355            self.logger.error("[!] SMB Error: %s" % e)
    + 356
    + 357    @command_arguments_required
    + 358    @active_smb_connection_needed
    + 359    @smb_share_is_set
    + 360    def command_cat(self, arguments, command):
    + 361        # Command arguments required   : Yes
    + 362        # Active SMB connection needed : Yes
    + 363        # SMB share needed             : Yes
    + 364
    + 365        # Parse wildcards
    + 366        files_and_directories = resolve_local_files(arguments)
    + 367
    + 368        for path_to_file in files_and_directories:
    + 369            if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file):
    + 370                # Read the file
    + 371                try:
    + 372                    rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file)
    + 373                    if rawcontents is not None:
    + 374                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 375                        if encoding is not None:
    + 376                            filecontent = rawcontents.decode(encoding).rstrip()
    + 377                            if len(files_and_directories) > 1:
    + 378                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 379                            self.logger.print(filecontent)
    + 380                        else:
    + 381                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 382                except impacket.smbconnection.SessionError as e:
    + 383                    self.logger.error("[!] SMB Error: %s" % e)
    + 384
    + 385    def command_close(self, arguments, command):
    + 386        # Command arguments required   : No
    + 387        # Active SMB connection needed : No
    + 388        # SMB share needed             : No
    + 389
    + 390        self.sessionsManager.current_session.ping_smb_session()
    + 391        if self.sessionsManager.current_session.connected:
    + 392            self.sessionsManager.current_session.close_smb_session()
    + 393
    + 394    @command_arguments_required
    + 395    @active_smb_connection_needed
    + 396    @smb_share_is_set
    + 397    def command_get(self, arguments, command):
    + 398        # Command arguments required   : Yes
    + 399        # Active SMB connection needed : Yes
    + 400        # SMB share needed             : Yes
    + 401
    + 402        is_recursive = False
    + 403        while '-r' in arguments:
    + 404            is_recursive = True
    + 405            arguments.remove('-r')
    + 406
    + 407        # Parse wildcards
    + 408        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 409
    + 410        # 
    + 411        for remotepath in files_and_directories:
    + 412            try:
    + 413                if is_recursive and self.sessionsManager.current_session.path_isdir(remotepath):
    + 414                    # Get files recursively
    + 415                    self.sessionsManager.current_session.get_file_recursively(path=remotepath)
    + 416                else:
    + 417                    # Get this single file
    + 418                    self.sessionsManager.current_session.get_file(path=remotepath)
    + 419            except impacket.smbconnection.SessionError as e:
    + 420                self.logger.error("[!] SMB Error: %s" % e)
    + 421
    + 422    def command_help(self, arguments, command):
    + 423        # Command arguments required   : No
    + 424        # Active SMB connection needed : No
    + 425        # SMB share needed             : No
    + 426
    + 427        if len(arguments) != 0:
    + 428            self.commandCompleterObject.print_help(command=arguments[0])
    + 429        else:
    + 430            self.commandCompleterObject.print_help(command=None)
    + 431
    + 432    @active_smb_connection_needed
    + 433    def command_info(self, arguments, command):
    + 434        # Command arguments required   : No
    + 435        # Active SMB connection needed : Yes
    + 436        # SMB share needed             : No
    + 437
    + 438        print_server_info = False
    + 439        print_share_info = False
    + 440        if len(arguments) != 0:
    + 441            print_server_info = (arguments[0].lower() == "server")
    + 442            print_share_info = (arguments[0].lower() == "share")
    + 443        else:
    + 444            print_server_info = True
    + 445            print_share_info = True
    + 446
    + 447        try:
    + 448            self.sessionsManager.current_session.info(
    + 449                share=print_share_info,
    + 450                server=print_server_info
    + 451            )
    + 452        except impacket.smbconnection.SessionError as e:
    + 453            self.logger.error("[!] SMB Error: %s" % e)
    + 454
    + 455    @command_arguments_required
    + 456    def command_lbat(self, arguments, command):
    + 457        # Command arguments required   : Yes
    + 458        # Active SMB connection needed : No
    + 459        # SMB share needed             : No
    + 460
    + 461        # Parse wildcards
    + 462        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 463
    + 464        for path_to_file in files_and_directories:
    + 465            # Read the file
    + 466            try:
    + 467                if os.path.exists(path=path_to_file):
    + 468                    f = open(path_to_file, 'rb')
    + 469                    rawcontents = f.read()
    + 470                    #
    + 471                    if rawcontents is not None:
    + 472                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 473                        if encoding is not None:
    + 474                            filecontent = rawcontents.decode(encoding).rstrip()
    + 475                            lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent)
    + 476                            # Some trickery for the files undetected by the lexer
    + 477                            if lexer == "default":
    + 478                                if '<?xml' in filecontent:
    + 479                                    lexer = "xml"
    + 480                                elif '<html>' in filecontent:
    + 481                                    lexer = "html"
    + 482                            syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    + 483                            if len(files_and_directories) > 1:
    + 484                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 485                            Console().print(syntax)
    + 486                        else:
    + 487                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 488                else:
    + 489                    self.logger.error("[!] Local file '%s' does not exist." % path_to_file)
    + 490            except impacket.smbconnection.SessionError as e:
    + 491                self.logger.error("[!] SMB Error: %s" % e)
    + 492
    + 493    @command_arguments_required
    + 494    def command_lcat(self, arguments, command):
    + 495        # Command arguments required   : Yes
    + 496        # Active SMB connection needed : No
    + 497        # SMB share needed             : No
    + 498
    + 499        # Parse wildcards
    + 500        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 501
    + 502        for path_to_file in files_and_directories:
    + 503            # Read the file 
    + 504            try:
    + 505                if os.path.exists(path=path_to_file):
    + 506                    f = open(path_to_file, 'rb')
    + 507                    rawcontents = f.read()
    + 508                    #
    + 509                    if rawcontents is not None:
    + 510                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 511                        if encoding is not None:
    + 512                            filecontent = rawcontents.decode(encoding).rstrip()
    + 513                            if len(files_and_directories) > 1:
    + 514                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 515                            self.logger.print(filecontent)
    + 516                        else:
    + 517                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 518                else:
    + 519                    self.logger.error("[!] Local file '%s' does not exist." % path_to_file)
    + 520            except impacket.smbconnection.SessionError as e:
    + 521                self.logger.error("[!] SMB Error: %s" % e)
    + 522
    + 523    @command_arguments_required
    + 524    def command_lcd(self, arguments, command):
    + 525        # Command arguments required   : Yes
    + 526        # Active SMB connection needed : No
    + 527        # SMB share needed             : No
    + 528        
    + 529        path = arguments[0]
    + 530
    + 531        if os.path.exists(path=path):
    + 532            if os.path.isdir(s=path):
    + 533                os.chdir(path=path)
    + 534            else:
    + 535                self.logger.error("Path '%s' is not a directory." % path)
    + 536        else:
    + 537            self.logger.error("Directory '%s' does not exists." % path)
    + 538
    + 539    @command_arguments_required
    + 540    def command_lcp(self, arguments, command):
    + 541        # Command arguments required   : Yes
    + 542        # Active SMB connection needed : No
    + 543        # SMB share needed             : No
    + 544
    + 545        if len(arguments) == 2:
    + 546            src_path = arguments[0]
    + 547            dst_path = arguments[1]
    + 548            if os.path.exists(path=src_path):
    + 549                try:
    + 550                    shutil.copyfile(src=src_path, dst=dst_path)
    + 551                except shutil.SameFileError as err:
    + 552                    self.logger.error("[!] Error: %s" % err)
    + 553            else:
    + 554                self.logger.error("[!] File '%s' does not exists." % src_path)
    + 555        else:
    + 556            self.commandCompleterObject.print_help(command=command)
    + 557
    + 558    def command_lls(self, arguments, command):
    + 559        # Command arguments required   : No
    + 560        # Active SMB connection needed : No
    + 561        # SMB share needed             : No
    + 562
    + 563        if len(arguments) == 0:
    + 564            arguments = ['.']
    + 565        else:
    + 566            arguments = resolve_local_files(arguments)
    + 567
    + 568        for path in arguments:
    + 569            if len(arguments) > 1:
    + 570                self.logger.print("%s:" % path)
    + 571            # lls <directory>
    + 572            if os.path.isdir(path):
    + 573                directory_contents = os.listdir(path=path)
    + 574                for entryname in sorted(directory_contents):
    + 575                    path_to_file = path + os.path.sep + entryname
    + 576                    rights_str = unix_permissions(path_to_file)
    + 577                    size_str = b_filesize(os.path.getsize(filename=path_to_file))
    + 578                    date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    + 579
    + 580                    if os.path.isdir(s=entryname):
    + 581                        if self.config.no_colors:
    + 582                            self.logger.print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    + 583                        else:
    + 584                            self.logger.print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    + 585                    else:
    + 586                        if self.config.no_colors:
    + 587                            self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    + 588                        else:
    + 589                            self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    + 590            # lls <file>
    + 591            elif os.path.isfile(path):
    + 592                rights_str = unix_permissions(path)
    + 593                size_str = b_filesize(os.path.getsize(filename=path))
    + 594                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    + 595                if self.config.no_colors:
    + 596                    self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    + 597                else:
    + 598                    self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    + 599            
    + 600            if len(arguments) > 1:
    + 601                self.logger.print()
    + 602
    + 603    @command_arguments_required
    + 604    def command_lmkdir(self, arguments, command):
    + 605        # Command arguments required   : Yes
    + 606        # Active SMB connection needed : No
    + 607        # SMB share needed             : No
    + 608
    + 609        for path in arguments:
    + 610            if os.path.sep in path:
    + 611                path = path.strip(os.path.sep).split(os.path.sep)
    + 612            else:
    + 613                path = [path]
    + 614
    + 615            # Create each dir in the path
    + 616            for depth in range(1, len(path)+1):
    + 617                tmp_path = os.path.sep.join(path[:depth])
    + 618                if not os.path.exists(tmp_path):
    + 619                    os.mkdir(path=tmp_path)
    + 620
    + 621    def command_lpwd(self, arguments, command):
    + 622        # Command arguments required   : No
    + 623        # Active SMB connection needed : No
    + 624        # SMB share needed             : No
    + 625
    + 626        self.logger.print(os.getcwd())
    + 627
    + 628    @command_arguments_required
    + 629    def command_lrename(self, arguments, command):
    + 630        # Command arguments required   : Yes
    + 631        # Active SMB connection needed : No
    + 632        # SMB share needed             : No
    + 633
    + 634        if len(arguments) == 2:
    + 635            os.rename(src=arguments[0], dst=arguments[1])
    + 636        else:
    + 637            self.commandCompleterObject.print_help(command=command)
    + 638
    + 639    @command_arguments_required
    + 640    def command_lrm(self, arguments, command):
    + 641        # Command arguments required   : Yes
    + 642        # Active SMB connection needed : No
    + 643        # SMB share needed             : No
    + 644
    + 645        path = arguments[0]
    + 646
    + 647        if os.path.exists(path):
    + 648            if not os.path.isdir(s=path):
    + 649                try:
    + 650                    os.remove(path=path)
    + 651                except Exception as e:
    + 652                    self.logger.error("Error removing file '%s' : %s" % path)
    + 653            else:
    + 654                self.logger.error("Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
    + 655        else:
    + 656            self.logger.error("Path '%s' does not exist." % path)
    + 657
    + 658    @command_arguments_required
    + 659    def command_lrmdir(self, arguments, command):
    + 660        # Command arguments required   : Yes
    + 661        # Active SMB connection needed : No
    + 662        # SMB share needed             : No
    + 663
    + 664        if len(arguments) == 0:
    + 665            path = '.'
    + 666        else:
    + 667            path = arguments[0]
    + 668
    + 669        if os.path.exists(path):
    + 670            if os.path.isdir(s=path):
    + 671                try:
    + 672                    shutil.rmtree(path=path)
    + 673                except Exception as e:
    + 674                    self.logger.error("Error removing directory '%s' : %s" % path)
    + 675            else:
    + 676                self.logger.error("Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
    + 677        else:
    + 678            self.logger.error("Path '%s' does not exist." % path)
    + 679
    + 680    def command_ltree(self, arguments, command):
    + 681        # Command arguments required   : No
    + 682        # Active SMB connection needed : No
    + 683        # SMB share needed             : No
    + 684
    + 685        if len(arguments) == 0:
    + 686            path = '.'
    + 687        else:
    + 688            path = arguments[0]
    + 689
    + 690        if len(arguments) == 0:
    + 691            local_tree(path='.', config=self.config)
    + 692        else:
    + 693            local_tree(path=path, config=self.config)
    + 694
    + 695    @active_smb_connection_needed
    + 696    @smb_share_is_set
    + 697    def command_ls(self, arguments, command):
    + 698        # Command arguments required   : No
    + 699        # Active SMB connection needed : Yes
    + 700        # SMB share needed             : Yes
    + 701
    + 702        if len(arguments) == 0:
    + 703            arguments = ['.']
    + 704        else:
    + 705            arguments = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 706
    + 707        for path in arguments:
    + 708            if len(arguments) > 1:
    + 709                self.logger.print("%s:" % path)
    + 710
    + 711            if self.sessionsManager.current_session.path_isdir(pathFromRoot=path):
    + 712                # Read the files
    + 713                directory_contents = self.sessionsManager.current_session.list_contents(path=path)
    + 714            else:
    + 715                entry = self.sessionsManager.current_session.get_entry(path=path)
    + 716                if entry is not None:
    + 717                    directory_contents = {entry.get_longname(): entry}
    + 718                else:
    + 719                    directory_contents = {}
    + 720
    + 721            for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
    + 722                windows_ls_entry(directory_contents[longname], self.config)
    + 723
    + 724            if len(arguments) > 1:
    + 725                self.logger.print()
    + 726            
    + 727    @command_arguments_required
    + 728    @active_smb_connection_needed
    + 729    @smb_share_is_set
    + 730    def command_mkdir(self, arguments, command):
    + 731        # Command arguments required   : Yes
    + 732        # Active SMB connection needed : Yes
    + 733        # SMB share needed             : Yes
    + 734
    + 735        self.sessionsManager.current_session.mkdir(path=arguments[0])
    + 736
    + 737    @command_arguments_required
    + 738    @active_smb_connection_needed
    + 739    @smb_share_is_set
    + 740    def command_module(self, arguments, command):
    + 741        module_name = arguments[0]
    + 742
    + 743        if module_name in self.modules.keys():
    + 744            module = self.modules[module_name](self.sessionsManager.current_session, self.config, self.logger)
    + 745            arguments_string = ' '.join(arguments[1:])
    + 746            module.run(arguments_string)
    + 747        else:
    + 748            self.logger.error("Module '%s' does not exist." % module_name)
    + 749
    + 750    @command_arguments_required
    + 751    @active_smb_connection_needed
    + 752    @smb_share_is_set
    + 753    def command_mount(self, arguments, command):
    + 754        # Command arguments required   : Yes
    + 755        # Active SMB connection needed : Yes
    + 756        # SMB share needed             : Yes
    + 757
    + 758        if len(arguments) == 2:
    + 759            remote_path = arguments[0]
    + 760            if not remote_path.startswith(ntpath.sep):
    + 761                remote_path = self.sessionsManager.current_session.smb_cwd + ntpath.sep + remote_path
    + 762
    + 763            local_mount_point = arguments[1]
    + 764
    + 765            self.logger.debug("Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point))
    + 766
    + 767            try:
    + 768                self.sessionsManager.current_session.mount(local_mount_point, remote_path)
    + 769            except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e:
    + 770                self.sessionsManager.current_session.umount(local_mount_point)
    + 771        else:
    + 772            self.commandCompleterObject.print_help(command=command)
    + 773
    + 774    @command_arguments_required
    + 775    @active_smb_connection_needed
    + 776    @smb_share_is_set
    + 777    def command_put(self, arguments, command):
    + 778        # Command arguments required   : Yes
    + 779        # Active SMB connection needed : Yes
    + 780        # SMB share needed             : Yes
    + 781        
    + 782        is_recursive = False
    + 783        while '-r' in arguments:
    + 784            is_recursive = True
    + 785            arguments.remove('-r')
    + 786
    + 787        # Parse wildcards
    + 788        files_and_directories = resolve_local_files(arguments)
    + 789
    + 790        # 
    + 791        for localpath in files_and_directories:
    + 792            try:
    + 793                self.logger.print(localpath)
    + 794                if is_recursive and os.path.isdir(s=localpath):
    + 795                    # Put files recursively
    + 796                    self.sessionsManager.current_session.put_file_recursively(localpath=localpath)
    + 797                else:
    + 798                    # Put this single file
    + 799                    self.sessionsManager.current_session.put_file(localpath=localpath)
    + 800            except impacket.smbconnection.SessionError as e:
    + 801                self.logger.error("[!] SMB Error: %s" % e)
    + 802
    + 803    def command_reconnect(self, arguments, command):
    + 804        # Command arguments required   : No
    + 805        # Active SMB connection needed : No
    + 806        # SMB share needed             : No
    + 807
    + 808        self.sessionsManager.current_session.ping_smb_session()
    + 809        if self.sessionsManager.current_session.connected:
    + 810            self.sessionsManager.current_session.close_smb_session()
    + 811            self.sessionsManager.current_session.init_smb_session()
    + 812        else:
    + 813            self.sessionsManager.current_session.init_smb_session()
    + 814
    + 815    def command_reset(self, arguments, command):
    + 816        # Command arguments required   : No
    + 817        # Active SMB connection needed : No
    + 818        # SMB share needed             : No
    + 819        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    + 820        sys.stdout.write('\x1b[v')  
    + 821        sys.stdout.write('\x1b[o') # Reset
    + 822        sys.stdout.flush()
    + 823
    + 824    @command_arguments_required
    + 825    @active_smb_connection_needed
    + 826    @smb_share_is_set
    + 827    def command_rm(self, arguments, command):
    + 828        # Command arguments required   : Yes
    + 829        # Active SMB connection needed : Yes
    + 830        # SMB share needed             : Yes
    + 831
    + 832        for path_to_file in arguments:
    + 833            # Wildcard
    + 834            if '*' in path_to_file:
    + 835                self.sessionsManager.current_session.rm(path=path_to_file)
    + 836            # File
    + 837            elif self.sessionsManager.current_session.path_exists(path_to_file):
    + 838                if self.sessionsManager.current_session.path_isfile(path_to_file):
    + 839                    try:
    + 840                        self.sessionsManager.current_session.rm(path=path_to_file)
    + 841                    except Exception as e:
    + 842                        self.logger.error("Error removing file '%s' : %s" % path_to_file)
    + 843                else:
    + 844                    self.logger.error("Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path_to_file)
    + 845            # File does not exist
    + 846            else:
    + 847                self.logger.error("Remote file '%s' does not exist." % path_to_file)
    + 848
    + 849    @command_arguments_required
    + 850    @active_smb_connection_needed
    + 851    @smb_share_is_set
    + 852    def command_rmdir(self, arguments, command):
    + 853        # Command arguments required   : Yes
    + 854        # Active SMB connection needed : Yes
    + 855        # SMB share needed             : Yes
    + 856
    + 857        for path_to_directory in arguments:
    + 858            if self.sessionsManager.current_session.path_exists(path_to_directory):
    + 859                if self.sessionsManager.current_session.path_isdir(path_to_directory):
    + 860                    try:
    + 861                        self.sessionsManager.current_session.rmdir(path=path_to_directory)
    + 862                    except Exception as e:
    + 863                        self.logger.error("Error removing directory '%s' : %s" % path_to_directory)
    + 864                else:
    + 865                    self.logger.error("Cannot delete '%s': This is a file, use 'rm <file>' instead." % path_to_directory)
    + 866            else:
    + 867                self.logger.error("Remote directory '%s' does not exist." % path_to_directory)
    + 868
    + 869    @active_smb_connection_needed
    + 870    @smb_share_is_set
    + 871    def command_sizeof(self, arguments, command):
    + 872        # Command arguments required   : Yes
    + 873        # Active SMB connection needed : Yes
    + 874        # SMB share needed             : Yes
    + 875
    + 876        class RecursiveSizeOfPrint(object):
    + 877            def __init__(self, entry, sessionsManager, config, logger):
    + 878                self.entry = entry
    + 879                self.config = config
    + 880                self.logger = logger
    + 881                self.sessionsManager = sessionsManager
    + 882                self.size = 0
    + 883            
    + 884            def update(self, entry, fullpath, depth):
    + 885                self.size += entry.get_filesize()
    + 886                self.print(end='\r')
    + 887            
    + 888            def print(self, end='\n'):
    + 889                # Directory
    + 890                if self.entry.is_directory():
    + 891                    if self.config.no_colors:
    + 892                        path = "%s\\" % self.entry.get_longname()
    + 893                    else:
    + 894                        path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname()
    + 895                # File
    + 896                else:
    + 897                    if self.config.no_colors:
    + 898                        path = "%s" % self.entry.get_longname()
    + 899                    else:
    + 900                        path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname()
    + 901                self.logger.print("%10s  %s" % (b_filesize(self.size), path), end=end)
    + 902
    + 903        entries = []
    + 904        if len(arguments) == 0:
    + 905            entries = self.sessionsManager.current_session.list_contents()
    + 906            entries = [entry for name, entry in entries.items() if name not in ['.', '..']]
    + 907        else:
    + 908            entry = self.sessionsManager.current_session.get_entry(path=' '.join(arguments))
    + 909            entries = []
    + 910            if entry is not None:
    + 911                entries = [entry]
    + 912            else:
    + 913                self.logger.print("[!] Path '%s' does not exist." % ' '.join(arguments))
    + 914
    + 915        total = 0
    + 916        for entry in entries:
    + 917            rsop = RecursiveSizeOfPrint(
    + 918                entry=entry, 
    + 919                sessionsManager=self.sessionsManager, 
    + 920                config=self.config,
    + 921                logger=self.logger
    + 922            )
    + 923            # Directory
    + 924            if entry.is_directory():
    + 925                self.sessionsManager.current_session.find(
    + 926                    paths=[entry.get_longname()],
    + 927                    callback=rsop.update
    + 928                )
    + 929            # File
    + 930            else:
    + 931                rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0)
    + 932            # Close the print
    + 933            rsop.print()
    + 934            total += rsop.size
    + 935        
    + 936        if len(entries) > 1:
    + 937            self.logger.print("──────────────────────")
    + 938            self.logger.print("     total  %s" % b_filesize(total))
    + 939
    + 940    @active_smb_connection_needed
    + 941    def command_shares(self, arguments, command):
    + 942        # Command arguments required   : No
    + 943        # Active SMB connection needed : Yes
    + 944        # SMB share needed             : No
    + 945
    + 946        do_check_rights = False
    + 947        if len(arguments) != 0:
    + 948            if arguments[0] == "rights":
    + 949                do_check_rights = True
    + 950
    + 951        shares = self.sessionsManager.current_session.list_shares()
    + 952        if len(shares.keys()) != 0:
    + 953            table = Table(title=None)
    + 954            table.add_column("Share")
    + 955            table.add_column("Visibility")
    + 956            table.add_column("Type")
    + 957            table.add_column("Description", justify="left")
    + 958            if do_check_rights:
    + 959                table.add_column("Rights")
    + 960
    + 961            for sharename in sorted(shares.keys()):
    + 962                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
    + 963
    + 964                is_hidden = bool(sharename.endswith('$'))
    + 965                if is_hidden:
    + 966                    str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]"
    + 967                    str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]"
    + 968                    str_types = "[bold bright_blue]" + types + "[/bold bright_blue]"
    + 969                    str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]"
    + 970                else:
    + 971                    str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]"
    + 972                    str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]"
    + 973                    str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]"
    + 974                    str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]"
    + 975
    + 976                if do_check_rights:
    + 977                    access_rights = self.sessionsManager.current_session.test_rights(sharename=shares[sharename]["name"])
    + 978                    str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    + 979                    if access_rights["readable"] and access_rights["writable"]:
    + 980                        str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]"
    + 981                    elif access_rights["readable"]:
    + 982                        str_access_rights = "[bold green]READ[/bold green]"
    + 983                    elif access_rights["writable"]:
    + 984                        # Without READ?? This should not happen IMHO
    + 985                        str_access_rights = "[bold red]WRITE[/bold red]"
    + 986                    else:
    + 987                        str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    + 988
    + 989                if do_check_rights:
    + 990                    table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights)
    + 991                else:
    + 992                    table.add_row(str_sharename, str_hidden, str_types, str_comment)
    + 993
    + 994            Console().print(table)
    + 995        else:
    + 996            self.logger.error("No share served on '%s'" % self.sessionsManager.current_session.host)
    + 997
    + 998    @active_smb_connection_needed
    + 999    @smb_share_is_set
    +1000    def command_tree(self, arguments, command):
    +1001        # Command arguments required   : No
    +1002        # Active SMB connection needed : Yes
    +1003        # SMB share needed             : Yes
    +1004
    +1005        if len(arguments) == 0:
    +1006            self.sessionsManager.current_session.tree(path='.')
    +1007        else:
    +1008            self.sessionsManager.current_session.tree(path=arguments[0])
    +1009
    +1010    @command_arguments_required
    +1011    @active_smb_connection_needed
    +1012    @smb_share_is_set
    +1013    def command_umount(self, arguments, command):
    +1014        # Command arguments required   : Yes
    +1015        # Active SMB connection needed : Yes
    +1016        # SMB share needed             : Yes
    +1017
    +1018        local_mount_point = arguments[0]
    +1019
    +1020        self.logger.debug("Trying to unmount local mount point '%s'" % (local_mount_point))
    +1021        
    +1022        self.sessionsManager.current_session.umount(local_mount_point)
    +1023        
    +1024    @command_arguments_required
    +1025    @active_smb_connection_needed
    +1026    def command_use(self, arguments, command):
    +1027        # Command arguments required   : Yes
    +1028        # Active SMB connection needed : Yes
    +1029        # SMB share needed             : No
    +1030
    +1031        sharename = arguments[0]
    +1032
    +1033        # Reload the list of shares
    +1034        shares = self.sessionsManager.current_session.list_shares()
    +1035        shares = [s.lower() for s in shares.keys()]
    +1036
    +1037        if sharename.lower() in shares:
    +1038            self.sessionsManager.current_session.set_share(sharename)
    +1039        else:
    +1040            self.logger.error("No share named '%s' on '%s'" % (sharename, self.sessionsManager.current_session.host))
    +1041
    +1042    # Private functions =======================================================
    +1043
    +1044    def __load_modules(self):
    +1045        """
    +1046        Dynamically loads all Python modules from the 'modules' directory and stores them in the 'modules' dictionary.
    +1047        Each module is expected to be a Python file that contains a class with the same name as the file (minus the .py extension).
    +1048        The class must have at least two attributes: 'name' and 'description'.
    +1049        
    +1050        This method clears any previously loaded modules, constructs the path to the modules directory, and iterates over
    +1051        each file in that directory. If the file is a Python file (ends with .py and is not '__init__.py'), it attempts to
    +1052        import the module and access the class within it to add to the 'modules' dictionary.
    +1053        
    +1054        If debug mode is enabled in the configuration, it prints debug information about the loading process and the loaded modules.
    +1055        """
    +1056
    +1057        self.modules.clear()
    +1058
    +1059        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
    +1060        self.logger.debug("Loading modules from %s ..." % modules_dir)
    +1061        sys.path.extend([modules_dir])
    +1062
    +1063        for file in os.listdir(modules_dir):
    +1064            filepath = os.path.normpath(modules_dir + os.path.sep + file)
    +1065            if file.endswith('.py'):
    +1066                if os.path.isfile(filepath) and file not in ["__init__.py"]:
    +1067                    try:
    +1068                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
    +1069                        module = module_file.__getattribute__(file[:-3])
    +1070                        self.modules[module.name.lower()] = module
    +1071                    except AttributeError as e:
    +1072                        pass
    +1073
    +1074        if self.config.debug:
    +1075            if len(self.modules.keys()) == 0:
    +1076                self.logger.debug("Loaded 0 modules.")
    +1077            elif len(self.modules.keys()) == 1:
    +1078                self.logger.debug("Loaded 1 module:")
    +1079            else:
    +1080                self.logger.debug("Loaded %d modules:" % len(self.modules.keys()))
    +1081            for modulename in sorted(self.modules.keys()):
    +1082                self.logger.debug("%s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename]))
    +1083
    +1084        if self.commandCompleterObject is not None:
    +1085            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
    +1086
    +1087    def __prompt(self):
    +1088        """
    +1089        Prints the command prompt for the interactive shell.
    +1090
    +1091        This method constructs and returns the command prompt string based on the current state of the SMB session.
    +1092        The prompt indicates the connection status with a visual symbol and displays the current working directory
    +1093        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
    +1094
    +1095        Returns:
    +1096            str: The formatted command prompt string.
    +1097        """
    +1098
    +1099        # A session exists
    +1100        if self.sessionsManager.current_session is not None:
    +1101            # Check if the session is still active
    +1102            self.sessionsManager.current_session.ping_smb_session()
    +1103            if self.sessionsManager.current_session.connected:
    +1104                if self.config.no_colors:
    +1105                    connected_dot = "[v]"
    +1106                else:
    +1107                    connected_dot = "\x1b[1;92m⏺\x1b[0m"
    +1108            else:
    +1109                if self.config.no_colors:
    +1110                    connected_dot = "[x]"
    +1111                else:
    +1112                    connected_dot = "\x1b[1;91m⏺\x1b[0m"
    +1113            
    +1114            # Session ID if 
    +1115            session_prompt = ""
    +1116            if len(self.sessionsManager.sessions.keys()) >= 2:
    +1117                session_prompt = "[#%d]" % self.sessionsManager.current_session_id
    +1118
    +1119            # No share set yet
    +1120            if self.sessionsManager.current_session.smb_share is None:
    +1121                str_path = "\\\\%s\\" % self.sessionsManager.current_session.host
    +1122            # A share is set
    +1123            else:
    +1124                if len(self.sessionsManager.current_session.smb_cwd) == 0:
    +1125                    current_path = ""
    +1126                else:
    +1127                    current_path = self.sessionsManager.current_session.smb_cwd.strip(ntpath.sep) + ntpath.sep
    +1128                
    +1129                str_path = "\\\\%s\\%s\\%s" % (self.sessionsManager.current_session.host, self.sessionsManager.current_session.smb_share, current_path)
    +1130        # No active session
    +1131        else:
    +1132            connected_dot = ""
    +1133            session_prompt = ""
    +1134            str_path = "No active session"
    +1135
    +1136        # Build final prompt string
    +1137        if self.config.no_colors:
    +1138            str_prompt = "%s%s[%s]> " % (connected_dot, session_prompt, str_path)
    +1139        else:
    +1140            str_prompt = "%s%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, session_prompt, str_path)
    +1141
    +1142        return str_prompt
     
    @@ -1197,15 +1349,15 @@

    -
    28def command_arguments_required(func):
    -29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    -36    return wrapper
    +            
    29def command_arguments_required(func):
    +30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
    +37    return wrapper
     
    @@ -1223,17 +1375,17 @@

    -
    38def active_smb_connection_needed(func):
    -39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    -48    return wrapper
    +            
    39def active_smb_connection_needed(func):
    +40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
    +49    return wrapper
     
    @@ -1251,15 +1403,15 @@

    -
    50def smb_share_is_set(func):
    -51    def wrapper(*args, **kwargs):
    -52        self, arguments,command  = args[0], args[1], args[2]
    -53        if self.smbSession.smb_share is not None:
    -54            return func(*args, **kwargs)
    -55        else:
    -56            print("[!] You must open a share first, try the 'use <share>' command.")
    -57            return None
    -58    return wrapper
    +            
    51def smb_share_is_set(func):
    +52    def wrapper(*args, **kwargs):
    +53        self, arguments,command  = args[0], args[1], args[2]
    +54        if self.sessionsManager.current_session.smb_share is not None:
    +55            return func(*args, **kwargs)
    +56        else:
    +57            print("[!] You must open a share first, try the 'use <share>' command.")
    +58            return None
    +59    return wrapper
     
    @@ -1277,943 +1429,1088 @@

    -
     61class InteractiveShell(object):
    - 62    """
    - 63    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
    - 64    
    - 65    This class handles user input, executes commands, and manages the state of the SMB session. It provides
    - 66    a command line interface for users to interact with SMB shares, execute commands like directory listing,
    - 67    file transfer, and more.
    - 68
    - 69    Attributes:
    - 70        smbSession (SMBConnection): The active SMB connection session.
    - 71        debug (bool): Flag to enable or disable debug mode.
    - 72        smb_share (str): The current SMB share in use.
    - 73        smb_path (str): The current path within the SMB share.
    - 74        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
    - 75
    - 76    Methods:
    - 77        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
    - 78        run(self): Starts the command line interface loop, processing user input until exit.
    - 79    """
    - 80    
    - 81    def __init__(self, smbSession, config):
    - 82        # Objects
    - 83        self.smbSession = smbSession
    - 84        self.config = config
    - 85        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
    - 86        readline.set_completer(self.commandCompleterObject.complete)
    - 87        readline.parse_and_bind("tab: complete")
    - 88        readline.set_completer_delims("\n")
    - 89        # Additional modules
    - 90        self.modules = {}
    - 91        self.__load_modules()
    - 92
    - 93    def run(self):
    - 94        running = True
    - 95        while running:
    - 96            try:
    - 97                user_input = input(self.__prompt()).strip().split(" ")
    - 98                command, arguments = user_input[0].lower(), user_input[1:]
    - 99                
    -100                # Exit the command line
    -101                if command == "exit":
    -102                    running = False
    -103
    -104                elif command in self.commandCompleterObject.commands.keys():
    -105                    self.process_command(
    -106                        command=command, 
    -107                        arguments=arguments
    -108                    )
    -109
    -110                elif command.strip() == "":
    -111                    pass
    -112
    -113                # Fallback to unknown command
    -114                else:
    -115                    print("Unknown command. Type \"help\" for help.")
    -116
    -117            except KeyboardInterrupt as e:
    -118                print()
    -119
    -120            except EOFError as e:
    -121                print()
    -122                running = False
    -123
    -124            except Exception as e:
    -125                if self.config.debug:
    -126                    traceback.print_exc()
    -127                print("[!] Error: %s" % str(e))
    -128
    -129    def process_command(self, command, arguments=[]):
    -130        # Skip
    -131        if command.strip() == "":
    -132            pass
    -133        
    -134        # Display help
    -135        elif command == "help":
    -136            self.command_help(arguments, command)
    -137
    -138        # Cat the contents of a file
    -139        elif command == "bat":
    -140            self.command_bat(arguments, command)
    -141
    -142        # Cat the contents of a file
    -143        elif command == "cat":
    -144            self.command_cat(arguments, command)
    -145
    -146        # Closes the current SMB session
    -147        elif command == "close":
    -148            self.command_close(arguments, command)
    -149               
    -150        # Change directory in the current share
    -151        elif command == "cd":
    -152            self.command_cd(arguments, command)
    -153        
    -154        # debug
    -155        elif command == "debug":
    -156            self.command_debug(arguments, command)
    -157
    -158        # Get a file
    -159        elif command == "get":
    -160            self.command_get(arguments, command)
    -161
    -162        # SMB server info
    -163        elif command == "info":
    -164            self.command_info(arguments, command)
    -165
    -166        # List directory contents in a share
    -167        elif command in ["ls", "dir"]:
    -168            self.command_ls(arguments, command)
    -169
    -170        # Creates a new remote directory
    -171        elif command == "mkdir":
    -172            self.command_mkdir(arguments, command)
    -173
    -174        # Put a file
    -175        elif command == "put":
    -176            self.command_put(arguments, command)
    -177
    -178        # Shows the content of a local file
    -179        elif command == "lcat":
    -180            self.command_lcat(arguments, command)
    -181
    -182        # Changes the current local directory
    -183        elif command == "lcd":
    -184            self.command_lcd(arguments, command)
    -185
    -186        # Creates a copy of a local file
    -187        elif command == "lcp":
    -188            self.command_lcp(arguments, command)
    -189
    -190        # Pretty prints the content of a local file
    -191        elif command == "lbat":
    -192            self.command_lbat(arguments, command)
    -193
    -194        # Lists the contents of the current local directory
    -195        elif command == "lls":
    -196            self.command_lls(arguments, command)
    -197
    -198        # Creates a new local directory
    -199        elif command == "lmkdir":
    -200            self.command_lmkdir(arguments, command)
    -201
    -202        # Shows the current local directory
    -203        elif command == "lpwd":
    -204            self.command_lpwd(arguments, command)
    -205
    -206        # Renames a local file
    -207        elif command == "lrename":
    -208            self.command_lrename(arguments, command)
    -209        
    -210        # Removes a local file
    -211        elif command == "lrm":
    -212            self.command_lrm(arguments, command)
    -213
    -214        # Removes a local directory
    -215        elif command == "lrmdir":
    -216            self.command_lrmdir(arguments, command)
    -217
    -218        # Shows the current local directory
    -219        elif command == "ltree":
    -220            self.command_ltree(arguments, command)
    -221
    -222        # Modules
    -223        elif command == "module":
    -224            self.command_module(arguments, command)
    -225
    -226        # Creates a mount point of the remote share on the local machine
    -227        elif command == "mount":
    -228            self.command_mount(arguments, command)
    -229
    -230        # Reconnects the current SMB session
    -231        elif command in ["connect", "reconnect"]:
    -232            self.command_reconnect(arguments, command)
    -233
    -234        # Reset the TTY output
    -235        elif command == "reset":
    -236            self.command_reset(arguments, command)
    -237
    -238        # Removes a remote file
    -239        elif command == "rm":
    -240            self.command_rm(arguments, command)
    -241            
    -242        # Removes a remote directory
    -243        elif command == "rmdir":
    -244            self.command_rmdir(arguments, command)
    -245
    -246        # List shares
    -247        elif command == "sizeof":
    -248            self.command_sizeof(arguments, command)
    -249
    -250        # List shares
    -251        elif command == "shares":
    -252            self.command_shares(arguments, command)
    -253        
    -254        # Displays a tree view of the CWD
    -255        elif command == "tree":
    -256            self.command_tree(arguments, command)
    -257        
    -258        # Use a share
    -259        elif command == "use":
    -260            self.command_use(arguments, command)
    -261
    -262    # Commands ================================================================
    -263
    -264    def command_debug(self, arguments, command):
    -265        try:
    -266            pass
    -267        except Exception as e:
    -268            traceback.print_exc()
    -269
    -270    @command_arguments_required
    -271    @active_smb_connection_needed
    -272    @smb_share_is_set
    -273    def command_bat(self, arguments, command):
    -274        # Command arguments required   : Yes
    -275        # Active SMB connection needed : Yes
    -276        # SMB share needed             : Yes
    -277
    -278        path = ' '.join(arguments)
    -279        try:
    -280            rawcontents = self.smbSession.read_file(path=path)
    -281            if rawcontents is not None:
    -282                encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -283                if encoding is not None:
    -284                    filecontent = rawcontents.decode(encoding).rstrip()
    -285                    lexer = Syntax.guess_lexer(path=ntpath.basename(path), code=filecontent)
    -286                    # Some trickery for the files undetected by the lexer
    -287                    if lexer == "default":
    -288                        if '<?xml' in filecontent:
    -289                            lexer = "xml"
    -290                        elif '<html>' in filecontent:
    -291                            lexer = "html"
    -292                    syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    -293                    Console().print(syntax)
    -294                else:
    -295                    print("[!] Could not detect charset of '%s'." % path)
    -296        except impacket.smbconnection.SessionError as e:
    -297            print("[!] SMB Error: %s" % e)
    -298
    -299    @command_arguments_required
    -300    @active_smb_connection_needed
    -301    @smb_share_is_set
    -302    def command_cd(self, arguments, command):
    -303        # Command arguments required   : Yes
    -304        # Active SMB connection needed : Yes
    -305        # SMB share needed             : Yes
    -306
    -307        path = ' '.join(arguments)
    -308        try:
    -309            self.smbSession.set_cwd(path=path)
    -310        except impacket.smbconnection.SessionError as e:
    -311            print("[!] SMB Error: %s" % e)
    -312
    -313    @command_arguments_required
    -314    @active_smb_connection_needed
    -315    @smb_share_is_set
    -316    def command_cat(self, arguments, command):
    -317        # Command arguments required   : Yes
    -318        # Active SMB connection needed : Yes
    -319        # SMB share needed             : Yes
    -320
    -321        path = ' '.join(arguments)
    -322        try:
    -323            rawcontents = self.smbSession.read_file(path=path)
    -324            if rawcontents is not None:
    -325                encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -326                if encoding is not None:
    -327                    filecontent = rawcontents.decode(encoding).rstrip()
    -328                    print(filecontent)
    -329                else:
    -330                    print("[!] Could not detect charset of '%s'." % path)
    -331        except impacket.smbconnection.SessionError as e:
    -332            print("[!] SMB Error: %s" % e)
    -333
    -334    def command_close(self, arguments, command):
    -335        # Command arguments required   : No
    -336        # Active SMB connection needed : No
    -337        # SMB share needed             : No
    -338
    -339        self.smbSession.ping_smb_session()
    -340        if self.smbSession.connected:
    -341            self.smbSession.close_smb_session()
    -342
    -343    @command_arguments_required
    -344    @active_smb_connection_needed
    -345    @smb_share_is_set
    -346    def command_get(self, arguments, command):
    -347        # Command arguments required   : Yes
    -348        # Active SMB connection needed : Yes
    -349        # SMB share needed             : Yes
    -350
    -351        # Get files recursively
    -352        if arguments[0] == "-r":
    -353            path = ' '.join(arguments[1:]).replace('/', ntpath.sep)
    -354            try:
    -355                self.smbSession.get_file_recursively(path=path)
    -356            except impacket.smbconnection.SessionError as e:
    -357                print("[!] SMB Error: %s" % e)
    -358        # Get a single file
    -359        else:
    -360            path = ' '.join(arguments).replace('/', ntpath.sep)
    -361            try:
    -362                self.smbSession.get_file(path=path)
    -363            except impacket.smbconnection.SessionError as e:
    -364                print("[!] SMB Error: %s" % e)
    -365
    -366    def command_help(self, arguments, command):
    -367        # Command arguments required   : No
    -368        # Active SMB connection needed : No
    -369        # SMB share needed             : No
    -370
    -371        if len(arguments) != 0:
    -372            self.commandCompleterObject.print_help(command=arguments[0])
    -373        else:
    -374            self.commandCompleterObject.print_help(command=None)
    -375
    -376    @active_smb_connection_needed
    -377    def command_info(self, arguments, command):
    -378        # Command arguments required   : No
    -379        # Active SMB connection needed : Yes
    -380        # SMB share needed             : No
    -381
    -382        print_server_info = False
    -383        print_share_info = False
    -384        if len(arguments) != 0:
    -385            print_server_info = (arguments[0].lower() == "server")
    -386            print_share_info = (arguments[0].lower() == "share")
    -387        else:
    -388            print_server_info = True
    -389            print_share_info = True
    -390
    -391        try:
    -392            self.smbSession.info(
    -393                share=print_share_info,
    -394                server=print_server_info
    -395            )
    -396        except impacket.smbconnection.SessionError as e:
    -397            print("[!] SMB Error: %s" % e)
    -398
    -399    @command_arguments_required
    -400    def command_lcat(self, arguments, command):
    -401        # Command arguments required   : Yes
    -402        # Active SMB connection needed : No
    -403        # SMB share needed             : No
    -404
    -405        path = ' '.join(arguments)
    -406        try:
    -407            if os.path.exists(path=path):
    -408                f = open(path, 'rb')
    -409                rawcontents = f.read()
    -410                if rawcontents is not None:
    -411                    encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -412                    if encoding is not None:
    -413                        filecontent = rawcontents.decode(encoding).rstrip()
    -414                        print(filecontent)
    -415                    else:
    -416                        print("[!] Could not detect charset of '%s'." % path)
    -417            else:
    -418                print("[!] Local file '%s' does not exist." % path)
    -419        except impacket.smbconnection.SessionError as e:
    -420            print("[!] SMB Error: %s" % e)
    -421
    -422    @command_arguments_required
    -423    def command_lcd(self, arguments, command):
    -424        # Command arguments required   : Yes
    -425        # Active SMB connection needed : No
    -426        # SMB share needed             : No
    -427
    -428        path = ' '.join(arguments)
    -429        if os.path.exists(path=path):
    -430            if os.path.isdir(s=path):
    -431                os.chdir(path=path)
    -432            else:
    -433                print("[!] Path '%s' is not a directory." % path)
    -434        else:
    -435            print("[!] Directory '%s' does not exists." % path)
    -436
    -437    @command_arguments_required
    -438    def command_lcp(self, arguments, command):
    -439        # Command arguments required   : Yes
    -440        # Active SMB connection needed : No
    -441        # SMB share needed             : No
    -442
    -443        if len(arguments) == 2:
    -444            src_path = arguments[0]
    -445            dst_path = arguments[1]
    -446            if os.path.exists(path=src_path):
    -447                try:
    -448                    shutil.copyfile(src=src_path, dst=dst_path)
    -449                except shutil.SameFileError as err:
    -450                    print("[!] Error: %s" % err)
    -451            else:
    -452                print("[!] File '%s' does not exists." % src_path)
    -453        else:
    -454            self.commandCompleterObject.print_help(command=command)
    -455
    -456    @command_arguments_required
    -457    def command_lbat(self, arguments, command):
    -458        # Command arguments required   : Yes
    -459        # Active SMB connection needed : No
    -460        # SMB share needed             : No
    -461
    -462        path = ' '.join(arguments)
    -463        try:
    -464            if os.path.exists(path=path):
    -465                f = open(path, 'rb')
    -466                rawcontents = f.read()
    -467                if rawcontents is not None:
    -468                    encoding = charset_normalizer.detect(rawcontents)["encoding"]
    -469                    if encoding is not None:
    -470                        filecontent = rawcontents.decode(encoding).rstrip()
    -471                        lexer = Syntax.guess_lexer(path=ntpath.basename(path), code=filecontent)
    -472                        # Some trickery for the files undetected by the lexer
    -473                        if lexer == "default":
    -474                            if '<?xml' in filecontent:
    -475                                lexer = "xml"
    -476                            elif '<html>' in filecontent:
    -477                                lexer = "html"
    -478                        syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    -479                        Console().print(syntax)
    -480                    else:
    -481                        print("[!] Could not detect charset of '%s'." % path)
    -482            else:
    -483                print("[!] Local file '%s' does not exist." % path)
    -484        except impacket.smbconnection.SessionError as e:
    -485            print("[!] SMB Error: %s" % e)
    -486
    -487    def command_lls(self, arguments, command):
    -488        # Command arguments required   : No
    -489        # Active SMB connection needed : No
    -490        # SMB share needed             : No
    -491
    -492        if len(arguments) == 0:
    -493            path = '.'
    -494        else:
    -495            path = ' '.join(arguments)
    -496
    -497        # lls <directory>
    -498        if os.path.isdir(path):
    -499            directory_contents = os.listdir(path=path)
    -500            for entryname in sorted(directory_contents):
    -501                path_to_file = path + os.path.sep + entryname
    -502                rights_str = unix_permissions(path_to_file)
    -503                size_str = b_filesize(os.path.getsize(filename=path_to_file))
    -504                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    -505
    -506                if os.path.isdir(s=entryname):
    -507                    if self.config.no_colors:
    -508                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -509                    else:
    -510                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -511                else:
    -512                    if self.config.no_colors:
    -513                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    -514                    else:
    -515                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    -516        # lls <file>
    -517        elif os.path.isfile(path):
    -518            rights_str = unix_permissions(path)
    -519            size_str = b_filesize(os.path.getsize(filename=path))
    -520            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    -521            if self.config.no_colors:
    -522                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    -523            else:
    -524               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    -525        else:
    -526            print("[!] No such file or directory.")
    -527
    -528    @command_arguments_required
    -529    def command_lmkdir(self, arguments, command):
    -530        # Command arguments required   : Yes
    -531        # Active SMB connection needed : No
    -532        # SMB share needed             : No
    -533
    -534        path = ' '.join(arguments)
    -535
    -536        # Split each dir
    -537        if os.path.sep in path:
    -538            path = path.strip(os.path.sep).split(os.path.sep)
    -539        else:
    -540            path = [path]
    -541
    -542        # Create each dir in the path
    -543        for depth in range(1, len(path)+1):
    -544            tmp_path = os.path.sep.join(path[:depth])
    -545            if not os.path.exists(tmp_path):
    -546                os.mkdir(path=tmp_path)
    -547
    -548    def command_lpwd(self, arguments, command):
    -549        # Command arguments required   : No
    -550        # Active SMB connection needed : No
    -551        # SMB share needed             : No
    -552
    -553        print(os.getcwd())
    -554
    -555    @command_arguments_required
    -556    def command_lrename(self, arguments, command):
    -557        # Command arguments required   : Yes
    -558        # Active SMB connection needed : No
    -559        # SMB share needed             : No
    -560
    -561        if len(arguments) == 2:
    -562            os.rename(src=arguments[0], dst=arguments[1])
    -563        else:
    -564            self.commandCompleterObject.print_help(command=command)
    -565
    -566    @command_arguments_required
    -567    def command_lrm(self, arguments, command):
    -568        # Command arguments required   : Yes
    -569        # Active SMB connection needed : No
    -570        # SMB share needed             : No
    -571
    -572        path = ' '.join(arguments)
    -573        if os.path.exists(path):
    -574            if not os.path.isdir(s=path):
    -575                try:
    -576                    os.remove(path=path)
    -577                except Exception as e:
    -578                    print("[!] Error removing file '%s' : %s" % path)
    -579            else:
    -580                print("[!] Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
    -581        else:
    -582            print("[!] Path '%s' does not exist." % path)
    -583
    -584    @command_arguments_required
    -585    def command_lrmdir(self, arguments, command):
    -586        # Command arguments required   : Yes
    -587        # Active SMB connection needed : No
    -588        # SMB share needed             : No
    -589
    -590        path = ' '.join(arguments)
    -591        if os.path.exists(path):
    -592            if os.path.isdir(s=path):
    -593                try:
    -594                    shutil.rmtree(path=path)
    -595                except Exception as e:
    -596                    print("[!] Error removing directory '%s' : %s" % path)
    -597            else:
    -598                print("[!] Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
    -599        else:
    -600            print("[!] Path '%s' does not exist." % path)
    -601
    -602    def command_ltree(self, arguments, command):
    -603        # Command arguments required   : No
    -604        # Active SMB connection needed : No
    -605        # SMB share needed             : No
    -606
    -607        if len(arguments) == 0:
    -608            local_tree(path='.', config=self.config)
    -609        else:
    -610            local_tree(path=' '.join(arguments), config=self.config)
    -611
    -612    @active_smb_connection_needed
    -613    @smb_share_is_set
    -614    def command_ls(self, arguments, command):
    -615        # Command arguments required   : No
    -616        # Active SMB connection needed : Yes
    -617        # SMB share needed             : Yes
    -618
    -619        # Read the files
    -620        directory_contents = self.smbSession.list_contents(path=' '.join(arguments))
    -621
    -622        for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
    -623            windows_ls_entry(directory_contents[longname], self.config)
    -624            
    -625    @command_arguments_required
    -626    @active_smb_connection_needed
    -627    @smb_share_is_set
    -628    def command_mkdir(self, arguments, command):
    -629        # Command arguments required   : Yes
    -630        # Active SMB connection needed : Yes
    -631        # SMB share needed             : Yes
    -632
    -633        path = ' '.join(arguments)
    -634        self.smbSession.mkdir(path=path)
    -635
    -636    @command_arguments_required
    -637    @active_smb_connection_needed
    -638    @smb_share_is_set
    -639    def command_module(self, arguments, command):
    -640        module_name = arguments[0]
    -641
    -642        if module_name in self.modules.keys():
    -643            module = self.modules[module_name](self.smbSession, self.config)
    -644            module.run(' '.join(arguments[1:]))
    -645        else:
    -646            print("[!] Module '%s' does not exist." % module_name)
    -647
    -648    @command_arguments_required
    -649    @active_smb_connection_needed
    -650    @smb_share_is_set
    -651    def command_mount(self, arguments, command):
    -652        # Command arguments required   : Yes
    -653        # Active SMB connection needed : Yes
    -654        # SMB share needed             : Yes
    -655
    -656        if len(arguments) == 2:
    -657            remote_path = arguments[0]
    -658            if not remote_path.startswith(ntpath.sep):
    -659                remote_path = self.smbSession.smb_cwd + ntpath.sep + remote_path
    -660
    -661            local_mount_point = arguments[1]
    -662
    -663            if self.config.debug:
    -664                print("[debug] Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point))
    -665
    -666            try:
    -667                self.smbSession.mount(local_mount_point, remote_path)
    -668            except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e:
    -669                self.smbSession.umount(local_mount_point)
    -670        else:
    -671            self.commandCompleterObject.print_help(command=command)
    -672
    -673    @command_arguments_required
    -674    @active_smb_connection_needed
    -675    @smb_share_is_set
    -676    def command_put(self, arguments, command):
    -677        # Command arguments required   : Yes
    -678        # Active SMB connection needed : Yes
    -679        # SMB share needed             : Yes
    -680
    -681        # Put files recursively
    -682        if arguments[0] == "-r":
    -683            localpath = ' '.join(arguments[1:])
    -684            try:
    -685                self.smbSession.put_file_recursively(localpath=localpath)
    -686            except impacket.smbconnection.SessionError as e:
    -687                print("[!] SMB Error: %s" % e)
    -688
    -689        # Put a single file
    -690        else:
    -691            localpath = ' '.join(arguments)
    -692            try:
    -693                self.smbSession.put_file(localpath=localpath)
    -694            except impacket.smbconnection.SessionError as e:
    -695                print("[!] SMB Error: %s" % e)
    -696
    -697    def command_reconnect(self, arguments, command):
    -698        # Command arguments required   : No
    -699        # Active SMB connection needed : No
    -700        # SMB share needed             : No
    -701
    -702        self.smbSession.ping_smb_session()
    -703        if self.smbSession.connected:
    -704            self.smbSession.close_smb_session()
    -705            self.smbSession.init_smb_session()
    -706        else:
    -707            self.smbSession.init_smb_session()
    -708
    -709    def command_reset(self, arguments, command):
    -710        # Command arguments required   : No
    -711        # Active SMB connection needed : No
    -712        # SMB share needed             : No
    -713        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    -714        sys.stdout.write('\x1b[v')  
    -715        sys.stdout.write('\x1b[o') # Reset
    -716        sys.stdout.flush()
    -717
    -718    @command_arguments_required
    -719    @active_smb_connection_needed
    -720    @smb_share_is_set
    -721    def command_rm(self, arguments, command):
    -722        # Command arguments required   : Yes
    -723        # Active SMB connection needed : Yes
    -724        # SMB share needed             : Yes
    -725
    -726        path = ' '.join(arguments)
    -727        if '*' in path:
    -728            self.smbSession.rm(path=path)
    -729        elif self.smbSession.path_exists(path):
    -730            if self.smbSession.path_isfile(path):
    -731                try:
    -732                    self.smbSession.rm(path=path)
    -733                except Exception as e:
    -734                    print("[!] Error removing file '%s' : %s" % path)
    -735            else:
    -736                print("[!] Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path)
    -737        else:
    -738            print("[!] Remote file '%s' does not exist." % path)
    -739
    -740    @command_arguments_required
    -741    @active_smb_connection_needed
    -742    @smb_share_is_set
    -743    def command_rmdir(self, arguments, command):
    -744        # Command arguments required   : Yes
    -745        # Active SMB connection needed : Yes
    -746        # SMB share needed             : Yes
    -747
    -748        path = ' '.join(arguments)
    -749        if self.smbSession.path_exists(path):
    -750            if self.smbSession.path_isdir(path):
    -751                try:
    -752                    self.smbSession.rmdir(path=path)
    -753                except Exception as e:
    -754                    print("[!] Error removing directory '%s' : %s" % path)
    -755            else:
    -756                print("[!] Cannot delete '%s': This is a file, use 'rm <file>' instead." % path)
    -757        else:
    -758            print("[!] Remote directory '%s' does not exist." % path)
    -759
    -760    @active_smb_connection_needed
    -761    @smb_share_is_set
    -762    def command_sizeof(self, arguments, command):
    -763        # Command arguments required   : Yes
    -764        # Active SMB connection needed : Yes
    -765        # SMB share needed             : Yes
    -766
    -767        class RecursiveSizeOfPrint(object):
    -768            def __init__(self, entry, smbSession, config):
    -769                self.entry = entry
    -770                self.config = config
    -771                self.smbSession = smbSession
    -772                self.size = 0
    -773            
    -774            def update(self, entry, fullpath, depth):
    -775                self.size += entry.get_filesize()
    -776                self.print(end='\r')
    -777            
    -778            def print(self, end='\n'):
    -779                #
    -780                if self.entry.is_directory():
    -781                    if self.config.no_colors:
    -782                        path = "%s\\" % self.entry.get_longname()
    -783                    else:
    -784                        path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname()
    -785                # 
    -786                else:
    -787                    if self.config.no_colors:
    -788                        path = "%s" % self.entry.get_longname()
    -789                    else:
    -790                        path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname()
    -791                print("%10s  %s" % (b_filesize(self.size), path), end=end)
    -792
    -793        entries = []
    -794        if len(arguments) == 0:
    -795            entries = self.smbSession.list_contents()
    -796            entries = [entry for name, entry in entries.items() if name not in ['.', '..']]
    -797        else:
    -798            entry = self.smbSession.get_entry(path=' '.join(arguments))
    -799            entries = []
    -800            if entry is not None:
    -801                entries = [entry]
    -802            else:
    -803                print("[!] Path '%s' does not exist." % ' '.join(arguments))
    -804
    -805        total = 0
    -806        for entry in entries:
    -807            rsop = RecursiveSizeOfPrint(entry=entry, smbSession=self.smbSession, config=self.config)
    -808            # Directory
    -809            if entry.is_directory():
    -810                self.smbSession.find(
    -811                    paths=[entry.get_longname()],
    -812                    callback=rsop.update
    -813                )
    -814            # File
    -815            else:
    -816                rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0)
    -817            # Close the print
    -818            rsop.print()
    -819            total += rsop.size
    -820        
    -821        if len(entries) > 1:
    -822            print("──────────────────────")
    -823            print("     total  %s" % b_filesize(total))
    -824
    -825    @active_smb_connection_needed
    -826    def command_shares(self, arguments, command):
    -827        # Command arguments required   : No
    -828        # Active SMB connection needed : Yes
    -829        # SMB share needed             : No
    -830
    -831        do_check_rights = False
    -832        if len(arguments) != 0:
    -833            if arguments[0] == "rights":
    -834                do_check_rights = True
    -835
    -836        shares = self.smbSession.list_shares()
    -837        if len(shares.keys()) != 0:
    -838            table = Table(title=None)
    -839            table.add_column("Share")
    -840            table.add_column("Visibility")
    -841            table.add_column("Type")
    -842            table.add_column("Description", justify="left")
    -843            if do_check_rights:
    -844                table.add_column("Rights")
    -845
    -846            for sharename in sorted(shares.keys()):
    -847                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
    -848
    -849                is_hidden = bool(sharename.endswith('$'))
    -850                if is_hidden:
    -851                    str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]"
    -852                    str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]"
    -853                    str_types = "[bold bright_blue]" + types + "[/bold bright_blue]"
    -854                    str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]"
    -855                else:
    -856                    str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]"
    -857                    str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]"
    -858                    str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]"
    -859                    str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]"
    -860
    -861                if do_check_rights:
    -862                    access_rights = self.smbSession.test_rights(sharename=shares[sharename]["name"])
    -863                    str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    -864                    if access_rights["readable"] and access_rights["writable"]:
    -865                        str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]"
    -866                    elif access_rights["readable"]:
    -867                        str_access_rights = "[bold green]READ[/bold green]"
    -868                    elif access_rights["writable"]:
    -869                        # Without READ?? This should not happen IMHO
    -870                        str_access_rights = "[bold red]WRITE[/bold red]"
    -871                    else:
    -872                        str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    -873
    -874                if do_check_rights:
    -875                    table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights)
    -876                else:
    -877                    table.add_row(str_sharename, str_hidden, str_types, str_comment)
    -878
    -879            Console().print(table)
    -880        else:
    -881            print("[!] No share served on '%s'" % self.smbSession.address)
    -882
    -883    @active_smb_connection_needed
    -884    @smb_share_is_set
    -885    def command_tree(self, arguments, command):
    -886        # Command arguments required   : No
    -887        # Active SMB connection needed : Yes
    -888        # SMB share needed             : Yes
    -889
    -890        if len(arguments) == 0:
    -891            self.smbSession.tree(path='.')
    -892        else:
    -893            self.smbSession.tree(path=' '.join(arguments))
    -894
    -895    @command_arguments_required
    -896    @active_smb_connection_needed
    -897    @smb_share_is_set
    -898    def command_umount(self, arguments, command):
    -899        # Command arguments required   : Yes
    -900        # Active SMB connection needed : Yes
    -901        # SMB share needed             : Yes
    -902
    -903        local_mount_point = arguments[0]
    -904
    -905        if self.config.debug:
    -906            print("[debug] Trying to unmount local mount point '%s'" % (local_mount_point))
    -907        
    -908        self.smbSession.umount(local_mount_point)
    -909        
    -910    @command_arguments_required
    -911    @active_smb_connection_needed
    -912    def command_use(self, arguments, command):
    -913        # Command arguments required   : Yes
    -914        # Active SMB connection needed : Yes
    -915        # SMB share needed             : No
    -916
    -917        sharename = ' '.join(arguments)
    -918        # Reload the list of shares
    -919        shares = self.smbSession.list_shares()
    -920        shares = [s.lower() for s in shares.keys()]
    -921        if sharename.lower() in shares:
    -922            self.smbSession.set_share(sharename)
    -923        else:
    -924            print("[!] No share named '%s' on '%s'" % (sharename, self.smbSession.address))
    -925
    -926    # Private functions =======================================================
    -927
    -928    def __load_modules(self):
    -929
    -930        self.modules.clear()
    -931
    -932        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
    -933        if self.config.debug:
    -934            print("[debug] Loading modules from %s ..." % modules_dir)
    -935        sys.path.extend([modules_dir])
    -936
    -937        for file in os.listdir(modules_dir):
    -938            filepath = os.path.normpath(modules_dir + os.path.sep + file)
    -939            if file.endswith('.py'):
    -940                if os.path.isfile(filepath) and file not in ["__init__.py"]:
    -941                    try:
    -942                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
    -943                        module = module_file.__getattribute__(file[:-3])
    -944                        self.modules[module.name.lower()] = module
    -945                    except AttributeError as e:
    -946                        pass
    -947
    -948        if self.config.debug:
    -949            if len(self.modules.keys()) == 0:
    -950                print("[debug] Loaded 0 modules.")
    -951            elif len(self.modules.keys()) == 1:
    -952                print("[debug] Loaded 1 module:")
    -953            else:
    -954                print("[debug] Loaded %d modules:" % len(self.modules.keys()))
    -955            for modulename in sorted(self.modules.keys()):
    -956                print("[debug] %s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename]))
    -957
    -958        if self.commandCompleterObject is not None:
    -959            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
    -960
    -961    def __prompt(self):
    -962        """
    -963        Prints the command prompt for the interactive shell.
    -964
    -965        This method constructs and returns the command prompt string based on the current state of the SMB session.
    -966        The prompt indicates the connection status with a visual symbol and displays the current working directory
    -967        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
    -968
    -969        Returns:
    -970            str: The formatted command prompt string.
    -971        """
    -972
    -973        self.smbSession.ping_smb_session()
    -974        if self.smbSession.connected:
    -975            if self.config.no_colors:
    -976                connected_dot = "[v]"
    -977            else:
    -978                connected_dot = "\x1b[1;92m⏺\x1b[0m"
    -979        else:
    -980            if self.config.no_colors:
    -981                connected_dot = "[x]"
    -982            else:
    -983                connected_dot = "\x1b[1;91m⏺\x1b[0m"
    -984        
    -985        if self.smbSession.smb_share is None:
    -986            if self.config.no_colors:
    -987                str_prompt = "%s[\\\\%s\\]> " % (connected_dot, self.smbSession.address)
    -988            else:
    -989                str_prompt = "%s[\x1b[1;94m\\\\%s\\\x1b[0m]> " % (connected_dot, self.smbSession.address)
    -990        else:
    -991            str_path = "\\\\%s\\%s\\%s" % (self.smbSession.address, self.smbSession.smb_share, self.smbSession.smb_cwd.lstrip(ntpath.sep))
    -992            if self.config.no_colors:
    -993                str_prompt = "%s[%s]> " % (connected_dot, str_path)
    -994            else:
    -995                str_prompt = "%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, str_path)
    -996
    -997        return str_prompt
    +            
      62class InteractiveShell(object):
    +  63    """
    +  64    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
    +  65    
    +  66    This class handles user input, executes commands, and manages the state of the SMB session. It provides
    +  67    a command line interface for users to interact with SMB shares, execute commands like directory listing,
    +  68    file transfer, and more.
    +  69
    +  70    Attributes:
    +  71        smbSession (SMBConnection): The active SMB connection session.
    +  72        debug (bool): Flag to enable or disable debug mode.
    +  73        smb_share (str): The current SMB share in use.
    +  74        smb_path (str): The current path within the SMB share.
    +  75        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
    +  76
    +  77    Methods:
    +  78        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
    +  79        run(self): Starts the command line interface loop, processing user input until exit.
    +  80    """
    +  81
    +  82    running = True
    +  83    modules = {}
    +  84    
    +  85    def __init__(self, sessionsManager, config, logger):
    +  86        # Objects
    +  87        self.sessionsManager = sessionsManager
    +  88        self.config = config
    +  89        self.logger = logger
    +  90        # Internals
    +  91        self.commandCompleterObject = CommandCompleter(
    +  92            smbSession=self.sessionsManager.current_session,
    +  93            config=self.config,
    +  94            logger=self.logger,
    +  95        )
    +  96        readline.set_completer(self.commandCompleterObject.complete)
    +  97        readline.parse_and_bind("tab: complete")
    +  98        readline.set_completer_delims("\n")
    +  99        # Additional modules
    + 100        self.__load_modules()
    + 101
    + 102    def run(self):
    + 103        # Read commands from script file first
    + 104        if self.config.startup_script:
    + 105            f = open(self.config.startup_script, 'r')
    + 106            for line in f.readlines():
    + 107                try:
    + 108                    self.logger.print("%s%s" % (self.__prompt(), line.strip()))
    + 109                    readline.add_history(line.strip())
    + 110                    self.process_line(commandLine=line.strip())
    + 111                except KeyboardInterrupt as e:
    + 112                    self.logger.print()
    + 113
    + 114                except EOFError as e:
    + 115                    self.logger.print()
    + 116                    running = False
    + 117
    + 118                except Exception as err:
    + 119                    if self.config.debug:
    + 120                        traceback.print_exc()
    + 121                    self.logger.error(str(err))
    + 122
    + 123        # Then interactive console
    + 124        if not self.config.not_interactive:
    + 125            while self.running:
    + 126                try:
    + 127                    user_input = input(self.__prompt()).strip()
    + 128                    self.process_line(commandLine=user_input)
    + 129                except KeyboardInterrupt as e:
    + 130                    self.logger.print()
    + 131
    + 132                except EOFError as e:
    + 133                    self.logger.print()
    + 134                    running = False
    + 135
    + 136                except Exception as err:
    + 137                    if self.config.debug:
    + 138                        traceback.print_exc()
    + 139                    self.logger.error(str(err))
    + 140
    + 141    def process_line(self, commandLine):
    + 142        # Split and parse the commandLine
    + 143        tokens = shlex.split(commandLine)
    + 144        if len(tokens) == 0:
    + 145            command = ""
    + 146            arguments = []
    + 147        elif len(tokens) == 1:
    + 148            command = tokens[0].lower()
    + 149            arguments = []
    + 150        else:
    + 151            command = tokens[0].lower()
    + 152            arguments = tokens[1:]
    + 153        
    + 154        # Skip
    + 155        if command.strip() == "":
    + 156            pass
    + 157        # Execute the command
    + 158        elif command in self.commandCompleterObject.commands.keys():
    + 159
    + 160            # Exit the command line
    + 161            if command in ["exit", "quit"]:
    + 162                self.running = False
    + 163            
    + 164            # Display help
    + 165            elif command == "help":
    + 166                self.command_help(arguments, command)
    + 167
    + 168            # Cat the contents of a file
    + 169            elif command == "bat":
    + 170                self.command_bat(arguments, command)
    + 171
    + 172            # Cat the contents of a file
    + 173            elif command == "cat":
    + 174                self.command_cat(arguments, command)
    + 175
    + 176            # Closes the current SMB session
    + 177            elif command == "close":
    + 178                self.command_close(arguments, command)
    + 179                
    + 180            # Change directory in the current share
    + 181            elif command == "cd":
    + 182                self.command_cd(arguments, command)
    + 183            
    + 184            # debug
    + 185            elif command == "debug":
    + 186                self.command_debug(arguments, command)
    + 187
    + 188            # Get a file
    + 189            elif command == "get":
    + 190                self.command_get(arguments, command)
    + 191
    + 192            # SMB server info
    + 193            elif command == "info":
    + 194                self.command_info(arguments, command)
    + 195
    + 196            # List directory contents in a share
    + 197            elif command in ["ls", "dir"]:
    + 198                self.command_ls(arguments, command)
    + 199
    + 200            # Creates a new remote directory
    + 201            elif command == "mkdir":
    + 202                self.command_mkdir(arguments, command)
    + 203
    + 204            # Put a file
    + 205            elif command == "put":
    + 206                self.command_put(arguments, command)
    + 207
    + 208            # Shows the content of a local file
    + 209            elif command == "lcat":
    + 210                self.command_lcat(arguments, command)
    + 211
    + 212            # Changes the current local directory
    + 213            elif command == "lcd":
    + 214                self.command_lcd(arguments, command)
    + 215
    + 216            # Creates a copy of a local file
    + 217            elif command == "lcp":
    + 218                self.command_lcp(arguments, command)
    + 219
    + 220            # Pretty prints the content of a local file
    + 221            elif command == "lbat":
    + 222                self.command_lbat(arguments, command)
    + 223
    + 224            # Lists the contents of the current local directory
    + 225            elif command == "lls":
    + 226                self.command_lls(arguments, command)
    + 227
    + 228            # Creates a new local directory
    + 229            elif command == "lmkdir":
    + 230                self.command_lmkdir(arguments, command)
    + 231
    + 232            # Shows the current local directory
    + 233            elif command == "lpwd":
    + 234                self.command_lpwd(arguments, command)
    + 235
    + 236            # Renames a local file
    + 237            elif command == "lrename":
    + 238                self.command_lrename(arguments, command)
    + 239            
    + 240            # Removes a local file
    + 241            elif command == "lrm":
    + 242                self.command_lrm(arguments, command)
    + 243
    + 244            # Removes a local directory
    + 245            elif command == "lrmdir":
    + 246                self.command_lrmdir(arguments, command)
    + 247
    + 248            # Shows the current local directory
    + 249            elif command == "ltree":
    + 250                self.command_ltree(arguments, command)
    + 251
    + 252            # Modules
    + 253            elif command == "module":
    + 254                self.command_module(arguments, command)
    + 255
    + 256            # Creates a mount point of the remote share on the local machine
    + 257            elif command == "mount":
    + 258                self.command_mount(arguments, command)
    + 259
    + 260            # Reconnects the current SMB session
    + 261            elif command in ["connect", "reconnect"]:
    + 262                self.command_reconnect(arguments, command)
    + 263
    + 264            # Reset the TTY output
    + 265            elif command == "reset":
    + 266                self.command_reset(arguments, command)
    + 267
    + 268            # Removes a remote file
    + 269            elif command == "rm":
    + 270                self.command_rm(arguments, command)
    + 271                
    + 272            # Removes a remote directory
    + 273            elif command == "rmdir":
    + 274                self.command_rmdir(arguments, command)
    + 275
    + 276            # Sessions management
    + 277            elif command == "sessions":
    + 278                self.sessionsManager.process_command_line(arguments)
    + 279
    + 280            # List shares
    + 281            elif command == "sizeof":
    + 282                self.command_sizeof(arguments, command)
    + 283
    + 284            # List shares
    + 285            elif command == "shares":
    + 286                self.command_shares(arguments, command)
    + 287            
    + 288            # Displays a tree view of the CWD
    + 289            elif command == "tree":
    + 290                self.command_tree(arguments, command)
    + 291            
    + 292            # Use a share
    + 293            elif command == "use":
    + 294                self.command_use(arguments, command)
    + 295        
    + 296        # Fallback to unknown command   
    + 297        else:
    + 298            self.logger.print("Unknown command. Type \"help\" for help.")
    + 299
    + 300    # Commands ================================================================
    + 301
    + 302    def command_debug(self, arguments, command):
    + 303        try:
    + 304            self.logger.print("[debug] command    = '%s'" % command)
    + 305            self.logger.print("[debug] arguments  = %s" % arguments)
    + 306        except Exception as e:
    + 307            traceback.print_exc()
    + 308
    + 309    @command_arguments_required
    + 310    @active_smb_connection_needed
    + 311    @smb_share_is_set
    + 312    def command_bat(self, arguments, command):
    + 313        # Command arguments required   : Yes
    + 314        # Active SMB connection needed : Yes
    + 315        # SMB share needed             : Yes
    + 316
    + 317        # Parse wildcards
    + 318        files_and_directories = resolve_local_files(arguments)
    + 319
    + 320        for path_to_file in files_and_directories:
    + 321            if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file):
    + 322                # Read the file
    + 323                try:    
    + 324                    rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file)
    + 325                    if rawcontents is not None:
    + 326                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 327                        if encoding is not None:
    + 328                            filecontent = rawcontents.decode(encoding).rstrip()
    + 329                            lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent)
    + 330                            # Some trickery for the files undetected by the lexer
    + 331                            if lexer == "default":
    + 332                                if '<?xml' in filecontent:
    + 333                                    lexer = "xml"
    + 334                                elif '<html>' in filecontent:
    + 335                                    lexer = "html"
    + 336                            syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    + 337                            if len(files_and_directories) > 1:
    + 338                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 339                            Console().print(syntax)
    + 340                        else:
    + 341                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 342                except impacket.smbconnection.SessionError as e:
    + 343                    self.logger.error("[!] SMB Error: %s" % e)
    + 344
    + 345    @command_arguments_required
    + 346    @active_smb_connection_needed
    + 347    @smb_share_is_set
    + 348    def command_cd(self, arguments, command):
    + 349        # Command arguments required   : Yes
    + 350        # Active SMB connection needed : Yes
    + 351        # SMB share needed             : Yes
    + 352
    + 353        try:
    + 354            self.sessionsManager.current_session.set_cwd(path=arguments[0])
    + 355        except impacket.smbconnection.SessionError as e:
    + 356            self.logger.error("[!] SMB Error: %s" % e)
    + 357
    + 358    @command_arguments_required
    + 359    @active_smb_connection_needed
    + 360    @smb_share_is_set
    + 361    def command_cat(self, arguments, command):
    + 362        # Command arguments required   : Yes
    + 363        # Active SMB connection needed : Yes
    + 364        # SMB share needed             : Yes
    + 365
    + 366        # Parse wildcards
    + 367        files_and_directories = resolve_local_files(arguments)
    + 368
    + 369        for path_to_file in files_and_directories:
    + 370            if self.sessionsManager.current_session.path_isfile(pathFromRoot=path_to_file):
    + 371                # Read the file
    + 372                try:
    + 373                    rawcontents = self.sessionsManager.current_session.read_file(path=path_to_file)
    + 374                    if rawcontents is not None:
    + 375                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 376                        if encoding is not None:
    + 377                            filecontent = rawcontents.decode(encoding).rstrip()
    + 378                            if len(files_and_directories) > 1:
    + 379                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 380                            self.logger.print(filecontent)
    + 381                        else:
    + 382                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 383                except impacket.smbconnection.SessionError as e:
    + 384                    self.logger.error("[!] SMB Error: %s" % e)
    + 385
    + 386    def command_close(self, arguments, command):
    + 387        # Command arguments required   : No
    + 388        # Active SMB connection needed : No
    + 389        # SMB share needed             : No
    + 390
    + 391        self.sessionsManager.current_session.ping_smb_session()
    + 392        if self.sessionsManager.current_session.connected:
    + 393            self.sessionsManager.current_session.close_smb_session()
    + 394
    + 395    @command_arguments_required
    + 396    @active_smb_connection_needed
    + 397    @smb_share_is_set
    + 398    def command_get(self, arguments, command):
    + 399        # Command arguments required   : Yes
    + 400        # Active SMB connection needed : Yes
    + 401        # SMB share needed             : Yes
    + 402
    + 403        is_recursive = False
    + 404        while '-r' in arguments:
    + 405            is_recursive = True
    + 406            arguments.remove('-r')
    + 407
    + 408        # Parse wildcards
    + 409        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 410
    + 411        # 
    + 412        for remotepath in files_and_directories:
    + 413            try:
    + 414                if is_recursive and self.sessionsManager.current_session.path_isdir(remotepath):
    + 415                    # Get files recursively
    + 416                    self.sessionsManager.current_session.get_file_recursively(path=remotepath)
    + 417                else:
    + 418                    # Get this single file
    + 419                    self.sessionsManager.current_session.get_file(path=remotepath)
    + 420            except impacket.smbconnection.SessionError as e:
    + 421                self.logger.error("[!] SMB Error: %s" % e)
    + 422
    + 423    def command_help(self, arguments, command):
    + 424        # Command arguments required   : No
    + 425        # Active SMB connection needed : No
    + 426        # SMB share needed             : No
    + 427
    + 428        if len(arguments) != 0:
    + 429            self.commandCompleterObject.print_help(command=arguments[0])
    + 430        else:
    + 431            self.commandCompleterObject.print_help(command=None)
    + 432
    + 433    @active_smb_connection_needed
    + 434    def command_info(self, arguments, command):
    + 435        # Command arguments required   : No
    + 436        # Active SMB connection needed : Yes
    + 437        # SMB share needed             : No
    + 438
    + 439        print_server_info = False
    + 440        print_share_info = False
    + 441        if len(arguments) != 0:
    + 442            print_server_info = (arguments[0].lower() == "server")
    + 443            print_share_info = (arguments[0].lower() == "share")
    + 444        else:
    + 445            print_server_info = True
    + 446            print_share_info = True
    + 447
    + 448        try:
    + 449            self.sessionsManager.current_session.info(
    + 450                share=print_share_info,
    + 451                server=print_server_info
    + 452            )
    + 453        except impacket.smbconnection.SessionError as e:
    + 454            self.logger.error("[!] SMB Error: %s" % e)
    + 455
    + 456    @command_arguments_required
    + 457    def command_lbat(self, arguments, command):
    + 458        # Command arguments required   : Yes
    + 459        # Active SMB connection needed : No
    + 460        # SMB share needed             : No
    + 461
    + 462        # Parse wildcards
    + 463        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 464
    + 465        for path_to_file in files_and_directories:
    + 466            # Read the file
    + 467            try:
    + 468                if os.path.exists(path=path_to_file):
    + 469                    f = open(path_to_file, 'rb')
    + 470                    rawcontents = f.read()
    + 471                    #
    + 472                    if rawcontents is not None:
    + 473                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 474                        if encoding is not None:
    + 475                            filecontent = rawcontents.decode(encoding).rstrip()
    + 476                            lexer = Syntax.guess_lexer(path=ntpath.basename(path_to_file), code=filecontent)
    + 477                            # Some trickery for the files undetected by the lexer
    + 478                            if lexer == "default":
    + 479                                if '<?xml' in filecontent:
    + 480                                    lexer = "xml"
    + 481                                elif '<html>' in filecontent:
    + 482                                    lexer = "html"
    + 483                            syntax = Syntax(code=filecontent, line_numbers=True, lexer=lexer)
    + 484                            if len(files_and_directories) > 1:
    + 485                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 486                            Console().print(syntax)
    + 487                        else:
    + 488                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 489                else:
    + 490                    self.logger.error("[!] Local file '%s' does not exist." % path_to_file)
    + 491            except impacket.smbconnection.SessionError as e:
    + 492                self.logger.error("[!] SMB Error: %s" % e)
    + 493
    + 494    @command_arguments_required
    + 495    def command_lcat(self, arguments, command):
    + 496        # Command arguments required   : Yes
    + 497        # Active SMB connection needed : No
    + 498        # SMB share needed             : No
    + 499
    + 500        # Parse wildcards
    + 501        files_and_directories = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 502
    + 503        for path_to_file in files_and_directories:
    + 504            # Read the file 
    + 505            try:
    + 506                if os.path.exists(path=path_to_file):
    + 507                    f = open(path_to_file, 'rb')
    + 508                    rawcontents = f.read()
    + 509                    #
    + 510                    if rawcontents is not None:
    + 511                        encoding = charset_normalizer.detect(rawcontents)["encoding"]
    + 512                        if encoding is not None:
    + 513                            filecontent = rawcontents.decode(encoding).rstrip()
    + 514                            if len(files_and_directories) > 1:
    + 515                                self.logger.print("\x1b[1;93m[>] %s\x1b[0m" % (path_to_file+' ').ljust(80,'='))
    + 516                            self.logger.print(filecontent)
    + 517                        else:
    + 518                            self.logger.error("[!] Could not detect charset of '%s'." % path_to_file)
    + 519                else:
    + 520                    self.logger.error("[!] Local file '%s' does not exist." % path_to_file)
    + 521            except impacket.smbconnection.SessionError as e:
    + 522                self.logger.error("[!] SMB Error: %s" % e)
    + 523
    + 524    @command_arguments_required
    + 525    def command_lcd(self, arguments, command):
    + 526        # Command arguments required   : Yes
    + 527        # Active SMB connection needed : No
    + 528        # SMB share needed             : No
    + 529        
    + 530        path = arguments[0]
    + 531
    + 532        if os.path.exists(path=path):
    + 533            if os.path.isdir(s=path):
    + 534                os.chdir(path=path)
    + 535            else:
    + 536                self.logger.error("Path '%s' is not a directory." % path)
    + 537        else:
    + 538            self.logger.error("Directory '%s' does not exists." % path)
    + 539
    + 540    @command_arguments_required
    + 541    def command_lcp(self, arguments, command):
    + 542        # Command arguments required   : Yes
    + 543        # Active SMB connection needed : No
    + 544        # SMB share needed             : No
    + 545
    + 546        if len(arguments) == 2:
    + 547            src_path = arguments[0]
    + 548            dst_path = arguments[1]
    + 549            if os.path.exists(path=src_path):
    + 550                try:
    + 551                    shutil.copyfile(src=src_path, dst=dst_path)
    + 552                except shutil.SameFileError as err:
    + 553                    self.logger.error("[!] Error: %s" % err)
    + 554            else:
    + 555                self.logger.error("[!] File '%s' does not exists." % src_path)
    + 556        else:
    + 557            self.commandCompleterObject.print_help(command=command)
    + 558
    + 559    def command_lls(self, arguments, command):
    + 560        # Command arguments required   : No
    + 561        # Active SMB connection needed : No
    + 562        # SMB share needed             : No
    + 563
    + 564        if len(arguments) == 0:
    + 565            arguments = ['.']
    + 566        else:
    + 567            arguments = resolve_local_files(arguments)
    + 568
    + 569        for path in arguments:
    + 570            if len(arguments) > 1:
    + 571                self.logger.print("%s:" % path)
    + 572            # lls <directory>
    + 573            if os.path.isdir(path):
    + 574                directory_contents = os.listdir(path=path)
    + 575                for entryname in sorted(directory_contents):
    + 576                    path_to_file = path + os.path.sep + entryname
    + 577                    rights_str = unix_permissions(path_to_file)
    + 578                    size_str = b_filesize(os.path.getsize(filename=path_to_file))
    + 579                    date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    + 580
    + 581                    if os.path.isdir(s=entryname):
    + 582                        if self.config.no_colors:
    + 583                            self.logger.print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    + 584                        else:
    + 585                            self.logger.print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    + 586                    else:
    + 587                        if self.config.no_colors:
    + 588                            self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    + 589                        else:
    + 590                            self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    + 591            # lls <file>
    + 592            elif os.path.isfile(path):
    + 593                rights_str = unix_permissions(path)
    + 594                size_str = b_filesize(os.path.getsize(filename=path))
    + 595                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    + 596                if self.config.no_colors:
    + 597                    self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    + 598                else:
    + 599                    self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    + 600            
    + 601            if len(arguments) > 1:
    + 602                self.logger.print()
    + 603
    + 604    @command_arguments_required
    + 605    def command_lmkdir(self, arguments, command):
    + 606        # Command arguments required   : Yes
    + 607        # Active SMB connection needed : No
    + 608        # SMB share needed             : No
    + 609
    + 610        for path in arguments:
    + 611            if os.path.sep in path:
    + 612                path = path.strip(os.path.sep).split(os.path.sep)
    + 613            else:
    + 614                path = [path]
    + 615
    + 616            # Create each dir in the path
    + 617            for depth in range(1, len(path)+1):
    + 618                tmp_path = os.path.sep.join(path[:depth])
    + 619                if not os.path.exists(tmp_path):
    + 620                    os.mkdir(path=tmp_path)
    + 621
    + 622    def command_lpwd(self, arguments, command):
    + 623        # Command arguments required   : No
    + 624        # Active SMB connection needed : No
    + 625        # SMB share needed             : No
    + 626
    + 627        self.logger.print(os.getcwd())
    + 628
    + 629    @command_arguments_required
    + 630    def command_lrename(self, arguments, command):
    + 631        # Command arguments required   : Yes
    + 632        # Active SMB connection needed : No
    + 633        # SMB share needed             : No
    + 634
    + 635        if len(arguments) == 2:
    + 636            os.rename(src=arguments[0], dst=arguments[1])
    + 637        else:
    + 638            self.commandCompleterObject.print_help(command=command)
    + 639
    + 640    @command_arguments_required
    + 641    def command_lrm(self, arguments, command):
    + 642        # Command arguments required   : Yes
    + 643        # Active SMB connection needed : No
    + 644        # SMB share needed             : No
    + 645
    + 646        path = arguments[0]
    + 647
    + 648        if os.path.exists(path):
    + 649            if not os.path.isdir(s=path):
    + 650                try:
    + 651                    os.remove(path=path)
    + 652                except Exception as e:
    + 653                    self.logger.error("Error removing file '%s' : %s" % path)
    + 654            else:
    + 655                self.logger.error("Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
    + 656        else:
    + 657            self.logger.error("Path '%s' does not exist." % path)
    + 658
    + 659    @command_arguments_required
    + 660    def command_lrmdir(self, arguments, command):
    + 661        # Command arguments required   : Yes
    + 662        # Active SMB connection needed : No
    + 663        # SMB share needed             : No
    + 664
    + 665        if len(arguments) == 0:
    + 666            path = '.'
    + 667        else:
    + 668            path = arguments[0]
    + 669
    + 670        if os.path.exists(path):
    + 671            if os.path.isdir(s=path):
    + 672                try:
    + 673                    shutil.rmtree(path=path)
    + 674                except Exception as e:
    + 675                    self.logger.error("Error removing directory '%s' : %s" % path)
    + 676            else:
    + 677                self.logger.error("Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
    + 678        else:
    + 679            self.logger.error("Path '%s' does not exist." % path)
    + 680
    + 681    def command_ltree(self, arguments, command):
    + 682        # Command arguments required   : No
    + 683        # Active SMB connection needed : No
    + 684        # SMB share needed             : No
    + 685
    + 686        if len(arguments) == 0:
    + 687            path = '.'
    + 688        else:
    + 689            path = arguments[0]
    + 690
    + 691        if len(arguments) == 0:
    + 692            local_tree(path='.', config=self.config)
    + 693        else:
    + 694            local_tree(path=path, config=self.config)
    + 695
    + 696    @active_smb_connection_needed
    + 697    @smb_share_is_set
    + 698    def command_ls(self, arguments, command):
    + 699        # Command arguments required   : No
    + 700        # Active SMB connection needed : Yes
    + 701        # SMB share needed             : Yes
    + 702
    + 703        if len(arguments) == 0:
    + 704            arguments = ['.']
    + 705        else:
    + 706            arguments = resolve_remote_files(self.sessionsManager.current_session, arguments)
    + 707
    + 708        for path in arguments:
    + 709            if len(arguments) > 1:
    + 710                self.logger.print("%s:" % path)
    + 711
    + 712            if self.sessionsManager.current_session.path_isdir(pathFromRoot=path):
    + 713                # Read the files
    + 714                directory_contents = self.sessionsManager.current_session.list_contents(path=path)
    + 715            else:
    + 716                entry = self.sessionsManager.current_session.get_entry(path=path)
    + 717                if entry is not None:
    + 718                    directory_contents = {entry.get_longname(): entry}
    + 719                else:
    + 720                    directory_contents = {}
    + 721
    + 722            for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
    + 723                windows_ls_entry(directory_contents[longname], self.config)
    + 724
    + 725            if len(arguments) > 1:
    + 726                self.logger.print()
    + 727            
    + 728    @command_arguments_required
    + 729    @active_smb_connection_needed
    + 730    @smb_share_is_set
    + 731    def command_mkdir(self, arguments, command):
    + 732        # Command arguments required   : Yes
    + 733        # Active SMB connection needed : Yes
    + 734        # SMB share needed             : Yes
    + 735
    + 736        self.sessionsManager.current_session.mkdir(path=arguments[0])
    + 737
    + 738    @command_arguments_required
    + 739    @active_smb_connection_needed
    + 740    @smb_share_is_set
    + 741    def command_module(self, arguments, command):
    + 742        module_name = arguments[0]
    + 743
    + 744        if module_name in self.modules.keys():
    + 745            module = self.modules[module_name](self.sessionsManager.current_session, self.config, self.logger)
    + 746            arguments_string = ' '.join(arguments[1:])
    + 747            module.run(arguments_string)
    + 748        else:
    + 749            self.logger.error("Module '%s' does not exist." % module_name)
    + 750
    + 751    @command_arguments_required
    + 752    @active_smb_connection_needed
    + 753    @smb_share_is_set
    + 754    def command_mount(self, arguments, command):
    + 755        # Command arguments required   : Yes
    + 756        # Active SMB connection needed : Yes
    + 757        # SMB share needed             : Yes
    + 758
    + 759        if len(arguments) == 2:
    + 760            remote_path = arguments[0]
    + 761            if not remote_path.startswith(ntpath.sep):
    + 762                remote_path = self.sessionsManager.current_session.smb_cwd + ntpath.sep + remote_path
    + 763
    + 764            local_mount_point = arguments[1]
    + 765
    + 766            self.logger.debug("Trying to mount remote '%s' onto local '%s'" % (remote_path, local_mount_point))
    + 767
    + 768            try:
    + 769                self.sessionsManager.current_session.mount(local_mount_point, remote_path)
    + 770            except (impacket.smbconnection.SessionError, impacket.smb3.SessionError) as e:
    + 771                self.sessionsManager.current_session.umount(local_mount_point)
    + 772        else:
    + 773            self.commandCompleterObject.print_help(command=command)
    + 774
    + 775    @command_arguments_required
    + 776    @active_smb_connection_needed
    + 777    @smb_share_is_set
    + 778    def command_put(self, arguments, command):
    + 779        # Command arguments required   : Yes
    + 780        # Active SMB connection needed : Yes
    + 781        # SMB share needed             : Yes
    + 782        
    + 783        is_recursive = False
    + 784        while '-r' in arguments:
    + 785            is_recursive = True
    + 786            arguments.remove('-r')
    + 787
    + 788        # Parse wildcards
    + 789        files_and_directories = resolve_local_files(arguments)
    + 790
    + 791        # 
    + 792        for localpath in files_and_directories:
    + 793            try:
    + 794                self.logger.print(localpath)
    + 795                if is_recursive and os.path.isdir(s=localpath):
    + 796                    # Put files recursively
    + 797                    self.sessionsManager.current_session.put_file_recursively(localpath=localpath)
    + 798                else:
    + 799                    # Put this single file
    + 800                    self.sessionsManager.current_session.put_file(localpath=localpath)
    + 801            except impacket.smbconnection.SessionError as e:
    + 802                self.logger.error("[!] SMB Error: %s" % e)
    + 803
    + 804    def command_reconnect(self, arguments, command):
    + 805        # Command arguments required   : No
    + 806        # Active SMB connection needed : No
    + 807        # SMB share needed             : No
    + 808
    + 809        self.sessionsManager.current_session.ping_smb_session()
    + 810        if self.sessionsManager.current_session.connected:
    + 811            self.sessionsManager.current_session.close_smb_session()
    + 812            self.sessionsManager.current_session.init_smb_session()
    + 813        else:
    + 814            self.sessionsManager.current_session.init_smb_session()
    + 815
    + 816    def command_reset(self, arguments, command):
    + 817        # Command arguments required   : No
    + 818        # Active SMB connection needed : No
    + 819        # SMB share needed             : No
    + 820        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    + 821        sys.stdout.write('\x1b[v')  
    + 822        sys.stdout.write('\x1b[o') # Reset
    + 823        sys.stdout.flush()
    + 824
    + 825    @command_arguments_required
    + 826    @active_smb_connection_needed
    + 827    @smb_share_is_set
    + 828    def command_rm(self, arguments, command):
    + 829        # Command arguments required   : Yes
    + 830        # Active SMB connection needed : Yes
    + 831        # SMB share needed             : Yes
    + 832
    + 833        for path_to_file in arguments:
    + 834            # Wildcard
    + 835            if '*' in path_to_file:
    + 836                self.sessionsManager.current_session.rm(path=path_to_file)
    + 837            # File
    + 838            elif self.sessionsManager.current_session.path_exists(path_to_file):
    + 839                if self.sessionsManager.current_session.path_isfile(path_to_file):
    + 840                    try:
    + 841                        self.sessionsManager.current_session.rm(path=path_to_file)
    + 842                    except Exception as e:
    + 843                        self.logger.error("Error removing file '%s' : %s" % path_to_file)
    + 844                else:
    + 845                    self.logger.error("Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path_to_file)
    + 846            # File does not exist
    + 847            else:
    + 848                self.logger.error("Remote file '%s' does not exist." % path_to_file)
    + 849
    + 850    @command_arguments_required
    + 851    @active_smb_connection_needed
    + 852    @smb_share_is_set
    + 853    def command_rmdir(self, arguments, command):
    + 854        # Command arguments required   : Yes
    + 855        # Active SMB connection needed : Yes
    + 856        # SMB share needed             : Yes
    + 857
    + 858        for path_to_directory in arguments:
    + 859            if self.sessionsManager.current_session.path_exists(path_to_directory):
    + 860                if self.sessionsManager.current_session.path_isdir(path_to_directory):
    + 861                    try:
    + 862                        self.sessionsManager.current_session.rmdir(path=path_to_directory)
    + 863                    except Exception as e:
    + 864                        self.logger.error("Error removing directory '%s' : %s" % path_to_directory)
    + 865                else:
    + 866                    self.logger.error("Cannot delete '%s': This is a file, use 'rm <file>' instead." % path_to_directory)
    + 867            else:
    + 868                self.logger.error("Remote directory '%s' does not exist." % path_to_directory)
    + 869
    + 870    @active_smb_connection_needed
    + 871    @smb_share_is_set
    + 872    def command_sizeof(self, arguments, command):
    + 873        # Command arguments required   : Yes
    + 874        # Active SMB connection needed : Yes
    + 875        # SMB share needed             : Yes
    + 876
    + 877        class RecursiveSizeOfPrint(object):
    + 878            def __init__(self, entry, sessionsManager, config, logger):
    + 879                self.entry = entry
    + 880                self.config = config
    + 881                self.logger = logger
    + 882                self.sessionsManager = sessionsManager
    + 883                self.size = 0
    + 884            
    + 885            def update(self, entry, fullpath, depth):
    + 886                self.size += entry.get_filesize()
    + 887                self.print(end='\r')
    + 888            
    + 889            def print(self, end='\n'):
    + 890                # Directory
    + 891                if self.entry.is_directory():
    + 892                    if self.config.no_colors:
    + 893                        path = "%s\\" % self.entry.get_longname()
    + 894                    else:
    + 895                        path = "\x1b[1;96m%s\x1b[0m\\" % self.entry.get_longname()
    + 896                # File
    + 897                else:
    + 898                    if self.config.no_colors:
    + 899                        path = "%s" % self.entry.get_longname()
    + 900                    else:
    + 901                        path = "\x1b[1m%s\x1b[0m" % self.entry.get_longname()
    + 902                self.logger.print("%10s  %s" % (b_filesize(self.size), path), end=end)
    + 903
    + 904        entries = []
    + 905        if len(arguments) == 0:
    + 906            entries = self.sessionsManager.current_session.list_contents()
    + 907            entries = [entry for name, entry in entries.items() if name not in ['.', '..']]
    + 908        else:
    + 909            entry = self.sessionsManager.current_session.get_entry(path=' '.join(arguments))
    + 910            entries = []
    + 911            if entry is not None:
    + 912                entries = [entry]
    + 913            else:
    + 914                self.logger.print("[!] Path '%s' does not exist." % ' '.join(arguments))
    + 915
    + 916        total = 0
    + 917        for entry in entries:
    + 918            rsop = RecursiveSizeOfPrint(
    + 919                entry=entry, 
    + 920                sessionsManager=self.sessionsManager, 
    + 921                config=self.config,
    + 922                logger=self.logger
    + 923            )
    + 924            # Directory
    + 925            if entry.is_directory():
    + 926                self.sessionsManager.current_session.find(
    + 927                    paths=[entry.get_longname()],
    + 928                    callback=rsop.update
    + 929                )
    + 930            # File
    + 931            else:
    + 932                rsop.update(entry=entry, fullpath=entry.get_longname(), depth=0)
    + 933            # Close the print
    + 934            rsop.print()
    + 935            total += rsop.size
    + 936        
    + 937        if len(entries) > 1:
    + 938            self.logger.print("──────────────────────")
    + 939            self.logger.print("     total  %s" % b_filesize(total))
    + 940
    + 941    @active_smb_connection_needed
    + 942    def command_shares(self, arguments, command):
    + 943        # Command arguments required   : No
    + 944        # Active SMB connection needed : Yes
    + 945        # SMB share needed             : No
    + 946
    + 947        do_check_rights = False
    + 948        if len(arguments) != 0:
    + 949            if arguments[0] == "rights":
    + 950                do_check_rights = True
    + 951
    + 952        shares = self.sessionsManager.current_session.list_shares()
    + 953        if len(shares.keys()) != 0:
    + 954            table = Table(title=None)
    + 955            table.add_column("Share")
    + 956            table.add_column("Visibility")
    + 957            table.add_column("Type")
    + 958            table.add_column("Description", justify="left")
    + 959            if do_check_rights:
    + 960                table.add_column("Rights")
    + 961
    + 962            for sharename in sorted(shares.keys()):
    + 963                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
    + 964
    + 965                is_hidden = bool(sharename.endswith('$'))
    + 966                if is_hidden:
    + 967                    str_hidden = "[bold bright_blue]Hidden[/bold bright_blue]"
    + 968                    str_sharename = "[bold bright_blue]" + shares[sharename]["name"] + "[/bold bright_blue]"
    + 969                    str_types = "[bold bright_blue]" + types + "[/bold bright_blue]"
    + 970                    str_comment = "[bold bright_blue]" + shares[sharename]["comment"] + "[/bold bright_blue]"
    + 971                else:
    + 972                    str_hidden = "[bold bright_yellow]Visible[/bold bright_yellow]"
    + 973                    str_sharename = "[bold bright_yellow]" + shares[sharename]["name"] + "[/bold bright_yellow]"
    + 974                    str_types = "[bold bright_yellow]" + types + "[/bold bright_yellow]"
    + 975                    str_comment = "[bold bright_yellow]" + shares[sharename]["comment"] + "[/bold bright_yellow]"
    + 976
    + 977                if do_check_rights:
    + 978                    access_rights = self.sessionsManager.current_session.test_rights(sharename=shares[sharename]["name"])
    + 979                    str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    + 980                    if access_rights["readable"] and access_rights["writable"]:
    + 981                        str_access_rights = "[bold green]READ[/bold green], [bold red]WRITE[/bold red]"
    + 982                    elif access_rights["readable"]:
    + 983                        str_access_rights = "[bold green]READ[/bold green]"
    + 984                    elif access_rights["writable"]:
    + 985                        # Without READ?? This should not happen IMHO
    + 986                        str_access_rights = "[bold red]WRITE[/bold red]"
    + 987                    else:
    + 988                        str_access_rights = "[bold yellow]NO ACCESS[/bold yellow]"
    + 989
    + 990                if do_check_rights:
    + 991                    table.add_row(str_sharename, str_hidden, str_types, str_comment, str_access_rights)
    + 992                else:
    + 993                    table.add_row(str_sharename, str_hidden, str_types, str_comment)
    + 994
    + 995            Console().print(table)
    + 996        else:
    + 997            self.logger.error("No share served on '%s'" % self.sessionsManager.current_session.host)
    + 998
    + 999    @active_smb_connection_needed
    +1000    @smb_share_is_set
    +1001    def command_tree(self, arguments, command):
    +1002        # Command arguments required   : No
    +1003        # Active SMB connection needed : Yes
    +1004        # SMB share needed             : Yes
    +1005
    +1006        if len(arguments) == 0:
    +1007            self.sessionsManager.current_session.tree(path='.')
    +1008        else:
    +1009            self.sessionsManager.current_session.tree(path=arguments[0])
    +1010
    +1011    @command_arguments_required
    +1012    @active_smb_connection_needed
    +1013    @smb_share_is_set
    +1014    def command_umount(self, arguments, command):
    +1015        # Command arguments required   : Yes
    +1016        # Active SMB connection needed : Yes
    +1017        # SMB share needed             : Yes
    +1018
    +1019        local_mount_point = arguments[0]
    +1020
    +1021        self.logger.debug("Trying to unmount local mount point '%s'" % (local_mount_point))
    +1022        
    +1023        self.sessionsManager.current_session.umount(local_mount_point)
    +1024        
    +1025    @command_arguments_required
    +1026    @active_smb_connection_needed
    +1027    def command_use(self, arguments, command):
    +1028        # Command arguments required   : Yes
    +1029        # Active SMB connection needed : Yes
    +1030        # SMB share needed             : No
    +1031
    +1032        sharename = arguments[0]
    +1033
    +1034        # Reload the list of shares
    +1035        shares = self.sessionsManager.current_session.list_shares()
    +1036        shares = [s.lower() for s in shares.keys()]
    +1037
    +1038        if sharename.lower() in shares:
    +1039            self.sessionsManager.current_session.set_share(sharename)
    +1040        else:
    +1041            self.logger.error("No share named '%s' on '%s'" % (sharename, self.sessionsManager.current_session.host))
    +1042
    +1043    # Private functions =======================================================
    +1044
    +1045    def __load_modules(self):
    +1046        """
    +1047        Dynamically loads all Python modules from the 'modules' directory and stores them in the 'modules' dictionary.
    +1048        Each module is expected to be a Python file that contains a class with the same name as the file (minus the .py extension).
    +1049        The class must have at least two attributes: 'name' and 'description'.
    +1050        
    +1051        This method clears any previously loaded modules, constructs the path to the modules directory, and iterates over
    +1052        each file in that directory. If the file is a Python file (ends with .py and is not '__init__.py'), it attempts to
    +1053        import the module and access the class within it to add to the 'modules' dictionary.
    +1054        
    +1055        If debug mode is enabled in the configuration, it prints debug information about the loading process and the loaded modules.
    +1056        """
    +1057
    +1058        self.modules.clear()
    +1059
    +1060        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
    +1061        self.logger.debug("Loading modules from %s ..." % modules_dir)
    +1062        sys.path.extend([modules_dir])
    +1063
    +1064        for file in os.listdir(modules_dir):
    +1065            filepath = os.path.normpath(modules_dir + os.path.sep + file)
    +1066            if file.endswith('.py'):
    +1067                if os.path.isfile(filepath) and file not in ["__init__.py"]:
    +1068                    try:
    +1069                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
    +1070                        module = module_file.__getattribute__(file[:-3])
    +1071                        self.modules[module.name.lower()] = module
    +1072                    except AttributeError as e:
    +1073                        pass
    +1074
    +1075        if self.config.debug:
    +1076            if len(self.modules.keys()) == 0:
    +1077                self.logger.debug("Loaded 0 modules.")
    +1078            elif len(self.modules.keys()) == 1:
    +1079                self.logger.debug("Loaded 1 module:")
    +1080            else:
    +1081                self.logger.debug("Loaded %d modules:" % len(self.modules.keys()))
    +1082            for modulename in sorted(self.modules.keys()):
    +1083                self.logger.debug("%s : \"%s\" (%s)" % (self.modules[modulename].name, self.modules[modulename].description, self.modules[modulename]))
    +1084
    +1085        if self.commandCompleterObject is not None:
    +1086            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
    +1087
    +1088    def __prompt(self):
    +1089        """
    +1090        Prints the command prompt for the interactive shell.
    +1091
    +1092        This method constructs and returns the command prompt string based on the current state of the SMB session.
    +1093        The prompt indicates the connection status with a visual symbol and displays the current working directory
    +1094        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
    +1095
    +1096        Returns:
    +1097            str: The formatted command prompt string.
    +1098        """
    +1099
    +1100        # A session exists
    +1101        if self.sessionsManager.current_session is not None:
    +1102            # Check if the session is still active
    +1103            self.sessionsManager.current_session.ping_smb_session()
    +1104            if self.sessionsManager.current_session.connected:
    +1105                if self.config.no_colors:
    +1106                    connected_dot = "[v]"
    +1107                else:
    +1108                    connected_dot = "\x1b[1;92m⏺\x1b[0m"
    +1109            else:
    +1110                if self.config.no_colors:
    +1111                    connected_dot = "[x]"
    +1112                else:
    +1113                    connected_dot = "\x1b[1;91m⏺\x1b[0m"
    +1114            
    +1115            # Session ID if 
    +1116            session_prompt = ""
    +1117            if len(self.sessionsManager.sessions.keys()) >= 2:
    +1118                session_prompt = "[#%d]" % self.sessionsManager.current_session_id
    +1119
    +1120            # No share set yet
    +1121            if self.sessionsManager.current_session.smb_share is None:
    +1122                str_path = "\\\\%s\\" % self.sessionsManager.current_session.host
    +1123            # A share is set
    +1124            else:
    +1125                if len(self.sessionsManager.current_session.smb_cwd) == 0:
    +1126                    current_path = ""
    +1127                else:
    +1128                    current_path = self.sessionsManager.current_session.smb_cwd.strip(ntpath.sep) + ntpath.sep
    +1129                
    +1130                str_path = "\\\\%s\\%s\\%s" % (self.sessionsManager.current_session.host, self.sessionsManager.current_session.smb_share, current_path)
    +1131        # No active session
    +1132        else:
    +1133            connected_dot = ""
    +1134            session_prompt = ""
    +1135            str_path = "No active session"
    +1136
    +1137        # Build final prompt string
    +1138        if self.config.no_colors:
    +1139            str_prompt = "%s%s[%s]> " % (connected_dot, session_prompt, str_path)
    +1140        else:
    +1141            str_prompt = "%s%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, session_prompt, str_path)
    +1142
    +1143        return str_prompt
     
    @@ -2240,36 +2537,65 @@

    - InteractiveShell(smbSession, config) + InteractiveShell(sessionsManager, config, logger)
    -
    81    def __init__(self, smbSession, config):
    -82        # Objects
    -83        self.smbSession = smbSession
    -84        self.config = config
    -85        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
    -86        readline.set_completer(self.commandCompleterObject.complete)
    -87        readline.parse_and_bind("tab: complete")
    -88        readline.set_completer_delims("\n")
    -89        # Additional modules
    -90        self.modules = {}
    -91        self.__load_modules()
    +            
     85    def __init__(self, sessionsManager, config, logger):
    + 86        # Objects
    + 87        self.sessionsManager = sessionsManager
    + 88        self.config = config
    + 89        self.logger = logger
    + 90        # Internals
    + 91        self.commandCompleterObject = CommandCompleter(
    + 92            smbSession=self.sessionsManager.current_session,
    + 93            config=self.config,
    + 94            logger=self.logger,
    + 95        )
    + 96        readline.set_completer(self.commandCompleterObject.complete)
    + 97        readline.parse_and_bind("tab: complete")
    + 98        readline.set_completer_delims("\n")
    + 99        # Additional modules
    +100        self.__load_modules()
     
    -
    +
    - smbSession + running = +True
    - + + + + +
    +
    +
    + modules = +{} + + +
    + + + + +
    +
    +
    + sessionsManager + + +
    + @@ -2285,24 +2611,24 @@

    -
    +
    - commandCompleterObject + logger
    - +
    -
    +
    - modules + commandCompleterObject
    - + @@ -2318,190 +2644,219 @@

    -
     93    def run(self):
    - 94        running = True
    - 95        while running:
    - 96            try:
    - 97                user_input = input(self.__prompt()).strip().split(" ")
    - 98                command, arguments = user_input[0].lower(), user_input[1:]
    - 99                
    -100                # Exit the command line
    -101                if command == "exit":
    -102                    running = False
    -103
    -104                elif command in self.commandCompleterObject.commands.keys():
    -105                    self.process_command(
    -106                        command=command, 
    -107                        arguments=arguments
    -108                    )
    -109
    -110                elif command.strip() == "":
    -111                    pass
    -112
    -113                # Fallback to unknown command
    -114                else:
    -115                    print("Unknown command. Type \"help\" for help.")
    -116
    -117            except KeyboardInterrupt as e:
    -118                print()
    -119
    -120            except EOFError as e:
    -121                print()
    -122                running = False
    -123
    -124            except Exception as e:
    -125                if self.config.debug:
    -126                    traceback.print_exc()
    -127                print("[!] Error: %s" % str(e))
    +            
    102    def run(self):
    +103        # Read commands from script file first
    +104        if self.config.startup_script:
    +105            f = open(self.config.startup_script, 'r')
    +106            for line in f.readlines():
    +107                try:
    +108                    self.logger.print("%s%s" % (self.__prompt(), line.strip()))
    +109                    readline.add_history(line.strip())
    +110                    self.process_line(commandLine=line.strip())
    +111                except KeyboardInterrupt as e:
    +112                    self.logger.print()
    +113
    +114                except EOFError as e:
    +115                    self.logger.print()
    +116                    running = False
    +117
    +118                except Exception as err:
    +119                    if self.config.debug:
    +120                        traceback.print_exc()
    +121                    self.logger.error(str(err))
    +122
    +123        # Then interactive console
    +124        if not self.config.not_interactive:
    +125            while self.running:
    +126                try:
    +127                    user_input = input(self.__prompt()).strip()
    +128                    self.process_line(commandLine=user_input)
    +129                except KeyboardInterrupt as e:
    +130                    self.logger.print()
    +131
    +132                except EOFError as e:
    +133                    self.logger.print()
    +134                    running = False
    +135
    +136                except Exception as err:
    +137                    if self.config.debug:
    +138                        traceback.print_exc()
    +139                    self.logger.error(str(err))
     
    -
    - +
    +
    def - process_command(self, command, arguments=[]): + process_line(self, commandLine): - +
    - -
    129    def process_command(self, command, arguments=[]):
    -130        # Skip
    -131        if command.strip() == "":
    -132            pass
    -133        
    -134        # Display help
    -135        elif command == "help":
    -136            self.command_help(arguments, command)
    -137
    -138        # Cat the contents of a file
    -139        elif command == "bat":
    -140            self.command_bat(arguments, command)
    -141
    -142        # Cat the contents of a file
    -143        elif command == "cat":
    -144            self.command_cat(arguments, command)
    -145
    -146        # Closes the current SMB session
    -147        elif command == "close":
    -148            self.command_close(arguments, command)
    -149               
    -150        # Change directory in the current share
    -151        elif command == "cd":
    -152            self.command_cd(arguments, command)
    -153        
    -154        # debug
    -155        elif command == "debug":
    -156            self.command_debug(arguments, command)
    -157
    -158        # Get a file
    -159        elif command == "get":
    -160            self.command_get(arguments, command)
    -161
    -162        # SMB server info
    -163        elif command == "info":
    -164            self.command_info(arguments, command)
    -165
    -166        # List directory contents in a share
    -167        elif command in ["ls", "dir"]:
    -168            self.command_ls(arguments, command)
    -169
    -170        # Creates a new remote directory
    -171        elif command == "mkdir":
    -172            self.command_mkdir(arguments, command)
    -173
    -174        # Put a file
    -175        elif command == "put":
    -176            self.command_put(arguments, command)
    -177
    -178        # Shows the content of a local file
    -179        elif command == "lcat":
    -180            self.command_lcat(arguments, command)
    -181
    -182        # Changes the current local directory
    -183        elif command == "lcd":
    -184            self.command_lcd(arguments, command)
    -185
    -186        # Creates a copy of a local file
    -187        elif command == "lcp":
    -188            self.command_lcp(arguments, command)
    -189
    -190        # Pretty prints the content of a local file
    -191        elif command == "lbat":
    -192            self.command_lbat(arguments, command)
    -193
    -194        # Lists the contents of the current local directory
    -195        elif command == "lls":
    -196            self.command_lls(arguments, command)
    -197
    -198        # Creates a new local directory
    -199        elif command == "lmkdir":
    -200            self.command_lmkdir(arguments, command)
    -201
    -202        # Shows the current local directory
    -203        elif command == "lpwd":
    -204            self.command_lpwd(arguments, command)
    -205
    -206        # Renames a local file
    -207        elif command == "lrename":
    -208            self.command_lrename(arguments, command)
    -209        
    -210        # Removes a local file
    -211        elif command == "lrm":
    -212            self.command_lrm(arguments, command)
    -213
    -214        # Removes a local directory
    -215        elif command == "lrmdir":
    -216            self.command_lrmdir(arguments, command)
    -217
    -218        # Shows the current local directory
    -219        elif command == "ltree":
    -220            self.command_ltree(arguments, command)
    -221
    -222        # Modules
    -223        elif command == "module":
    -224            self.command_module(arguments, command)
    -225
    -226        # Creates a mount point of the remote share on the local machine
    -227        elif command == "mount":
    -228            self.command_mount(arguments, command)
    -229
    -230        # Reconnects the current SMB session
    -231        elif command in ["connect", "reconnect"]:
    -232            self.command_reconnect(arguments, command)
    -233
    -234        # Reset the TTY output
    -235        elif command == "reset":
    -236            self.command_reset(arguments, command)
    -237
    -238        # Removes a remote file
    -239        elif command == "rm":
    -240            self.command_rm(arguments, command)
    -241            
    -242        # Removes a remote directory
    -243        elif command == "rmdir":
    -244            self.command_rmdir(arguments, command)
    -245
    -246        # List shares
    -247        elif command == "sizeof":
    -248            self.command_sizeof(arguments, command)
    -249
    -250        # List shares
    -251        elif command == "shares":
    -252            self.command_shares(arguments, command)
    -253        
    -254        # Displays a tree view of the CWD
    -255        elif command == "tree":
    -256            self.command_tree(arguments, command)
    -257        
    -258        # Use a share
    -259        elif command == "use":
    -260            self.command_use(arguments, command)
    +    
    +            
    141    def process_line(self, commandLine):
    +142        # Split and parse the commandLine
    +143        tokens = shlex.split(commandLine)
    +144        if len(tokens) == 0:
    +145            command = ""
    +146            arguments = []
    +147        elif len(tokens) == 1:
    +148            command = tokens[0].lower()
    +149            arguments = []
    +150        else:
    +151            command = tokens[0].lower()
    +152            arguments = tokens[1:]
    +153        
    +154        # Skip
    +155        if command.strip() == "":
    +156            pass
    +157        # Execute the command
    +158        elif command in self.commandCompleterObject.commands.keys():
    +159
    +160            # Exit the command line
    +161            if command in ["exit", "quit"]:
    +162                self.running = False
    +163            
    +164            # Display help
    +165            elif command == "help":
    +166                self.command_help(arguments, command)
    +167
    +168            # Cat the contents of a file
    +169            elif command == "bat":
    +170                self.command_bat(arguments, command)
    +171
    +172            # Cat the contents of a file
    +173            elif command == "cat":
    +174                self.command_cat(arguments, command)
    +175
    +176            # Closes the current SMB session
    +177            elif command == "close":
    +178                self.command_close(arguments, command)
    +179                
    +180            # Change directory in the current share
    +181            elif command == "cd":
    +182                self.command_cd(arguments, command)
    +183            
    +184            # debug
    +185            elif command == "debug":
    +186                self.command_debug(arguments, command)
    +187
    +188            # Get a file
    +189            elif command == "get":
    +190                self.command_get(arguments, command)
    +191
    +192            # SMB server info
    +193            elif command == "info":
    +194                self.command_info(arguments, command)
    +195
    +196            # List directory contents in a share
    +197            elif command in ["ls", "dir"]:
    +198                self.command_ls(arguments, command)
    +199
    +200            # Creates a new remote directory
    +201            elif command == "mkdir":
    +202                self.command_mkdir(arguments, command)
    +203
    +204            # Put a file
    +205            elif command == "put":
    +206                self.command_put(arguments, command)
    +207
    +208            # Shows the content of a local file
    +209            elif command == "lcat":
    +210                self.command_lcat(arguments, command)
    +211
    +212            # Changes the current local directory
    +213            elif command == "lcd":
    +214                self.command_lcd(arguments, command)
    +215
    +216            # Creates a copy of a local file
    +217            elif command == "lcp":
    +218                self.command_lcp(arguments, command)
    +219
    +220            # Pretty prints the content of a local file
    +221            elif command == "lbat":
    +222                self.command_lbat(arguments, command)
    +223
    +224            # Lists the contents of the current local directory
    +225            elif command == "lls":
    +226                self.command_lls(arguments, command)
    +227
    +228            # Creates a new local directory
    +229            elif command == "lmkdir":
    +230                self.command_lmkdir(arguments, command)
    +231
    +232            # Shows the current local directory
    +233            elif command == "lpwd":
    +234                self.command_lpwd(arguments, command)
    +235
    +236            # Renames a local file
    +237            elif command == "lrename":
    +238                self.command_lrename(arguments, command)
    +239            
    +240            # Removes a local file
    +241            elif command == "lrm":
    +242                self.command_lrm(arguments, command)
    +243
    +244            # Removes a local directory
    +245            elif command == "lrmdir":
    +246                self.command_lrmdir(arguments, command)
    +247
    +248            # Shows the current local directory
    +249            elif command == "ltree":
    +250                self.command_ltree(arguments, command)
    +251
    +252            # Modules
    +253            elif command == "module":
    +254                self.command_module(arguments, command)
    +255
    +256            # Creates a mount point of the remote share on the local machine
    +257            elif command == "mount":
    +258                self.command_mount(arguments, command)
    +259
    +260            # Reconnects the current SMB session
    +261            elif command in ["connect", "reconnect"]:
    +262                self.command_reconnect(arguments, command)
    +263
    +264            # Reset the TTY output
    +265            elif command == "reset":
    +266                self.command_reset(arguments, command)
    +267
    +268            # Removes a remote file
    +269            elif command == "rm":
    +270                self.command_rm(arguments, command)
    +271                
    +272            # Removes a remote directory
    +273            elif command == "rmdir":
    +274                self.command_rmdir(arguments, command)
    +275
    +276            # Sessions management
    +277            elif command == "sessions":
    +278                self.sessionsManager.process_command_line(arguments)
    +279
    +280            # List shares
    +281            elif command == "sizeof":
    +282                self.command_sizeof(arguments, command)
    +283
    +284            # List shares
    +285            elif command == "shares":
    +286                self.command_shares(arguments, command)
    +287            
    +288            # Displays a tree view of the CWD
    +289            elif command == "tree":
    +290                self.command_tree(arguments, command)
    +291            
    +292            # Use a share
    +293            elif command == "use":
    +294                self.command_use(arguments, command)
    +295        
    +296        # Fallback to unknown command   
    +297        else:
    +298            self.logger.print("Unknown command. Type \"help\" for help.")
     
    @@ -2519,11 +2874,12 @@

    -
    264    def command_debug(self, arguments, command):
    -265        try:
    -266            pass
    -267        except Exception as e:
    -268            traceback.print_exc()
    +            
    302    def command_debug(self, arguments, command):
    +303        try:
    +304            self.logger.print("[debug] command    = '%s'" % command)
    +305            self.logger.print("[debug] arguments  = %s" % arguments)
    +306        except Exception as e:
    +307            traceback.print_exc()
     
    @@ -2541,13 +2897,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2565,13 +2921,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2589,13 +2945,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2613,14 +2969,14 @@

    -
    334    def command_close(self, arguments, command):
    -335        # Command arguments required   : No
    -336        # Active SMB connection needed : No
    -337        # SMB share needed             : No
    -338
    -339        self.smbSession.ping_smb_session()
    -340        if self.smbSession.connected:
    -341            self.smbSession.close_smb_session()
    +            
    386    def command_close(self, arguments, command):
    +387        # Command arguments required   : No
    +388        # Active SMB connection needed : No
    +389        # SMB share needed             : No
    +390
    +391        self.sessionsManager.current_session.ping_smb_session()
    +392        if self.sessionsManager.current_session.connected:
    +393            self.sessionsManager.current_session.close_smb_session()
     
    @@ -2638,13 +2994,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2662,15 +3018,15 @@

    -
    366    def command_help(self, arguments, command):
    -367        # Command arguments required   : No
    -368        # Active SMB connection needed : No
    -369        # SMB share needed             : No
    -370
    -371        if len(arguments) != 0:
    -372            self.commandCompleterObject.print_help(command=arguments[0])
    -373        else:
    -374            self.commandCompleterObject.print_help(command=None)
    +            
    423    def command_help(self, arguments, command):
    +424        # Command arguments required   : No
    +425        # Active SMB connection needed : No
    +426        # SMB share needed             : No
    +427
    +428        if len(arguments) != 0:
    +429            self.commandCompleterObject.print_help(command=arguments[0])
    +430        else:
    +431            self.commandCompleterObject.print_help(command=None)
     
    @@ -2688,15 +3044,39 @@

    -
    39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    +            
    40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
    +
    + + + + +
    +
    + +
    + + def + command_lbat(*args, **kwargs): + + + +
    + +
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2714,13 +3094,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2738,13 +3118,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2762,37 +3142,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    -
    - - - - -
    -
    - -
    - - def - command_lbat(*args, **kwargs): - - - -
    - -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2810,46 +3166,50 @@

    -
    487    def command_lls(self, arguments, command):
    -488        # Command arguments required   : No
    -489        # Active SMB connection needed : No
    -490        # SMB share needed             : No
    -491
    -492        if len(arguments) == 0:
    -493            path = '.'
    -494        else:
    -495            path = ' '.join(arguments)
    -496
    -497        # lls <directory>
    -498        if os.path.isdir(path):
    -499            directory_contents = os.listdir(path=path)
    -500            for entryname in sorted(directory_contents):
    -501                path_to_file = path + os.path.sep + entryname
    -502                rights_str = unix_permissions(path_to_file)
    -503                size_str = b_filesize(os.path.getsize(filename=path_to_file))
    -504                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    -505
    -506                if os.path.isdir(s=entryname):
    -507                    if self.config.no_colors:
    -508                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -509                    else:
    -510                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    -511                else:
    -512                    if self.config.no_colors:
    -513                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    -514                    else:
    -515                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    -516        # lls <file>
    -517        elif os.path.isfile(path):
    -518            rights_str = unix_permissions(path)
    -519            size_str = b_filesize(os.path.getsize(filename=path))
    -520            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    -521            if self.config.no_colors:
    -522                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    -523            else:
    -524               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    -525        else:
    -526            print("[!] No such file or directory.")
    +            
    559    def command_lls(self, arguments, command):
    +560        # Command arguments required   : No
    +561        # Active SMB connection needed : No
    +562        # SMB share needed             : No
    +563
    +564        if len(arguments) == 0:
    +565            arguments = ['.']
    +566        else:
    +567            arguments = resolve_local_files(arguments)
    +568
    +569        for path in arguments:
    +570            if len(arguments) > 1:
    +571                self.logger.print("%s:" % path)
    +572            # lls <directory>
    +573            if os.path.isdir(path):
    +574                directory_contents = os.listdir(path=path)
    +575                for entryname in sorted(directory_contents):
    +576                    path_to_file = path + os.path.sep + entryname
    +577                    rights_str = unix_permissions(path_to_file)
    +578                    size_str = b_filesize(os.path.getsize(filename=path_to_file))
    +579                    date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
    +580
    +581                    if os.path.isdir(s=entryname):
    +582                        if self.config.no_colors:
    +583                            self.logger.print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    +584                        else:
    +585                            self.logger.print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
    +586                    else:
    +587                        if self.config.no_colors:
    +588                            self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
    +589                        else:
    +590                            self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
    +591            # lls <file>
    +592            elif os.path.isfile(path):
    +593                rights_str = unix_permissions(path)
    +594                size_str = b_filesize(os.path.getsize(filename=path))
    +595                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
    +596                if self.config.no_colors:
    +597                    self.logger.print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
    +598                else:
    +599                    self.logger.print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
    +600            
    +601            if len(arguments) > 1:
    +602                self.logger.print()
     
    @@ -2867,13 +3227,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2891,12 +3251,12 @@

    -
    548    def command_lpwd(self, arguments, command):
    -549        # Command arguments required   : No
    -550        # Active SMB connection needed : No
    -551        # SMB share needed             : No
    -552
    -553        print(os.getcwd())
    +            
    622    def command_lpwd(self, arguments, command):
    +623        # Command arguments required   : No
    +624        # Active SMB connection needed : No
    +625        # SMB share needed             : No
    +626
    +627        self.logger.print(os.getcwd())
     
    @@ -2914,13 +3274,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2938,13 +3298,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2962,13 +3322,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -2986,15 +3346,20 @@

    -
    602    def command_ltree(self, arguments, command):
    -603        # Command arguments required   : No
    -604        # Active SMB connection needed : No
    -605        # SMB share needed             : No
    -606
    -607        if len(arguments) == 0:
    -608            local_tree(path='.', config=self.config)
    -609        else:
    -610            local_tree(path=' '.join(arguments), config=self.config)
    +            
    681    def command_ltree(self, arguments, command):
    +682        # Command arguments required   : No
    +683        # Active SMB connection needed : No
    +684        # SMB share needed             : No
    +685
    +686        if len(arguments) == 0:
    +687            path = '.'
    +688        else:
    +689            path = arguments[0]
    +690
    +691        if len(arguments) == 0:
    +692            local_tree(path='.', config=self.config)
    +693        else:
    +694            local_tree(path=path, config=self.config)
     
    @@ -3012,15 +3377,15 @@

    -
    39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    +            
    40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
     
    @@ -3038,13 +3403,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3062,13 +3427,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3086,13 +3451,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3110,13 +3475,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3134,17 +3499,17 @@

    -
    697    def command_reconnect(self, arguments, command):
    -698        # Command arguments required   : No
    -699        # Active SMB connection needed : No
    -700        # SMB share needed             : No
    -701
    -702        self.smbSession.ping_smb_session()
    -703        if self.smbSession.connected:
    -704            self.smbSession.close_smb_session()
    -705            self.smbSession.init_smb_session()
    -706        else:
    -707            self.smbSession.init_smb_session()
    +            
    804    def command_reconnect(self, arguments, command):
    +805        # Command arguments required   : No
    +806        # Active SMB connection needed : No
    +807        # SMB share needed             : No
    +808
    +809        self.sessionsManager.current_session.ping_smb_session()
    +810        if self.sessionsManager.current_session.connected:
    +811            self.sessionsManager.current_session.close_smb_session()
    +812            self.sessionsManager.current_session.init_smb_session()
    +813        else:
    +814            self.sessionsManager.current_session.init_smb_session()
     
    @@ -3162,14 +3527,14 @@

    -
    709    def command_reset(self, arguments, command):
    -710        # Command arguments required   : No
    -711        # Active SMB connection needed : No
    -712        # SMB share needed             : No
    -713        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    -714        sys.stdout.write('\x1b[v')  
    -715        sys.stdout.write('\x1b[o') # Reset
    -716        sys.stdout.flush()
    +            
    816    def command_reset(self, arguments, command):
    +817        # Command arguments required   : No
    +818        # Active SMB connection needed : No
    +819        # SMB share needed             : No
    +820        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
    +821        sys.stdout.write('\x1b[v')  
    +822        sys.stdout.write('\x1b[o') # Reset
    +823        sys.stdout.flush()
     
    @@ -3187,13 +3552,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3211,13 +3576,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3235,15 +3600,15 @@

    -
    39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    +            
    40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
     
    @@ -3261,15 +3626,15 @@

    -
    39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    +            
    40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
     
    @@ -3287,15 +3652,15 @@

    -
    39    def wrapper(*args, **kwargs):
    -40        self, arguments,command  = args[0], args[1], args[2]
    -41        #
    -42        self.smbSession.ping_smb_session()
    -43        if self.smbSession.connected:
    -44            return func(*args, **kwargs)
    -45        else:
    -46            print("[!] SMB Session is disconnected.")
    -47            return None
    +            
    40    def wrapper(*args, **kwargs):
    +41        self, arguments,command  = args[0], args[1], args[2]
    +42        #
    +43        self.sessionsManager.current_session.ping_smb_session()
    +44        if self.sessionsManager.current_session.connected:
    +45            return func(*args, **kwargs)
    +46        else:
    +47            print("[!] SMB Session is disconnected.")
    +48            return None
     
    @@ -3313,13 +3678,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    @@ -3337,13 +3702,13 @@

    -
    29    def wrapper(*args, **kwargs):
    -30        self, arguments,command  = args[0], args[1], args[2]
    -31        if len(arguments) != 0:
    -32            return func(*args, **kwargs)
    -33        else:
    -34            self.commandCompleterObject.print_help(command=command)
    -35            return None
    +            
    30    def wrapper(*args, **kwargs):
    +31        self, arguments,command  = args[0], args[1], args[2]
    +32        if len(arguments) != 0:
    +33            return func(*args, **kwargs)
    +34        else:
    +35            self.commandCompleterObject.print_help(command=command)
    +36            return None
     
    diff --git a/documentation/smbclientng/core/LocalFileIO.html b/documentation/smbclientng/core/LocalFileIO.html index 4aacd1b..cce941e 100644 --- a/documentation/smbclientng/core/LocalFileIO.html +++ b/documentation/smbclientng/core/LocalFileIO.html @@ -36,6 +36,9 @@

    API Documentation

  • LocalFileIO
  • +
  • + logger +
  • mode
  • @@ -119,141 +122,157 @@

    27 read(self, size): Reads data from the file up to the specified size and updates the progress bar if expected size is provided. 28 """ 29 - 30 def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, debug=False): + 30 def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, logger=None): 31 super(LocalFileIO, self).__init__() - 32 self.mode = mode - 33 # Convert remote path format to local operating system path format - 34 self.path = path.replace(ntpath.sep, os.path.sep) - 35 self.dir = None - 36 self.debug = False - 37 self.expected_size = expected_size - 38 self.keepRemotePath = keepRemotePath - 39 - 40 # Write to local (read remote) - 41 if self.mode in ["wb"]: - 42 self.dir = '.' + os.path.sep - 43 if keepRemotePath: - 44 self.dir += os.path.dirname(self.path) - 45 - 46 if not os.path.exists(self.dir): - 47 if self.debug: - 48 print("[debug] Creating local directory '%s'" % self.dir) + 32 self.logger = logger + 33 self.mode = mode + 34 # Convert remote path format to local operating system path format + 35 self.path = path.replace(ntpath.sep, os.path.sep) + 36 self.dir = None + 37 self.debug = False + 38 self.expected_size = expected_size + 39 self.keepRemotePath = keepRemotePath + 40 + 41 # Write to local (read remote) + 42 if self.mode in ["wb"]: + 43 self.dir = '.' + os.path.sep + 44 if keepRemotePath: + 45 self.dir += os.path.dirname(self.path) + 46 + 47 if not os.path.exists(self.dir): + 48 self.logger.debug("[debug] Creating local directory '%s'" % self.dir) 49 os.makedirs(self.dir) 50 - 51 if self.debug: - 52 print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) - 53 - 54 self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode) - 55 - 56 # Write to remote (read local) - 57 elif self.mode in ["rb"]: - 58 if ntpath.sep in self.path: - 59 self.dir = os.path.dirname(self.path) - 60 - 61 if self.debug: - 62 print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) - 63 self.fd = open(self.path, self.mode) - 64 - 65 if self.expected_size is None: - 66 self.expected_size = os.path.getsize(filename=self.path) - 67 - 68 # Create progress bar - 69 if self.expected_size is not None: - 70 self.__progress = Progress( - 71 TextColumn("[bold blue]{task.description}", justify="right"), - 72 BarColumn(bar_width=None), - 73 "[progress.percentage]{task.percentage:>3.1f}%", - 74 "•", - 75 DownloadColumn(), - 76 "•", - 77 TransferSpeedColumn(), - 78 "•", - 79 TimeRemainingColumn(), - 80 ) - 81 self.__progress.start() - 82 self.__task = self.__progress.add_task( - 83 description="'%s'" % os.path.basename(self.path), - 84 start=True, - 85 total=self.expected_size, - 86 visible=True - 87 ) - 88 - 89 def write(self, data): - 90 """ - 91 Writes data to the file. - 92 - 93 This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set. + 51 self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + 52 + 53 try: + 54 self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode) + 55 except PermissionError as err: + 56 self.fd = None + 57 + 58 # Write to remote (read local) + 59 elif self.mode in ["rb"]: + 60 if ntpath.sep in self.path: + 61 self.dir = os.path.dirname(self.path) + 62 + 63 self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + 64 + 65 try: + 66 self.fd = open(self.path, self.mode) + 67 except PermissionError as err: + 68 self.fd = None + 69 + 70 if self.fd is not None: + 71 if self.expected_size is None: + 72 self.expected_size = os.path.getsize(filename=self.path) + 73 + 74 # Create progress bar + 75 if self.expected_size is not None: + 76 self.__progress = Progress( + 77 TextColumn("[bold blue]{task.description}", justify="right"), + 78 BarColumn(bar_width=None), + 79 "[progress.percentage]{task.percentage:>3.1f}%", + 80 "•", + 81 DownloadColumn(), + 82 "•", + 83 TransferSpeedColumn(), + 84 "•", + 85 TimeRemainingColumn(), + 86 ) + 87 self.__progress.start() + 88 self.__task = self.__progress.add_task( + 89 description="'%s'" % os.path.basename(self.path), + 90 start=True, + 91 total=self.expected_size, + 92 visible=True + 93 ) 94 - 95 Args: - 96 data (bytes): The data to be written to the file. - 97 - 98 Returns: - 99 int: The number of bytes written. -100 """ -101 -102 if self.expected_size is not None: -103 self.__progress.update(self.__task, advance=len(data)) -104 return self.fd.write(data) -105 -106 def read(self, size): -107 """ -108 Reads a specified amount of data from the file. -109 -110 This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set. -111 -112 Args: -113 size (int): The number of bytes to read from the file. -114 -115 Returns: -116 bytes: The data read from the file. -117 """ + 95 def write(self, data): + 96 """ + 97 Writes data to the file. + 98 + 99 This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set. +100 +101 Args: +102 data (bytes): The data to be written to the file. +103 +104 Returns: +105 int: The number of bytes written. +106 """ +107 +108 if self.fd is not None: +109 if self.expected_size is not None: +110 self.__progress.update(self.__task, advance=len(data)) +111 return self.fd.write(data) +112 else: +113 return 0 +114 +115 def read(self, size): +116 """ +117 Reads a specified amount of data from the file. 118 -119 read_data = self.fd.read(size) -120 if self.expected_size is not None: -121 self.__progress.update(self.__task, advance=len(read_data)) -122 return read_data +119 This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set. +120 +121 Args: +122 size (int): The number of bytes to read from the file. 123 -124 def close(self, remove=False): -125 """ -126 Closes the file descriptor and optionally removes the file. +124 Returns: +125 bytes: The data read from the file. +126 """ 127 -128 This method ensures that the file descriptor is properly closed and the file is removed if specified. -129 It also stops the progress bar if it was initiated and cleans up the object by deleting it. -130 -131 Args: -132 remove (bool): If True, the file at the path will be removed after closing the file descriptor. -133 """ -134 -135 self.fd.close() -136 -137 if remove: -138 os.remove(path=self.path) +128 if self.fd is not None: +129 read_data = self.fd.read(size) +130 if self.expected_size is not None: +131 self.__progress.update(self.__task, advance=len(read_data)) +132 return read_data +133 else: +134 return b"" +135 +136 def close(self, remove=False): +137 """ +138 Closes the file descriptor and optionally removes the file. 139 -140 if self.expected_size is not None: -141 self.__progress.stop() -142 -143 del self -144 -145 def set_error(self, message): -146 """ -147 Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns. -148 -149 This method is used to communicate error states or important messages directly in the progress bar interface. -150 It updates the task description with the provided message and simplifies the progress bar to show only the text -151 and download columns, removing other elements like speed and time remaining which may not be relevant in an error state. -152 -153 Args: -154 message (str): The error or status message to display in the progress bar. -155 """ -156 -157 self.__progress.tasks[0].description = message -158 self.__progress.columns = [ -159 TextColumn("[bold blue]{task.description}", justify="right"), -160 BarColumn(bar_width=None), -161 "•", -162 DownloadColumn(), -163 ] -164 self.__progress.update(self.__task, advance=0) +140 This method ensures that the file descriptor is properly closed and the file is removed if specified. +141 It also stops the progress bar if it was initiated and cleans up the object by deleting it. +142 +143 Args: +144 remove (bool): If True, the file at the path will be removed after closing the file descriptor. +145 """ +146 +147 if self.fd is not None: +148 self.fd.close() +149 +150 if remove: +151 try: +152 os.remove(path=self.path) +153 except (PermissionError, FileNotFoundError) as err: +154 pass +155 +156 if self.expected_size is not None: +157 self.__progress.stop() +158 +159 del self +160 +161 def set_error(self, message): +162 """ +163 Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns. +164 +165 This method is used to communicate error states or important messages directly in the progress bar interface. +166 It updates the task description with the provided message and simplifies the progress bar to show only the text +167 and download columns, removing other elements like speed and time remaining which may not be relevant in an error state. +168 +169 Args: +170 message (str): The error or status message to display in the progress bar. +171 """ +172 +173 self.__progress.tasks[0].description = message +174 self.__progress.columns = [ +175 TextColumn("[bold blue]{task.description}", justify="right"), +176 BarColumn(bar_width=None), +177 "•", +178 DownloadColumn(), +179 ] +180 self.__progress.update(self.__task, advance=0)

    @@ -286,141 +305,157 @@

    28 read(self, size): Reads data from the file up to the specified size and updates the progress bar if expected size is provided. 29 """ 30 - 31 def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, debug=False): + 31 def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, logger=None): 32 super(LocalFileIO, self).__init__() - 33 self.mode = mode - 34 # Convert remote path format to local operating system path format - 35 self.path = path.replace(ntpath.sep, os.path.sep) - 36 self.dir = None - 37 self.debug = False - 38 self.expected_size = expected_size - 39 self.keepRemotePath = keepRemotePath - 40 - 41 # Write to local (read remote) - 42 if self.mode in ["wb"]: - 43 self.dir = '.' + os.path.sep - 44 if keepRemotePath: - 45 self.dir += os.path.dirname(self.path) - 46 - 47 if not os.path.exists(self.dir): - 48 if self.debug: - 49 print("[debug] Creating local directory '%s'" % self.dir) + 33 self.logger = logger + 34 self.mode = mode + 35 # Convert remote path format to local operating system path format + 36 self.path = path.replace(ntpath.sep, os.path.sep) + 37 self.dir = None + 38 self.debug = False + 39 self.expected_size = expected_size + 40 self.keepRemotePath = keepRemotePath + 41 + 42 # Write to local (read remote) + 43 if self.mode in ["wb"]: + 44 self.dir = '.' + os.path.sep + 45 if keepRemotePath: + 46 self.dir += os.path.dirname(self.path) + 47 + 48 if not os.path.exists(self.dir): + 49 self.logger.debug("[debug] Creating local directory '%s'" % self.dir) 50 os.makedirs(self.dir) 51 - 52 if self.debug: - 53 print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) - 54 - 55 self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode) - 56 - 57 # Write to remote (read local) - 58 elif self.mode in ["rb"]: - 59 if ntpath.sep in self.path: - 60 self.dir = os.path.dirname(self.path) - 61 - 62 if self.debug: - 63 print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) - 64 self.fd = open(self.path, self.mode) - 65 - 66 if self.expected_size is None: - 67 self.expected_size = os.path.getsize(filename=self.path) - 68 - 69 # Create progress bar - 70 if self.expected_size is not None: - 71 self.__progress = Progress( - 72 TextColumn("[bold blue]{task.description}", justify="right"), - 73 BarColumn(bar_width=None), - 74 "[progress.percentage]{task.percentage:>3.1f}%", - 75 "•", - 76 DownloadColumn(), - 77 "•", - 78 TransferSpeedColumn(), - 79 "•", - 80 TimeRemainingColumn(), - 81 ) - 82 self.__progress.start() - 83 self.__task = self.__progress.add_task( - 84 description="'%s'" % os.path.basename(self.path), - 85 start=True, - 86 total=self.expected_size, - 87 visible=True - 88 ) - 89 - 90 def write(self, data): - 91 """ - 92 Writes data to the file. - 93 - 94 This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set. + 52 self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + 53 + 54 try: + 55 self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode) + 56 except PermissionError as err: + 57 self.fd = None + 58 + 59 # Write to remote (read local) + 60 elif self.mode in ["rb"]: + 61 if ntpath.sep in self.path: + 62 self.dir = os.path.dirname(self.path) + 63 + 64 self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode)) + 65 + 66 try: + 67 self.fd = open(self.path, self.mode) + 68 except PermissionError as err: + 69 self.fd = None + 70 + 71 if self.fd is not None: + 72 if self.expected_size is None: + 73 self.expected_size = os.path.getsize(filename=self.path) + 74 + 75 # Create progress bar + 76 if self.expected_size is not None: + 77 self.__progress = Progress( + 78 TextColumn("[bold blue]{task.description}", justify="right"), + 79 BarColumn(bar_width=None), + 80 "[progress.percentage]{task.percentage:>3.1f}%", + 81 "•", + 82 DownloadColumn(), + 83 "•", + 84 TransferSpeedColumn(), + 85 "•", + 86 TimeRemainingColumn(), + 87 ) + 88 self.__progress.start() + 89 self.__task = self.__progress.add_task( + 90 description="'%s'" % os.path.basename(self.path), + 91 start=True, + 92 total=self.expected_size, + 93 visible=True + 94 ) 95 - 96 Args: - 97 data (bytes): The data to be written to the file. - 98 - 99 Returns: -100 int: The number of bytes written. -101 """ -102 -103 if self.expected_size is not None: -104 self.__progress.update(self.__task, advance=len(data)) -105 return self.fd.write(data) -106 -107 def read(self, size): -108 """ -109 Reads a specified amount of data from the file. -110 -111 This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set. -112 -113 Args: -114 size (int): The number of bytes to read from the file. -115 -116 Returns: -117 bytes: The data read from the file. -118 """ + 96 def write(self, data): + 97 """ + 98 Writes data to the file. + 99 +100 This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set. +101 +102 Args: +103 data (bytes): The data to be written to the file. +104 +105 Returns: +106 int: The number of bytes written. +107 """ +108 +109 if self.fd is not None: +110 if self.expected_size is not None: +111 self.__progress.update(self.__task, advance=len(data)) +112 return self.fd.write(data) +113 else: +114 return 0 +115 +116 def read(self, size): +117 """ +118 Reads a specified amount of data from the file. 119 -120 read_data = self.fd.read(size) -121 if self.expected_size is not None: -122 self.__progress.update(self.__task, advance=len(read_data)) -123 return read_data +120 This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set. +121 +122 Args: +123 size (int): The number of bytes to read from the file. 124 -125 def close(self, remove=False): -126 """ -127 Closes the file descriptor and optionally removes the file. +125 Returns: +126 bytes: The data read from the file. +127 """ 128 -129 This method ensures that the file descriptor is properly closed and the file is removed if specified. -130 It also stops the progress bar if it was initiated and cleans up the object by deleting it. -131 -132 Args: -133 remove (bool): If True, the file at the path will be removed after closing the file descriptor. -134 """ -135 -136 self.fd.close() -137 -138 if remove: -139 os.remove(path=self.path) +129 if self.fd is not None: +130 read_data = self.fd.read(size) +131 if self.expected_size is not None: +132 self.__progress.update(self.__task, advance=len(read_data)) +133 return read_data +134 else: +135 return b"" +136 +137 def close(self, remove=False): +138 """ +139 Closes the file descriptor and optionally removes the file. 140 -141 if self.expected_size is not None: -142 self.__progress.stop() -143 -144 del self -145 -146 def set_error(self, message): -147 """ -148 Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns. -149 -150 This method is used to communicate error states or important messages directly in the progress bar interface. -151 It updates the task description with the provided message and simplifies the progress bar to show only the text -152 and download columns, removing other elements like speed and time remaining which may not be relevant in an error state. -153 -154 Args: -155 message (str): The error or status message to display in the progress bar. -156 """ -157 -158 self.__progress.tasks[0].description = message -159 self.__progress.columns = [ -160 TextColumn("[bold blue]{task.description}", justify="right"), -161 BarColumn(bar_width=None), -162 "•", -163 DownloadColumn(), -164 ] -165 self.__progress.update(self.__task, advance=0) +141 This method ensures that the file descriptor is properly closed and the file is removed if specified. +142 It also stops the progress bar if it was initiated and cleans up the object by deleting it. +143 +144 Args: +145 remove (bool): If True, the file at the path will be removed after closing the file descriptor. +146 """ +147 +148 if self.fd is not None: +149 self.fd.close() +150 +151 if remove: +152 try: +153 os.remove(path=self.path) +154 except (PermissionError, FileNotFoundError) as err: +155 pass +156 +157 if self.expected_size is not None: +158 self.__progress.stop() +159 +160 del self +161 +162 def set_error(self, message): +163 """ +164 Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns. +165 +166 This method is used to communicate error states or important messages directly in the progress bar interface. +167 It updates the task description with the provided message and simplifies the progress bar to show only the text +168 and download columns, removing other elements like speed and time remaining which may not be relevant in an error state. +169 +170 Args: +171 message (str): The error or status message to display in the progress bar. +172 """ +173 +174 self.__progress.tasks[0].description = message +175 self.__progress.columns = [ +176 TextColumn("[bold blue]{task.description}", justify="right"), +177 BarColumn(bar_width=None), +178 "•", +179 DownloadColumn(), +180 ] +181 self.__progress.update(self.__task, advance=0)

    @@ -444,75 +479,92 @@

    - LocalFileIO( mode, path=None, expected_size=None, keepRemotePath=False, debug=False) + LocalFileIO( mode, path=None, expected_size=None, keepRemotePath=False, logger=None)
    -
    31    def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, debug=False):
    +            
    31    def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, logger=None):
     32        super(LocalFileIO, self).__init__()
    -33        self.mode = mode
    -34        # Convert remote path format to local operating system path format 
    -35        self.path = path.replace(ntpath.sep, os.path.sep)
    -36        self.dir = None
    -37        self.debug = False
    -38        self.expected_size = expected_size
    -39        self.keepRemotePath = keepRemotePath
    -40
    -41        # Write to local (read remote)
    -42        if self.mode in ["wb"]:
    -43            self.dir = '.' + os.path.sep
    -44            if keepRemotePath:
    -45                self.dir += os.path.dirname(self.path)
    -46
    -47            if not os.path.exists(self.dir):
    -48                if self.debug:
    -49                    print("[debug] Creating local directory '%s'" % self.dir)
    +33        self.logger = logger
    +34        self.mode = mode
    +35        # Convert remote path format to local operating system path format 
    +36        self.path = path.replace(ntpath.sep, os.path.sep)
    +37        self.dir = None
    +38        self.debug = False
    +39        self.expected_size = expected_size
    +40        self.keepRemotePath = keepRemotePath
    +41
    +42        # Write to local (read remote)
    +43        if self.mode in ["wb"]:
    +44            self.dir = '.' + os.path.sep
    +45            if keepRemotePath:
    +46                self.dir += os.path.dirname(self.path)
    +47
    +48            if not os.path.exists(self.dir):
    +49                self.logger.debug("[debug] Creating local directory '%s'" % self.dir)
     50                os.makedirs(self.dir)
     51
    -52            if self.debug:
    -53                print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode))
    -54            
    -55            self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode)
    -56
    -57        # Write to remote (read local)
    -58        elif self.mode in ["rb"]:
    -59            if ntpath.sep in self.path:
    -60                self.dir = os.path.dirname(self.path)
    -61
    -62            if self.debug:
    -63                print("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode))
    -64            self.fd = open(self.path, self.mode)
    -65
    -66            if self.expected_size is None:
    -67                self.expected_size = os.path.getsize(filename=self.path)
    -68
    -69        # Create progress bar
    -70        if self.expected_size is not None:
    -71            self.__progress = Progress(
    -72                TextColumn("[bold blue]{task.description}", justify="right"),
    -73                BarColumn(bar_width=None),
    -74                "[progress.percentage]{task.percentage:>3.1f}%",
    -75                "•",
    -76                DownloadColumn(),
    -77                "•",
    -78                TransferSpeedColumn(),
    -79                "•",
    -80                TimeRemainingColumn(),
    -81            )
    -82            self.__progress.start()
    -83            self.__task = self.__progress.add_task(
    -84                description="'%s'" % os.path.basename(self.path),
    -85                start=True,
    -86                total=self.expected_size,
    -87                visible=True
    -88            )
    +52            self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode))
    +53            
    +54            try:
    +55                self.fd = open(self.dir + os.path.sep + os.path.basename(self.path), self.mode)
    +56            except PermissionError as err:
    +57                self.fd = None
    +58
    +59        # Write to remote (read local)
    +60        elif self.mode in ["rb"]:
    +61            if ntpath.sep in self.path:
    +62                self.dir = os.path.dirname(self.path)
    +63
    +64            self.logger.debug("[debug] Openning local '%s' with mode '%s'" % (self.path, self.mode))
    +65            
    +66            try:
    +67                self.fd = open(self.path, self.mode)
    +68            except PermissionError as err:
    +69                self.fd = None
    +70
    +71            if self.fd is not None:
    +72                if self.expected_size is None:
    +73                    self.expected_size = os.path.getsize(filename=self.path)
    +74
    +75        # Create progress bar
    +76        if self.expected_size is not None:
    +77            self.__progress = Progress(
    +78                TextColumn("[bold blue]{task.description}", justify="right"),
    +79                BarColumn(bar_width=None),
    +80                "[progress.percentage]{task.percentage:>3.1f}%",
    +81                "•",
    +82                DownloadColumn(),
    +83                "•",
    +84                TransferSpeedColumn(),
    +85                "•",
    +86                TimeRemainingColumn(),
    +87            )
    +88            self.__progress.start()
    +89            self.__task = self.__progress.add_task(
    +90                description="'%s'" % os.path.basename(self.path),
    +91                start=True,
    +92                total=self.expected_size,
    +93                visible=True
    +94            )
     
    +
    +
    +
    + logger + + +
    + + + +
    @@ -591,22 +643,25 @@

    -
     90    def write(self, data):
    - 91        """
    - 92        Writes data to the file.
    - 93
    - 94        This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set.
    - 95
    - 96        Args:
    - 97            data (bytes): The data to be written to the file.
    - 98
    - 99        Returns:
    -100            int: The number of bytes written.
    -101        """
    -102
    -103        if self.expected_size is not None:
    -104            self.__progress.update(self.__task, advance=len(data))
    -105        return self.fd.write(data)
    +            
     96    def write(self, data):
    + 97        """
    + 98        Writes data to the file.
    + 99
    +100        This method writes the specified data to the file and updates the progress bar with the amount of data written if the expected size is set.
    +101
    +102        Args:
    +103            data (bytes): The data to be written to the file.
    +104
    +105        Returns:
    +106            int: The number of bytes written.
    +107        """
    +108
    +109        if self.fd is not None:
    +110            if self.expected_size is not None:
    +111                self.__progress.update(self.__task, advance=len(data))
    +112            return self.fd.write(data)
    +113        else:
    +114            return 0
     
    @@ -634,23 +689,26 @@

    -
    107    def read(self, size):
    -108        """
    -109        Reads a specified amount of data from the file.
    -110
    -111        This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set.
    -112
    -113        Args:
    -114            size (int): The number of bytes to read from the file.
    -115
    -116        Returns:
    -117            bytes: The data read from the file.
    -118        """
    +            
    116    def read(self, size):
    +117        """
    +118        Reads a specified amount of data from the file.
     119
    -120        read_data = self.fd.read(size)
    -121        if self.expected_size is not None:
    -122            self.__progress.update(self.__task, advance=len(read_data))
    -123        return read_data
    +120        This method reads data from the file based on the size specified. It also updates the progress bar with the amount of data read if the expected size is set.
    +121
    +122        Args:
    +123            size (int): The number of bytes to read from the file.
    +124
    +125        Returns:
    +126            bytes: The data read from the file.
    +127        """
    +128
    +129        if self.fd is not None:
    +130            read_data = self.fd.read(size)
    +131            if self.expected_size is not None:
    +132                self.__progress.update(self.__task, advance=len(read_data))
    +133            return read_data
    +134        else:
    +135            return b""
     
    @@ -678,26 +736,30 @@

    -
    125    def close(self, remove=False):
    -126        """
    -127        Closes the file descriptor and optionally removes the file.
    -128
    -129        This method ensures that the file descriptor is properly closed and the file is removed if specified.
    -130        It also stops the progress bar if it was initiated and cleans up the object by deleting it.
    -131
    -132        Args:
    -133            remove (bool): If True, the file at the path will be removed after closing the file descriptor.
    -134        """
    -135
    -136        self.fd.close()
    -137
    -138        if remove:
    -139            os.remove(path=self.path)
    +            
    137    def close(self, remove=False):
    +138        """
    +139        Closes the file descriptor and optionally removes the file.
     140
    -141        if self.expected_size is not None:
    -142            self.__progress.stop()
    -143        
    -144        del self
    +141        This method ensures that the file descriptor is properly closed and the file is removed if specified.
    +142        It also stops the progress bar if it was initiated and cleans up the object by deleting it.
    +143
    +144        Args:
    +145            remove (bool): If True, the file at the path will be removed after closing the file descriptor.
    +146        """
    +147
    +148        if self.fd is not None:
    +149            self.fd.close()
    +150
    +151        if remove:
    +152            try:
    +153                os.remove(path=self.path)
    +154            except (PermissionError, FileNotFoundError) as err:
    +155                pass
    +156
    +157        if self.expected_size is not None:
    +158            self.__progress.stop()
    +159        
    +160        del self
     
    @@ -723,26 +785,26 @@

    -
    146    def set_error(self, message):
    -147        """
    -148        Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns.
    -149
    -150        This method is used to communicate error states or important messages directly in the progress bar interface.
    -151        It updates the task description with the provided message and simplifies the progress bar to show only the text
    -152        and download columns, removing other elements like speed and time remaining which may not be relevant in an error state.
    -153
    -154        Args:
    -155            message (str): The error or status message to display in the progress bar.
    -156        """
    -157
    -158        self.__progress.tasks[0].description = message
    -159        self.__progress.columns = [
    -160            TextColumn("[bold blue]{task.description}", justify="right"),
    -161            BarColumn(bar_width=None),
    -162            "•",
    -163            DownloadColumn(),
    -164        ]
    -165        self.__progress.update(self.__task, advance=0)
    +            
    162    def set_error(self, message):
    +163        """
    +164        Sets an error message in the progress bar's description and modifies the progress bar to show only essential columns.
    +165
    +166        This method is used to communicate error states or important messages directly in the progress bar interface.
    +167        It updates the task description with the provided message and simplifies the progress bar to show only the text
    +168        and download columns, removing other elements like speed and time remaining which may not be relevant in an error state.
    +169
    +170        Args:
    +171            message (str): The error or status message to display in the progress bar.
    +172        """
    +173
    +174        self.__progress.tasks[0].description = message
    +175        self.__progress.columns = [
    +176            TextColumn("[bold blue]{task.description}", justify="right"),
    +177            BarColumn(bar_width=None),
    +178            "•",
    +179            DownloadColumn(),
    +180        ]
    +181        self.__progress.update(self.__task, advance=0)
     
    diff --git a/documentation/smbclientng/core/Logger.html b/documentation/smbclientng/core/Logger.html new file mode 100644 index 0000000..3a078fa --- /dev/null +++ b/documentation/smbclientng/core/Logger.html @@ -0,0 +1,928 @@ + + + + + + + smbclientng.core.Logger API documentation + + + + + + + + + +
    +
    +

    +smbclientng.core.Logger

    + + + + + + +
      1#!/usr/bin/env python3
    +  2# -*- coding: utf-8 -*-
    +  3# File name          : LocalFileIO.py
    +  4# Author             : Podalirius (@podalirius_)
    +  5# Date created       : 24 June 2024
    +  6
    +  7
    +  8import os
    +  9import re
    + 10from enum import Enum
    + 11
    + 12
    + 13class LogLevel(Enum):
    + 14    INFO = 1
    + 15    DEBUG = 2
    + 16    WARNING = 3
    + 17    ERROR = 4
    + 18    CRITICAL = 5
    + 19
    + 20
    + 21class Logger(object):
    + 22    """
    + 23    A Logger class that provides logging functionalities with various levels such as INFO, DEBUG, WARNING, ERROR, and CRITICAL.
    + 24    It supports color-coded output, which can be disabled, and can also log messages to a file.
    + 25
    + 26    Attributes:
    + 27        __debug (bool): If True, debug level messages will be printed and logged.
    + 28        __nocolors (bool): If True, disables color-coded output.
    + 29        logfile (str|None): Path to a file where logs will be written. If None, logging to a file is disabled.
    + 30
    + 31    Methods:
    + 32        __init__(debug=False, logfile=None, nocolors=False): Initializes the Logger instance.
    + 33        print(message=""): Prints a message to stdout and logs it to a file if logging is enabled.
    + 34        info(message): Logs a message at the INFO level.
    + 35        debug(message): Logs a message at the DEBUG level if debugging is enabled.
    + 36        error(message): Logs a message at the ERROR level.
    + 37    """
    + 38
    + 39    def __init__(self, config, logfile=None):
    + 40        super(Logger, self).__init__()
    + 41        self.config = config
    + 42        self.logfile = logfile
    + 43        #
    + 44        if self.logfile is not None:
    + 45            if os.path.exists(self.logfile):
    + 46                k = 1
    + 47                while os.path.exists(self.logfile+(".%d"%k)):
    + 48                    k += 1
    + 49                self.logfile = self.logfile + (".%d" % k)
    + 50            open(self.logfile, "w").close()
    + 51
    + 52    def print(self, message="", end='\n'):
    + 53        """
    + 54        Prints a message to stdout and logs it to a file if logging is enabled.
    + 55
    + 56        This method prints the provided message to the standard output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    + 57
    + 58        Args:
    + 59            message (str): The message to be printed and logged.
    + 60        """
    + 61
    + 62        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 63        if self.config.no_colors:
    + 64            print(nocolor_message, end=end)
    + 65        else:
    + 66            print(message, end=end)
    + 67        self.__write_to_logfile(nocolor_message, end=end)
    + 68
    + 69    def info(self, message):
    + 70        """
    + 71        Logs a message at the INFO level.
    + 72
    + 73        This method logs the provided message at the INFO level. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True. The message is also logged to a file if a log file path is specified during the Logger instance initialization.
    + 74
    + 75        Args:
    + 76            message (str): The message to be logged at the INFO level.
    + 77        """
    + 78
    + 79        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 80        if self.config.no_colors:
    + 81            print("[info] %s" % nocolor_message)
    + 82        else:
    + 83            print("[\x1b[1;92minfo\x1b[0m] %s" % message)
    + 84        self.__write_to_logfile("[info] %s" % nocolor_message)
    + 85
    + 86    def debug(self, message):
    + 87        """
    + 88        Logs a message at the DEBUG level if debugging is enabled.
    + 89
    + 90        This method logs the provided message at the DEBUG level if the `debug` attribute is set to True during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    + 91
    + 92        Args:
    + 93            message (str): The message to be logged.
    + 94        """
    + 95        
    + 96        if self.config.debug == True:
    + 97            nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 98            if self.config.no_colors:
    + 99                print("[debug] %s" % nocolor_message)
    +100            else:
    +101                print("[debug] %s" % message)
    +102            self.__write_to_logfile("[debug] %s" % nocolor_message)
    +103
    +104    def error(self, message):
    +105        """
    +106        Logs an error message to the console and the log file.
    +107
    +108        This method logs the provided error message to the standard error output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    +109
    +110        Args:
    +111            message (str): The error message to be logged.
    +112        """
    +113
    +114        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    +115        if self.config.no_colors:
    +116            print("[error] %s" % nocolor_message)
    +117        else:
    +118            print("[\x1b[1;91merror\x1b[0m] %s" % message)
    +119        self.__write_to_logfile("[error] %s" % nocolor_message)
    +120
    +121    def __write_to_logfile(self, message, end='\n'):
    +122        """
    +123        Writes the provided message to the log file specified during Logger instance initialization.
    +124
    +125        This method appends the provided message to the log file specified by the `logfile` attribute. If no log file path is specified, this method does nothing.
    +126
    +127        Args:
    +128            message (str): The message to be written to the log file.
    +129        """
    +130
    +131        if self.logfile is not None:
    +132            f = open(self.logfile, "a")
    +133            f.write(message + end)
    +134            f.close()
    +
    + + +
    +
    + +
    + + class + LogLevel(enum.Enum): + + + +
    + +
    14class LogLevel(Enum):
    +15    INFO = 1
    +16    DEBUG = 2
    +17    WARNING = 3
    +18    ERROR = 4
    +19    CRITICAL = 5
    +
    + + +

    Create a collection of name/value pairs.

    + +

    Example enumeration:

    + +
    +
    >>> class Color(Enum):
    +...     RED = 1
    +...     BLUE = 2
    +...     GREEN = 3
    +
    +
    + +

    Access them by:

    + +
      +
    • attribute access:

      + +
      +
      >>> Color.RED
      +<Color.RED: 1>
      +
      +
    • +
    • value lookup:

      + +
      +
      >>> Color(1)
      +<Color.RED: 1>
      +
      +
    • +
    • name lookup:

      + +
      +
      >>> Color['RED']
      +<Color.RED: 1>
      +
      +
    • +
    + +

    Enumerations can be iterated over, and know how many members they have:

    + +
    +
    >>> len(Color)
    +3
    +
    +
    + +
    +
    >>> list(Color)
    +[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
    +
    +
    + +

    Methods can be added to enumerations, and members can have their own +attributes -- see the documentation for details.

    +
    + + +
    +
    + INFO = +<LogLevel.INFO: 1> + + +
    + + + + +
    +
    +
    + DEBUG = +<LogLevel.DEBUG: 2> + + +
    + + + + +
    +
    +
    + WARNING = +<LogLevel.WARNING: 3> + + +
    + + + + +
    +
    +
    + ERROR = +<LogLevel.ERROR: 4> + + +
    + + + + +
    +
    +
    + CRITICAL = +<LogLevel.CRITICAL: 5> + + +
    + + + + +
    +
    +
    Inherited Members
    +
    +
    enum.Enum
    +
    name
    +
    value
    + +
    +
    +
    +
    +
    + +
    + + class + Logger: + + + +
    + +
     22class Logger(object):
    + 23    """
    + 24    A Logger class that provides logging functionalities with various levels such as INFO, DEBUG, WARNING, ERROR, and CRITICAL.
    + 25    It supports color-coded output, which can be disabled, and can also log messages to a file.
    + 26
    + 27    Attributes:
    + 28        __debug (bool): If True, debug level messages will be printed and logged.
    + 29        __nocolors (bool): If True, disables color-coded output.
    + 30        logfile (str|None): Path to a file where logs will be written. If None, logging to a file is disabled.
    + 31
    + 32    Methods:
    + 33        __init__(debug=False, logfile=None, nocolors=False): Initializes the Logger instance.
    + 34        print(message=""): Prints a message to stdout and logs it to a file if logging is enabled.
    + 35        info(message): Logs a message at the INFO level.
    + 36        debug(message): Logs a message at the DEBUG level if debugging is enabled.
    + 37        error(message): Logs a message at the ERROR level.
    + 38    """
    + 39
    + 40    def __init__(self, config, logfile=None):
    + 41        super(Logger, self).__init__()
    + 42        self.config = config
    + 43        self.logfile = logfile
    + 44        #
    + 45        if self.logfile is not None:
    + 46            if os.path.exists(self.logfile):
    + 47                k = 1
    + 48                while os.path.exists(self.logfile+(".%d"%k)):
    + 49                    k += 1
    + 50                self.logfile = self.logfile + (".%d" % k)
    + 51            open(self.logfile, "w").close()
    + 52
    + 53    def print(self, message="", end='\n'):
    + 54        """
    + 55        Prints a message to stdout and logs it to a file if logging is enabled.
    + 56
    + 57        This method prints the provided message to the standard output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    + 58
    + 59        Args:
    + 60            message (str): The message to be printed and logged.
    + 61        """
    + 62
    + 63        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 64        if self.config.no_colors:
    + 65            print(nocolor_message, end=end)
    + 66        else:
    + 67            print(message, end=end)
    + 68        self.__write_to_logfile(nocolor_message, end=end)
    + 69
    + 70    def info(self, message):
    + 71        """
    + 72        Logs a message at the INFO level.
    + 73
    + 74        This method logs the provided message at the INFO level. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True. The message is also logged to a file if a log file path is specified during the Logger instance initialization.
    + 75
    + 76        Args:
    + 77            message (str): The message to be logged at the INFO level.
    + 78        """
    + 79
    + 80        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 81        if self.config.no_colors:
    + 82            print("[info] %s" % nocolor_message)
    + 83        else:
    + 84            print("[\x1b[1;92minfo\x1b[0m] %s" % message)
    + 85        self.__write_to_logfile("[info] %s" % nocolor_message)
    + 86
    + 87    def debug(self, message):
    + 88        """
    + 89        Logs a message at the DEBUG level if debugging is enabled.
    + 90
    + 91        This method logs the provided message at the DEBUG level if the `debug` attribute is set to True during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    + 92
    + 93        Args:
    + 94            message (str): The message to be logged.
    + 95        """
    + 96        
    + 97        if self.config.debug == True:
    + 98            nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 99            if self.config.no_colors:
    +100                print("[debug] %s" % nocolor_message)
    +101            else:
    +102                print("[debug] %s" % message)
    +103            self.__write_to_logfile("[debug] %s" % nocolor_message)
    +104
    +105    def error(self, message):
    +106        """
    +107        Logs an error message to the console and the log file.
    +108
    +109        This method logs the provided error message to the standard error output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    +110
    +111        Args:
    +112            message (str): The error message to be logged.
    +113        """
    +114
    +115        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    +116        if self.config.no_colors:
    +117            print("[error] %s" % nocolor_message)
    +118        else:
    +119            print("[\x1b[1;91merror\x1b[0m] %s" % message)
    +120        self.__write_to_logfile("[error] %s" % nocolor_message)
    +121
    +122    def __write_to_logfile(self, message, end='\n'):
    +123        """
    +124        Writes the provided message to the log file specified during Logger instance initialization.
    +125
    +126        This method appends the provided message to the log file specified by the `logfile` attribute. If no log file path is specified, this method does nothing.
    +127
    +128        Args:
    +129            message (str): The message to be written to the log file.
    +130        """
    +131
    +132        if self.logfile is not None:
    +133            f = open(self.logfile, "a")
    +134            f.write(message + end)
    +135            f.close()
    +
    + + +

    A Logger class that provides logging functionalities with various levels such as INFO, DEBUG, WARNING, ERROR, and CRITICAL. +It supports color-coded output, which can be disabled, and can also log messages to a file.

    + +

    Attributes: + __debug (bool): If True, debug level messages will be printed and logged. + __nocolors (bool): If True, disables color-coded output. + logfile (str|None): Path to a file where logs will be written. If None, logging to a file is disabled.

    + +

    Methods: + __init__(debug=False, logfile=None, nocolors=False): Initializes the Logger instance. + print(message=""): Prints a message to stdout and logs it to a file if logging is enabled. + info(message): Logs a message at the INFO level. + debug(message): Logs a message at the DEBUG level if debugging is enabled. + error(message): Logs a message at the ERROR level.

    +
    + + +
    + +
    + + Logger(config, logfile=None) + + + +
    + +
    40    def __init__(self, config, logfile=None):
    +41        super(Logger, self).__init__()
    +42        self.config = config
    +43        self.logfile = logfile
    +44        #
    +45        if self.logfile is not None:
    +46            if os.path.exists(self.logfile):
    +47                k = 1
    +48                while os.path.exists(self.logfile+(".%d"%k)):
    +49                    k += 1
    +50                self.logfile = self.logfile + (".%d" % k)
    +51            open(self.logfile, "w").close()
    +
    + + + + +
    +
    +
    + config + + +
    + + + + +
    +
    +
    + logfile + + +
    + + + + +
    +
    + +
    + + def + print(self, message='', end='\n'): + + + +
    + +
    53    def print(self, message="", end='\n'):
    +54        """
    +55        Prints a message to stdout and logs it to a file if logging is enabled.
    +56
    +57        This method prints the provided message to the standard output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    +58
    +59        Args:
    +60            message (str): The message to be printed and logged.
    +61        """
    +62
    +63        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    +64        if self.config.no_colors:
    +65            print(nocolor_message, end=end)
    +66        else:
    +67            print(message, end=end)
    +68        self.__write_to_logfile(nocolor_message, end=end)
    +
    + + +

    Prints a message to stdout and logs it to a file if logging is enabled.

    + +

    This method prints the provided message to the standard output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

    + +

    Args: + message (str): The message to be printed and logged.

    +
    + + +
    +
    + +
    + + def + info(self, message): + + + +
    + +
    70    def info(self, message):
    +71        """
    +72        Logs a message at the INFO level.
    +73
    +74        This method logs the provided message at the INFO level. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True. The message is also logged to a file if a log file path is specified during the Logger instance initialization.
    +75
    +76        Args:
    +77            message (str): The message to be logged at the INFO level.
    +78        """
    +79
    +80        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    +81        if self.config.no_colors:
    +82            print("[info] %s" % nocolor_message)
    +83        else:
    +84            print("[\x1b[1;92minfo\x1b[0m] %s" % message)
    +85        self.__write_to_logfile("[info] %s" % nocolor_message)
    +
    + + +

    Logs a message at the INFO level.

    + +

    This method logs the provided message at the INFO level. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True. The message is also logged to a file if a log file path is specified during the Logger instance initialization.

    + +

    Args: + message (str): The message to be logged at the INFO level.

    +
    + + +
    +
    + +
    + + def + debug(self, message): + + + +
    + +
     87    def debug(self, message):
    + 88        """
    + 89        Logs a message at the DEBUG level if debugging is enabled.
    + 90
    + 91        This method logs the provided message at the DEBUG level if the `debug` attribute is set to True during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    + 92
    + 93        Args:
    + 94            message (str): The message to be logged.
    + 95        """
    + 96        
    + 97        if self.config.debug == True:
    + 98            nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    + 99            if self.config.no_colors:
    +100                print("[debug] %s" % nocolor_message)
    +101            else:
    +102                print("[debug] %s" % message)
    +103            self.__write_to_logfile("[debug] %s" % nocolor_message)
    +
    + + +

    Logs a message at the DEBUG level if debugging is enabled.

    + +

    This method logs the provided message at the DEBUG level if the debug attribute is set to True during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

    + +

    Args: + message (str): The message to be logged.

    +
    + + +
    +
    + +
    + + def + error(self, message): + + + +
    + +
    105    def error(self, message):
    +106        """
    +107        Logs an error message to the console and the log file.
    +108
    +109        This method logs the provided error message to the standard error output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the `nocolors` attribute to True.
    +110
    +111        Args:
    +112            message (str): The error message to be logged.
    +113        """
    +114
    +115        nocolor_message = re.sub(r"\x1b[\[]([0-9;]+)m", "", message)
    +116        if self.config.no_colors:
    +117            print("[error] %s" % nocolor_message)
    +118        else:
    +119            print("[\x1b[1;91merror\x1b[0m] %s" % message)
    +120        self.__write_to_logfile("[error] %s" % nocolor_message)
    +
    + + +

    Logs an error message to the console and the log file.

    + +

    This method logs the provided error message to the standard error output and also logs it to a file if a log file path is specified during the Logger instance initialization. The message can include color codes for color-coded output, which can be disabled by setting the nocolors attribute to True.

    + +

    Args: + message (str): The error message to be logged.

    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/documentation/smbclientng/core/Module.html b/documentation/smbclientng/core/Module.html index f3a042e..9833f3a 100644 --- a/documentation/smbclientng/core/Module.html +++ b/documentation/smbclientng/core/Module.html @@ -51,6 +51,9 @@

    API Documentation

  • config
  • +
  • + logger +
  • parseArgs
  • @@ -106,31 +109,35 @@

    20 smbSession = None 21 options = None 22 -23 def __init__(self, smbSession, config): +23 def __init__(self, smbSession, config, logger): 24 self.smbSession = smbSession 25 self.config = config -26 -27 def parseArgs(self): -28 raise NotImplementedError("Subclasses must implement this method") -29 -30 def run(self): -31 """ -32 Placeholder method for running the module. -33 -34 This method should be implemented by subclasses to define the specific behavior of the module. -35 """ -36 raise NotImplementedError("Subclasses must implement this method") -37 -38 def processArguments(self, parser, arguments): -39 if type(arguments) == list: -40 arguments = ' '.join(arguments) -41 -42 __iterableArguments = shlex.split(arguments) -43 -44 self.options = parser.parse_args(__iterableArguments) -45 -46 return self.options -47 +26 self.logger = logger +27 +28 def parseArgs(self): +29 raise NotImplementedError("Subclasses must implement this method") +30 +31 def run(self): +32 """ +33 Placeholder method for running the module. +34 +35 This method should be implemented by subclasses to define the specific behavior of the module. +36 """ +37 raise NotImplementedError("Subclasses must implement this method") +38 +39 def processArguments(self, parser, arguments): +40 if type(arguments) == list: +41 arguments = ' '.join(arguments) +42 +43 __iterableArguments = shlex.split(arguments) +44 +45 try: +46 self.options = parser.parse_args(__iterableArguments) +47 except SystemExit as e: +48 pass +49 +50 return self.options +51

    @@ -158,30 +165,34 @@

    21 smbSession = None 22 options = None 23 -24 def __init__(self, smbSession, config): +24 def __init__(self, smbSession, config, logger): 25 self.smbSession = smbSession 26 self.config = config -27 -28 def parseArgs(self): -29 raise NotImplementedError("Subclasses must implement this method") -30 -31 def run(self): -32 """ -33 Placeholder method for running the module. -34 -35 This method should be implemented by subclasses to define the specific behavior of the module. -36 """ -37 raise NotImplementedError("Subclasses must implement this method") -38 -39 def processArguments(self, parser, arguments): -40 if type(arguments) == list: -41 arguments = ' '.join(arguments) -42 -43 __iterableArguments = shlex.split(arguments) -44 -45 self.options = parser.parse_args(__iterableArguments) -46 -47 return self.options +27 self.logger = logger +28 +29 def parseArgs(self): +30 raise NotImplementedError("Subclasses must implement this method") +31 +32 def run(self): +33 """ +34 Placeholder method for running the module. +35 +36 This method should be implemented by subclasses to define the specific behavior of the module. +37 """ +38 raise NotImplementedError("Subclasses must implement this method") +39 +40 def processArguments(self, parser, arguments): +41 if type(arguments) == list: +42 arguments = ' '.join(arguments) +43 +44 __iterableArguments = shlex.split(arguments) +45 +46 try: +47 self.options = parser.parse_args(__iterableArguments) +48 except SystemExit as e: +49 pass +50 +51 return self.options

    @@ -195,15 +206,16 @@

    - Module(smbSession, config) + Module(smbSession, config, logger)
    -
    24    def __init__(self, smbSession, config):
    +            
    24    def __init__(self, smbSession, config, logger):
     25        self.smbSession = smbSession
     26        self.config = config
    +27        self.logger = logger
     
    @@ -268,6 +280,17 @@

    +

    +
    +
    + logger + + +
    + + + +
    @@ -280,8 +303,8 @@

    -
    28    def parseArgs(self):
    -29        raise NotImplementedError("Subclasses must implement this method")
    +            
    29    def parseArgs(self):
    +30        raise NotImplementedError("Subclasses must implement this method")
     
    @@ -299,13 +322,13 @@

    -
    31    def run(self):
    -32        """
    -33        Placeholder method for running the module.
    -34
    -35        This method should be implemented by subclasses to define the specific behavior of the module.
    -36        """
    -37        raise NotImplementedError("Subclasses must implement this method")
    +            
    32    def run(self):
    +33        """
    +34        Placeholder method for running the module.
    +35
    +36        This method should be implemented by subclasses to define the specific behavior of the module.
    +37        """
    +38        raise NotImplementedError("Subclasses must implement this method")
     
    @@ -327,15 +350,18 @@

    -
    39    def processArguments(self, parser, arguments):
    -40        if type(arguments) == list:
    -41            arguments = ' '.join(arguments)
    -42        
    -43        __iterableArguments = shlex.split(arguments)
    -44
    -45        self.options = parser.parse_args(__iterableArguments)
    -46
    -47        return self.options
    +            
    40    def processArguments(self, parser, arguments):
    +41        if type(arguments) == list:
    +42            arguments = ' '.join(arguments)
    +43        
    +44        __iterableArguments = shlex.split(arguments)
    +45
    +46        try:
    +47            self.options = parser.parse_args(__iterableArguments)
    +48        except SystemExit as e:
    +49            pass
    +50
    +51        return self.options
     
    diff --git a/documentation/smbclientng/core/ModuleArgumentParser.html b/documentation/smbclientng/core/ModuleArgumentParser.html index 1bb8040..f378eaa 100644 --- a/documentation/smbclientng/core/ModuleArgumentParser.html +++ b/documentation/smbclientng/core/ModuleArgumentParser.html @@ -33,6 +33,9 @@

    API Documentation

  • ModuleArgumentParser
      +
    • + exit_on_error +
    • error
    • @@ -86,19 +89,21 @@

      24 Overrides the default error handling to provide a more informative error message and display the help text. 25 """ 26 -27 def error(self, message): -28 """ -29 Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display. -30 -31 This method is called when ArgumentParser encounters an error. It writes the error message to stderr, -32 displays the help message, and then exits the program with a status code of 2. -33 -34 Args: -35 message (str): The error message to be displayed. -36 """ -37 -38 sys.stderr.write('[!] Error: %s\n' % message) -39 self.print_help() +27 exit_on_error = False +28 +29 def error(self, message): +30 """ +31 Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display. +32 +33 This method is called when ArgumentParser encounters an error. It writes the error message to stderr, +34 displays the help message, and then exits the program with a status code of 2. +35 +36 Args: +37 message (str): The error message to be displayed. +38 """ +39 +40 self.print_help() +41 sys.stderr.write('\n[!] Error: %s\n' % message)

  • @@ -129,19 +134,21 @@

    25 Overrides the default error handling to provide a more informative error message and display the help text. 26 """ 27 -28 def error(self, message): -29 """ -30 Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display. -31 -32 This method is called when ArgumentParser encounters an error. It writes the error message to stderr, -33 displays the help message, and then exits the program with a status code of 2. -34 -35 Args: -36 message (str): The error message to be displayed. -37 """ -38 -39 sys.stderr.write('[!] Error: %s\n' % message) -40 self.print_help() +28 exit_on_error = False +29 +30 def error(self, message): +31 """ +32 Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display. +33 +34 This method is called when ArgumentParser encounters an error. It writes the error message to stderr, +35 displays the help message, and then exits the program with a status code of 2. +36 +37 Args: +38 message (str): The error message to be displayed. +39 """ +40 +41 self.print_help() +42 sys.stderr.write('\n[!] Error: %s\n' % message) @@ -159,6 +166,18 @@

    +
    +
    + exit_on_error = +False + + +
    + + + + +
    @@ -170,19 +189,19 @@

    -
    28    def error(self, message):
    -29        """
    -30        Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display.
    -31
    -32        This method is called when ArgumentParser encounters an error. It writes the error message to stderr,
    -33        displays the help message, and then exits the program with a status code of 2.
    -34
    -35        Args:
    -36            message (str): The error message to be displayed.
    -37        """
    -38
    -39        sys.stderr.write('[!] Error: %s\n' % message)
    -40        self.print_help()
    +            
    30    def error(self, message):
    +31        """
    +32        Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display.
    +33
    +34        This method is called when ArgumentParser encounters an error. It writes the error message to stderr,
    +35        displays the help message, and then exits the program with a status code of 2.
    +36
    +37        Args:
    +38            message (str): The error message to be displayed.
    +39        """
    +40
    +41        self.print_help()
    +42        sys.stderr.write('\n[!] Error: %s\n' % message)
     
    @@ -209,7 +228,6 @@
    Inherited Members
    fromfile_prefix_chars
    add_help
    allow_abbrev
    -
    exit_on_error
    add_subparsers
    parse_args
    parse_known_args
    diff --git a/documentation/smbclientng/core/SMBSession.html b/documentation/smbclientng/core/SMBSession.html index 96605e1..90e68ec 100644 --- a/documentation/smbclientng/core/SMBSession.html +++ b/documentation/smbclientng/core/SMBSession.html @@ -40,28 +40,16 @@

    API Documentation

    config
  • - address + logger
  • - domain + host
  • - username + port
  • - password -
  • -
  • - lmhash -
  • -
  • - nthash -
  • -
  • - use_kerberos -
  • -
  • - kdcHost + credentials
  • smbClient @@ -82,13 +70,13 @@

    API Documentation

    smb_tree_id
  • - init_smb_session + close_smb_session
  • - close_smb_session + init_smb_session
  • - read_file + ping_smb_session
  • find @@ -126,15 +114,15 @@

    API Documentation

  • path_isfile
  • -
  • - ping_smb_session -
  • put_file
  • put_file_recursively
  • +
  • + read_file +
  • rmdir
  • @@ -196,1227 +184,1287 @@

    14import sys 15import traceback 16from smbclientng.core.LocalFileIO import LocalFileIO - 17from smbclientng.core.utils import b_filesize, STYPE_MASK + 17from smbclientng.core.utils import b_filesize, STYPE_MASK, is_port_open 18 19 20class SMBSession(object): 21 """ - 22 Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. - 23 It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares. - 24 - 25 Attributes: - 26 address (str): The IP address or hostname of the SMB server. - 27 domain (str): The domain name for SMB server authentication. - 28 username (str): The username for SMB server authentication. - 29 password (str): The password for SMB server authentication. - 30 lmhash (str): The LM hash of the user's password, if available. - 31 nthash (str): The NT hash of the user's password, if available. - 32 use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. - 33 kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. - 34 debug (bool): A flag to enable debug output. - 35 smbClient (object): The SMB client object used for the connection. - 36 connected (bool): A flag to check the status of the connection. + 22 Represents an SMB session for interacting with an SMB server. + 23 + 24 This class provides methods to manage and interact with an SMB server, including + 25 connecting to the server, listing shares, uploading and downloading files, and + 26 managing directories and files on the server. It handles session initialization, + 27 authentication, and cleanup. + 28 + 29 Attributes: + 30 host (str): The hostname or IP address of the SMB server. + 31 port (int): The port number on which the SMB server is listening. + 32 credentials (dict): Authentication credentials for the SMB server. + 33 config (dict, optional): Configuration options for the SMB session. + 34 smbClient (impacket.smbconnection.SMBConnection): The SMB connection instance. + 35 connected (bool): Connection status to the SMB server. + 36 available_shares (dict): A dictionary of available SMB shares. 37 smb_share (str): The current SMB share in use. - 38 smb_path (str): The current path within the SMB share. - 39 - 40 Methods: - 41 __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): - 42 Initializes the SMBSession with the specified parameters. - 43 init_smb_session(): - 44 Initializes the SMB session by connecting to the server and authenticating using the specified method. - 45 """ - 46 - 47 def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None): - 48 super(SMBSession, self).__init__() - 49 # Objects - 50 self.config = config - 51 - 52 # Target server - 53 self.address = address - 54 - 55 # Credentials - 56 self.domain = domain - 57 self.username = username - 58 self.password = password - 59 self.lmhash = lmhash - 60 self.nthash = nthash - 61 self.use_kerberos = use_kerberos - 62 self.kdcHost = kdcHost - 63 - 64 self.smbClient = None - 65 self.connected = False + 38 smb_cwd (str): The current working directory on the SMB share. + 39 smb_tree_id (int): The tree ID of the connected SMB share. + 40 + 41 Methods: + 42 close_smb_session(): Closes the current SMB session. + 43 init_smb_session(): Initializes the SMB session with the server. + 44 list_shares(): Lists all shares available on the SMB server. + 45 set_share(shareName): Sets the current SMB share. + 46 set_cwd(path): Sets the current working directory on the SMB share. + 47 put_file(localpath): Uploads a file to the current SMB share. + 48 get_file(remotepath, localpath): Downloads a file from the SMB share. + 49 mkdir(path): Creates a directory on the SMB share. + 50 rmdir(path): Removes a directory from the SMB share. + 51 rm(path): Removes a file from the SMB share. + 52 read_file(path): Reads a file from the SMB share. + 53 test_rights(sharename): Tests read and write access rights on a share. + 54 """ + 55 + 56 def __init__(self, host, port, credentials, config=None, logger=None): + 57 super(SMBSession, self).__init__() + 58 # Objects + 59 self.config = config + 60 self.logger = logger + 61 + 62 # Target server + 63 self.host = host + 64 # Target port (by default on 445) + 65 self.port = port 66 - 67 self.available_shares = {} - 68 self.smb_share = None - 69 self.smb_cwd = "" - 70 self.smb_tree_id = None - 71 - 72 self.list_shares() - 73 - 74 # Connect and disconnect SMB session - 75 - 76 def init_smb_session(self): - 77 """ - 78 Initializes and establishes a session with the SMB server. + 67 # Credentials + 68 self.credentials = credentials + 69 + 70 self.smbClient = None + 71 self.connected = False + 72 + 73 self.available_shares = {} + 74 self.smb_share = None + 75 self.smb_cwd = "" + 76 self.smb_tree_id = None + 77 + 78 self.list_shares() 79 - 80 This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. - 81 It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization. - 82 - 83 The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True. - 84 - 85 Returns: - 86 bool: True if the connection and authentication are successful, False otherwise. - 87 """ + 80 # Connect and disconnect SMB session + 81 + 82 def close_smb_session(self): + 83 """ + 84 Closes the current SMB session by disconnecting the SMB client. + 85 + 86 This method ensures that the SMB client connection is properly closed. It checks if the client is connected + 87 and if so, it closes the connection and resets the connection status. 88 - 89 self.connected = False - 90 - 91 if self.config.debug: - 92 print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address) - 93 try: - 94 self.smbClient = impacket.smbconnection.SMBConnection( - 95 remoteName=self.address, - 96 remoteHost=self.address, - 97 sess_port=int(445) - 98 ) - 99 except OSError as err: - 100 print("[!] %s" % err) - 101 self.smbClient = None + 89 Raises: + 90 Exception: If the SMB client is not initialized or if there's an error during the disconnection process. + 91 """ + 92 + 93 if self.smbClient is not None: + 94 if self.connected: + 95 self.smbClient.close() + 96 self.connected = False + 97 self.logger.debug("[+] SMB connection closed successfully.") + 98 else: + 99 self.logger.debug("[!] No active SMB connection to close.") + 100 else: + 101 raise Exception("SMB client is not initialized.") 102 - 103 if self.smbClient is not None: - 104 if self.use_kerberos: - 105 if self.config.debug: - 106 print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username)) - 107 try: - 108 self.connected = self.smbClient.kerberosLogin( - 109 user=self.username, - 110 password=self.password, - 111 domain=self.domain, - 112 lmhash=self.lmhash, - 113 nthash=self.nthash, - 114 aesKey=self.aesKey, - 115 kdcHost=self.kdcHost - 116 ) - 117 except impacket.smbconnection.SessionError as err: - 118 if self.config.debug: - 119 traceback.print_exc() - 120 print("[!] Could not login: %s" % err) - 121 self.connected = False - 122 - 123 else: - 124 if self.config.debug: - 125 print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username)) - 126 try: - 127 self.connected = self.smbClient.login( - 128 user=self.username, - 129 password=self.password, - 130 domain=self.domain, - 131 lmhash=self.lmhash, - 132 nthash=self.nthash - 133 ) - 134 except impacket.smbconnection.SessionError as err: - 135 if self.config.debug: - 136 traceback.print_exc() - 137 print("[!] Could not login: %s" % err) - 138 self.connected = False - 139 - 140 if self.connected: - 141 print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) - 142 else: - 143 print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username)) - 144 - 145 return self.connected - 146 - 147 def close_smb_session(self): - 148 """ - 149 Closes the current SMB session by disconnecting the SMB client. - 150 - 151 This method ensures that the SMB client connection is properly closed. It checks if the client is connected - 152 and if so, it closes the connection and resets the connection status. + 103 def init_smb_session(self): + 104 """ + 105 Initializes and establishes a session with the SMB server. + 106 + 107 This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. + 108 It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization. + 109 + 110 The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True. + 111 + 112 Returns: + 113 bool: True if the connection and authentication are successful, False otherwise. + 114 """ + 115 + 116 self.connected = False + 117 + 118 self.logger.debug("[>] Connecting to remote SMB server '%s' ... " % self.host) + 119 + 120 try: + 121 if is_port_open(self.host, self.port): + 122 self.smbClient = impacket.smbconnection.SMBConnection( + 123 remoteName=self.host, + 124 remoteHost=self.host, + 125 sess_port=int(self.port) + 126 ) + 127 else: + 128 self.connected = False + 129 except OSError as err: + 130 if self.config.debug: + 131 traceback.print_exc() + 132 self.logger.error(err) + 133 self.smbClient = None + 134 + 135 if self.smbClient is not None: + 136 if self.credentials.use_kerberos: + 137 self.logger.debug("[>] Authenticating as '%s\\%s' with kerberos ... " % (self.credentials.domain, self.credentials.username)) + 138 try: + 139 self.connected = self.smbClient.kerberosLogin( + 140 user=self.credentials.username, + 141 password=self.credentials.password, + 142 domain=self.credentials.domain, + 143 lmhash=self.credentials.lm_hex, + 144 nthash=self.credentials.nt_hex, + 145 aesKey=self.credentials.aesKey, + 146 kdcHost=self.credentials.kdcHost + 147 ) + 148 except impacket.smbconnection.SessionError as err: + 149 if self.config.debug: + 150 traceback.print_exc() + 151 self.logger.error("Could not login: %s" % err) + 152 self.connected = False 153 - 154 Raises: - 155 Exception: If the SMB client is not initialized or if there's an error during the disconnection process. - 156 """ - 157 - 158 if self.smbClient is not None: - 159 if self.connected: - 160 self.smbClient.close() - 161 self.connected = False - 162 if self.config.debug: - 163 print("[+] SMB connection closed successfully.") - 164 else: - 165 if self.config.debug: - 166 print("[!] No active SMB connection to close.") - 167 else: - 168 raise Exception("SMB client is not initialized.") - 169 - 170 # Operations - 171 - 172 def read_file(self, path=None): - 173 if self.path_isfile(path=path): - 174 tmp_file_path = self.smb_cwd + ntpath.sep + path - 175 matches = self.smbClient.listPath( - 176 shareName=self.smb_share, - 177 path=tmp_file_path - 178 ) - 179 - 180 fh = io.BytesIO() - 181 try: - 182 # opening the files in streams instead of mounting shares allows - 183 # for running the script from unprivileged containers - 184 self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write) - 185 except impacket.smbconnection.SessionError as e: - 186 return None - 187 rawdata = fh.getvalue() - 188 fh.close() - 189 return rawdata - 190 else: - 191 print("[!] Remote path '%s' is not a file." % path) - 192 - 193 def find(self, paths=[], callback=None): - 194 def recurse_action(paths=[], depth=0, callback=None): - 195 if callback is None: - 196 return [] - 197 - 198 next_directories_to_explore = [] - 199 - 200 for path in paths: - 201 remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path) - 202 entries = [] - 203 - 204 try: - 205 entries = self.smbClient.listPath( - 206 shareName=self.smb_share, - 207 path=(remote_smb_path + ntpath.sep + '*') - 208 ) - 209 except impacket.smbconnection.SessionError as err: - 210 continue - 211 # Remove dot names - 212 entries = [e for e in entries if e.get_longname() not in [".", ".."]] - 213 # Sort the entries ignoring case - 214 entries = sorted(entries, key=lambda x:x.get_longname().lower()) - 215 - 216 for entry in entries: - 217 if entry.is_directory(): - 218 callback(entry, path + ntpath.sep + entry.get_longname() + ntpath.sep, depth) - 219 else: - 220 callback(entry, path + ntpath.sep + entry.get_longname(), depth) - 221 - 222 # Next directories to explore - 223 for entry in entries: - 224 if entry.is_directory(): - 225 next_directories_to_explore.append(path + ntpath.sep + entry.get_longname() + ntpath.sep) - 226 - 227 return next_directories_to_explore - 228 # - 229 if callback is not None: - 230 depth = 0 - 231 while len(paths) != 0: - 232 paths = recurse_action( - 233 paths=paths, - 234 depth=depth, - 235 callback=callback - 236 ) - 237 depth = depth + 1 - 238 else: - 239 print("[!] SMBSession.find(), callback function cannot be None.") - 240 - 241 def get_file(self, path=None, keepRemotePath=False): - 242 """ - 243 Retrieves a file from the specified path on the SMB share. - 244 - 245 This method attempts to retrieve a file from the given path within the currently connected SMB share. - 246 If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local - 247 file object and writing the contents of the remote file to it using the SMB client's getFile method. - 248 - 249 Parameters: - 250 path (str): The path of the file to retrieve. If None, uses the current smb_path. - 251 - 252 Returns: - 253 None - 254 """ - 255 - 256 # Parse path - 257 path = path.replace('/', ntpath.sep) - 258 if ntpath.sep in path: - 259 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path)) + 154 else: + 155 self.logger.debug("[>] Authenticating as '%s\\%s' with NTLM ... " % (self.credentials.domain, self.credentials.username)) + 156 + 157 try: + 158 self.connected = self.smbClient.login( + 159 user=self.credentials.username, + 160 password=self.credentials.password, + 161 domain=self.credentials.domain, + 162 lmhash=self.credentials.lm_hex, + 163 nthash=self.credentials.nt_hex + 164 ) + 165 except impacket.smbconnection.SessionError as err: + 166 if self.config.debug: + 167 traceback.print_exc() + 168 self.logger.error("Could not login: %s" % err) + 169 self.connected = False + 170 + 171 if self.connected: + 172 self.logger.print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username)) + 173 else: + 174 self.logger.error("Failed to authenticate to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username)) + 175 + 176 return self.connected + 177 + 178 def ping_smb_session(self): + 179 """ + 180 Tests the connectivity to the SMB server by sending an echo command. + 181 + 182 This method attempts to send an echo command to the SMB server to check if the session is still active. + 183 It updates the `connected` attribute of the class based on the success or failure of the echo command. + 184 + 185 Returns: + 186 bool: True if the echo command succeeds (indicating the session is active), False otherwise. + 187 """ + 188 + 189 if not is_port_open(self.host, self.port): + 190 self.connected = False + 191 else: + 192 try: + 193 self.smbClient.getSMBServer().echo() + 194 except Exception as e: + 195 self.connected = False + 196 + 197 return self.connected + 198 + 199 # Operations + 200 + 201 def find(self, paths=[], callback=None): + 202 """ + 203 Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry. + 204 + 205 This method traverses the specified paths on the SMB share, recursively exploring directories and invoking the callback + 206 function on each file or directory found. The callback function is called with three arguments: the entry object, the + 207 full path of the entry, and the current depth of recursion. + 208 + 209 Args: + 210 paths (list, optional): A list of paths to start the search from. Defaults to an empty list. + 211 callback (function, optional): A function to be called on each entry found. The function should accept three arguments: + 212 the entry object, the full path of the entry, and the current depth of recursion. Defaults to None. + 213 + 214 Note: + 215 If the callback function is None, the method will print an error message and return without performing any action. + 216 """ + 217 + 218 def recurse_action(paths=[], depth=0, callback=None): + 219 if callback is None: + 220 return [] + 221 + 222 next_directories_to_explore = [] + 223 + 224 for path in paths: + 225 remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path) + 226 entries = [] + 227 + 228 try: + 229 entries = self.smbClient.listPath( + 230 shareName=self.smb_share, + 231 path=(remote_smb_path + ntpath.sep + '*') + 232 ) + 233 except impacket.smbconnection.SessionError as err: + 234 continue + 235 # Remove dot names + 236 entries = [e for e in entries if e.get_longname() not in [".", ".."]] + 237 # Sort the entries ignoring case + 238 entries = sorted(entries, key=lambda x:x.get_longname().lower()) + 239 + 240 for entry in entries: + 241 if entry.is_directory(): + 242 fullpath = path + ntpath.sep + entry.get_longname() + ntpath.sep + 243 next_directories_to_explore.append(fullpath) + 244 else: + 245 fullpath = path + ntpath.sep + entry.get_longname() + 246 fullpath = re.sub(r'\\\\+', r'\\', fullpath) + 247 callback(entry, fullpath, depth) + 248 + 249 return next_directories_to_explore + 250 # + 251 if callback is not None: + 252 depth = 0 + 253 while len(paths) != 0: + 254 paths = recurse_action( + 255 paths=paths, + 256 depth=depth, + 257 callback=callback + 258 ) + 259 depth = depth + 1 260 else: - 261 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep) - 262 # Parse filename - 263 filename = ntpath.basename(path) - 264 - 265 # Search for the file - 266 matches = self.smbClient.listPath( - 267 shareName=self.smb_share, - 268 path=tmp_search_path + ntpath.sep + '*' - 269 ) + 261 self.logger.error("SMBSession.find(), callback function cannot be None.") + 262 + 263 def get_file(self, path=None, keepRemotePath=False): + 264 """ + 265 Retrieves a file from the specified path on the SMB share. + 266 + 267 This method attempts to retrieve a file from the given path within the currently connected SMB share. + 268 If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local + 269 file object and writing the contents of the remote file to it using the SMB client's getFile method. 270 - 271 # Filter the entries - 272 matching_entries = [] - 273 for entry in matches: - 274 if entry.is_directory(): - 275 # Skip directories - 276 continue - 277 if entry.get_longname() == filename: - 278 matching_entries.append(entry) - 279 elif '*' in filename: - 280 regexp = filename.replace('.', '\\.').replace('*', '.*') - 281 if re.match(regexp, entry.get_longname()): - 282 matching_entries.append(entry) - 283 - 284 matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname()) - 285 - 286 for entry in matching_entries: - 287 if entry.is_directory(): - 288 if self.config.debug: - 289 print("[debug] [>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname())) - 290 else: - 291 try: - 292 if ntpath.sep in path: - 293 outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() - 294 else: - 295 outputfile = entry.get_longname() - 296 f = LocalFileIO( - 297 mode="wb", - 298 path=outputfile, - 299 expected_size=entry.get_filesize(), - 300 debug=self.config.debug, - 301 keepRemotePath=keepRemotePath - 302 ) - 303 self.smbClient.getFile( - 304 shareName=self.smb_share, - 305 pathName=tmp_search_path + ntpath.sep + entry.get_longname(), - 306 callback=f.write - 307 ) - 308 f.close() - 309 except (BrokenPipeError, KeyboardInterrupt) as e: - 310 f.close() - 311 print("\x1b[v\x1b[o\r[!] Interrupted.") - 312 self.close_smb_session() - 313 self.init_smb_session() - 314 - 315 return None - 316 - 317 def get_file_recursively(self, path=None): - 318 """ - 319 Recursively retrieves files from a specified path on the SMB share. - 320 - 321 This method navigates through all directories starting from the given path, - 322 and downloads all files found. It handles directories recursively, ensuring - 323 that all nested files are retrieved. The method skips over directory entries - 324 and handles errors gracefully, attempting to continue the operation where possible. - 325 - 326 Parameters: - 327 path (str): The initial directory path from which to start the recursive file retrieval. - 328 If None, it starts from the root of the configured SMB share. - 329 """ - 330 - 331 def recurse_action(base_dir="", path=[]): - 332 if len(base_dir) == 0: - 333 remote_smb_path = ntpath.sep.join(path) - 334 else: - 335 remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path) - 336 remote_smb_path = ntpath.normpath(remote_smb_path) + 271 Parameters: + 272 path (str): The path of the file to retrieve. If None, uses the current smb_path. + 273 + 274 Returns: + 275 None + 276 """ + 277 + 278 # Parse path + 279 path = path.replace('/', ntpath.sep) + 280 if ntpath.sep in path: + 281 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path)) + 282 else: + 283 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep) + 284 # Parse filename + 285 filename = ntpath.basename(path) + 286 + 287 # Search for the file + 288 matches = self.smbClient.listPath( + 289 shareName=self.smb_share, + 290 path=tmp_search_path + ntpath.sep + '*' + 291 ) + 292 + 293 # Filter the entries + 294 matching_entries = [] + 295 for entry in matches: + 296 if entry.is_directory(): + 297 # Skip directories + 298 continue + 299 if entry.get_longname() == filename: + 300 matching_entries.append(entry) + 301 elif '*' in filename: + 302 regexp = filename.replace('.', '\\.').replace('*', '.*') + 303 if re.match(regexp, entry.get_longname()): + 304 matching_entries.append(entry) + 305 + 306 matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname()) + 307 + 308 for entry in matching_entries: + 309 if entry.is_directory(): + 310 self.logger.debug("[>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname())) + 311 else: + 312 try: + 313 if ntpath.sep in path: + 314 outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname() + 315 else: + 316 outputfile = entry.get_longname() + 317 f = LocalFileIO( + 318 mode="wb", + 319 path=outputfile, + 320 expected_size=entry.get_filesize(), + 321 debug=self.config.debug, + 322 keepRemotePath=keepRemotePath + 323 ) + 324 self.smbClient.getFile( + 325 shareName=self.smb_share, + 326 pathName=tmp_search_path + ntpath.sep + entry.get_longname(), + 327 callback=f.write + 328 ) + 329 f.close() + 330 except (BrokenPipeError, KeyboardInterrupt) as e: + 331 f.close() + 332 print("\x1b[v\x1b[o\r[!] Interrupted.") + 333 self.close_smb_session() + 334 self.init_smb_session() + 335 + 336 return None 337 - 338 entries = self.smbClient.listPath( - 339 shareName=self.smb_share, - 340 path=remote_smb_path + '\\*' - 341 ) - 342 if len(entries) != 0: - 343 files = [entry for entry in entries if not entry.is_directory()] - 344 directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]] - 345 - 346 # Files - 347 if len(files) != 0: - 348 print("[>] Retrieving files of '%s'" % remote_smb_path) - 349 for entry_file in files: - 350 if not entry_file.is_directory(): - 351 f = LocalFileIO( - 352 mode="wb", - 353 path=remote_smb_path + ntpath.sep + entry_file.get_longname(), - 354 expected_size=entry_file.get_filesize(), - 355 keepRemotePath=True, - 356 debug=self.config.debug - 357 ) - 358 try: - 359 self.smbClient.getFile( - 360 shareName=self.smb_share, - 361 pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), - 362 callback=f.write - 363 ) - 364 f.close() - 365 except BrokenPipeError as err: - 366 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) - 367 f.close(remove=True) - 368 break - 369 except Exception as err: - 370 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) - 371 f.close(remove=True) - 372 - 373 # Directories - 374 for entry_directory in directories: - 375 if entry_directory.is_directory(): - 376 recurse_action( - 377 base_dir=self.smb_cwd, - 378 path=path+[entry_directory.get_longname()] - 379 ) - 380 # Entrypoint - 381 try: - 382 recurse_action( - 383 base_dir=self.smb_cwd, - 384 path=[path] - 385 ) - 386 except (BrokenPipeError, KeyboardInterrupt) as e: - 387 print("\x1b[v\x1b[o\r[!] Interrupted.") - 388 self.close_smb_session() - 389 self.init_smb_session() - 390 - 391 def get_entry(self, path=None): - 392 """ - 393 Retrieves information about a specific entry located at the provided path on the SMB share. - 394 - 395 This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None. - 396 - 397 Args: - 398 path (str): The path of the entry to retrieve information about. - 399 - 400 Returns: - 401 Entry: An object representing the entry at the specified path, or None if the entry is not found. - 402 """ - 403 - 404 if self.path_exists(path=path): - 405 matches = self.smbClient.listPath(shareName=self.smb_share, path=path) - 406 - 407 if len(matches) == 1: - 408 return matches[0] - 409 else: - 410 return None - 411 - 412 else: - 413 return None - 414 - 415 def info(self, share=True, server=True): - 416 """ - 417 Displays information about the server and optionally the shares. - 418 - 419 This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share. + 338 def get_file_recursively(self, path=None): + 339 """ + 340 Recursively retrieves files from a specified path on the SMB share. + 341 + 342 This method navigates through all directories starting from the given path, + 343 and downloads all files found. It handles directories recursively, ensuring + 344 that all nested files are retrieved. The method skips over directory entries + 345 and handles errors gracefully, attempting to continue the operation where possible. + 346 + 347 Parameters: + 348 path (str): The initial directory path from which to start the recursive file retrieval. + 349 If None, it starts from the root of the configured SMB share. + 350 """ + 351 + 352 def recurse_action(base_dir="", path=[]): + 353 if len(base_dir) == 0: + 354 remote_smb_path = ntpath.sep.join(path) + 355 else: + 356 remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path) + 357 remote_smb_path = ntpath.normpath(remote_smb_path) + 358 + 359 entries = self.smbClient.listPath( + 360 shareName=self.smb_share, + 361 path=remote_smb_path + '\\*' + 362 ) + 363 if len(entries) != 0: + 364 files = [entry for entry in entries if not entry.is_directory()] + 365 directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]] + 366 + 367 # Files + 368 if len(files) != 0: + 369 self.logger.print("[>] Retrieving files of '%s'" % remote_smb_path) + 370 for entry_file in files: + 371 if not entry_file.is_directory(): + 372 f = LocalFileIO( + 373 mode="wb", + 374 path=remote_smb_path + ntpath.sep + entry_file.get_longname(), + 375 expected_size=entry_file.get_filesize(), + 376 keepRemotePath=True, + 377 debug=self.config.debug + 378 ) + 379 try: + 380 self.smbClient.getFile( + 381 shareName=self.smb_share, + 382 pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), + 383 callback=f.write + 384 ) + 385 f.close() + 386 except BrokenPipeError as err: + 387 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) + 388 f.close(remove=True) + 389 break + 390 except Exception as err: + 391 f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err)) + 392 f.close(remove=True) + 393 + 394 # Directories + 395 for entry_directory in directories: + 396 if entry_directory.is_directory(): + 397 recurse_action( + 398 base_dir=self.smb_cwd, + 399 path=path+[entry_directory.get_longname()] + 400 ) + 401 # Entrypoint + 402 try: + 403 recurse_action( + 404 base_dir=self.smb_cwd, + 405 path=[path] + 406 ) + 407 except (BrokenPipeError, KeyboardInterrupt) as e: + 408 print("\x1b[v\x1b[o\r[!] Interrupted.") + 409 self.close_smb_session() + 410 self.init_smb_session() + 411 + 412 def get_entry(self, path=None): + 413 """ + 414 Retrieves information about a specific entry located at the provided path on the SMB share. + 415 + 416 This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None. + 417 + 418 Args: + 419 path (str): The path of the entry to retrieve information about. 420 - 421 Parameters: - 422 share (bool): If True, display information about the current share. - 423 server (bool): If True, display information about the server. + 421 Returns: + 422 Entry: An object representing the entry at the specified path, or None if the entry is not found. + 423 """ 424 - 425 Returns: - 426 None - 427 """ - 428 - 429 if server: - 430 if self.config.no_colors: - 431 print("[+] Server:") - 432 print(" ├─NetBIOS:") - 433 print(" │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName())) - 434 print(" │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain())) - 435 print(" ├─DNS:") - 436 print(" │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName())) - 437 print(" │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName())) - 438 print(" ├─OS:") - 439 print(" │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS())) - 440 print(" │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) - 441 print(" ├─Server:") - 442 print(" │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired())) - 443 print(" │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired())) - 444 print(" │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2())) - 445 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] - 446 print(" │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize))) - 447 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] - 448 print(" │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize))) - 449 print(" └─") - 450 else: - 451 print("[+] Server:") - 452 print(" ├─NetBIOS:") - 453 print(" │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName())) - 454 print(" │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain())) - 455 print(" ├─DNS:") - 456 print(" │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName())) - 457 print(" │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName())) - 458 print(" ├─OS:") - 459 print(" │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS())) - 460 print(" │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) - 461 print(" ├─Server:") - 462 print(" │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired())) - 463 print(" │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired())) - 464 print(" │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2())) - 465 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] - 466 print(" │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize))) - 467 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] - 468 print(" │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize))) - 469 print(" └─") - 470 - 471 if share and self.smb_share is not None: - 472 share_name = self.available_shares.get(self.smb_share.lower(), "")["name"] - 473 share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"] - 474 share_type = self.available_shares.get(self.smb_share.lower(), "")["type"] - 475 share_type =', '.join([s.replace("STYPE_","") for s in share_type]) - 476 share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"] - 477 if self.config.no_colors: - 478 print("\n[+] Share:") - 479 print(" ├─ Name ──────────── : %s" % (share_name)) - 480 print(" ├─ Description ───── : %s" % (share_comment)) - 481 print(" ├─ Type ──────────── : %s" % (share_type)) - 482 print(" └─ Raw type value ── : %s" % (share_rawtype)) - 483 else: - 484 print("\n[+] Share:") - 485 print(" ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name)) - 486 print(" ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment)) - 487 print(" ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type)) - 488 print(" └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype)) - 489 - 490 def list_contents(self, path=None): - 491 """ - 492 Lists the contents of a specified directory on the SMB share. + 425 if self.path_exists(path=path): + 426 matches = self.smbClient.listPath( + 427 shareName=self.smb_share, + 428 path=path + 429 ) + 430 + 431 if len(matches) == 1: + 432 return matches[0] + 433 else: + 434 return None + 435 else: + 436 return None + 437 + 438 def info(self, share=True, server=True): + 439 """ + 440 Displays information about the server and optionally the shares. + 441 + 442 This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share. + 443 + 444 Parameters: + 445 share (bool): If True, display information about the current share. + 446 server (bool): If True, display information about the server. + 447 + 448 Returns: + 449 None + 450 """ + 451 + 452 if server: + 453 if self.config.no_colors: + 454 self.logger.print("[+] Server:") + 455 self.logger.print(" ├─NetBIOS:") + 456 self.logger.print(" │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName())) + 457 self.logger.print(" │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain())) + 458 self.logger.print(" ├─DNS:") + 459 self.logger.print(" │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName())) + 460 self.logger.print(" │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName())) + 461 self.logger.print(" ├─OS:") + 462 self.logger.print(" │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS())) + 463 self.logger.print(" │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) + 464 self.logger.print(" ├─Server:") + 465 self.logger.print(" │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired())) + 466 self.logger.print(" │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired())) + 467 self.logger.print(" │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2())) + 468 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] + 469 self.logger.print(" │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize))) + 470 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] + 471 self.logger.print(" │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize))) + 472 self.logger.print(" └─") + 473 else: + 474 self.logger.print("[+] Server:") + 475 self.logger.print(" ├─NetBIOS:") + 476 self.logger.print(" │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName())) + 477 self.logger.print(" │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain())) + 478 self.logger.print(" ├─DNS:") + 479 self.logger.print(" │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName())) + 480 self.logger.print(" │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName())) + 481 self.logger.print(" ├─OS:") + 482 self.logger.print(" │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS())) + 483 self.logger.print(" │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild())) + 484 self.logger.print(" ├─Server:") + 485 self.logger.print(" │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired())) + 486 self.logger.print(" │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired())) + 487 self.logger.print(" │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2())) + 488 MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"] + 489 self.logger.print(" │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize))) + 490 MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"] + 491 self.logger.print(" │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize))) + 492 self.logger.print(" └─") 493 - 494 This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path` - 495 is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with - 496 the long names of the files and directories as keys and their respective SMB entry objects as values. - 497 - 498 Args: - 499 shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. - 500 path (str, optional): The directory path to list contents from. Defaults to the current path if None. - 501 - 502 Returns: - 503 dict: A dictionary with file and directory names as keys and their SMB entry objects as values. - 504 """ - 505 - 506 dest_path = [self.smb_cwd.rstrip(ntpath.sep),] - 507 if path is not None and len(path) > 0: - 508 dest_path.append(path.rstrip(ntpath.sep)) - 509 dest_path.append('*') - 510 path = ntpath.sep.join(dest_path) - 511 - 512 contents = {} - 513 entries = self.smbClient.listPath( - 514 shareName=self.smb_share, - 515 path=path - 516 ) - 517 for entry in entries: - 518 contents[entry.get_longname()] = entry - 519 - 520 return contents - 521 - 522 def list_shares(self): - 523 """ - 524 Lists all the shares available on the connected SMB server. - 525 - 526 This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary - 527 with key-value pairs where the key is the share name and the value is a dictionary containing details about the share - 528 such as its name, type, raw type, and any comments associated with the share. - 529 - 530 Returns: - 531 dict: A dictionary containing information about each share available on the server. - 532 """ - 533 - 534 self.available_shares = {} - 535 - 536 if self.connected: - 537 if self.smbClient is not None: - 538 resp = self.smbClient.listShares() - 539 - 540 for share in resp: - 541 # SHARE_INFO_1 structure (lmshare.h) - 542 # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1 - 543 sharename = share["shi1_netname"][:-1] - 544 sharecomment = share["shi1_remark"][:-1] - 545 sharetype = share["shi1_type"] - 546 - 547 self.available_shares[sharename.lower()] = { - 548 "name": sharename, - 549 "type": STYPE_MASK(sharetype), - 550 "rawtype": sharetype, - 551 "comment": sharecomment - 552 } - 553 else: - 554 print("[!] Error: SMBSession.smbClient is None.") - 555 - 556 return self.available_shares - 557 - 558 def mkdir(self, path=None): - 559 """ - 560 Creates a directory at the specified path on the SMB share. - 561 - 562 This method takes a path and attempts to create the directory structure on the SMB share. If the path includes - 563 nested directories, it will create each directory in the sequence. If a directory already exists, it will skip - 564 the creation for that directory without raising an error. - 565 - 566 Args: - 567 path (str, optional): The full path of the directory to create on the SMB share. Defaults to None. - 568 - 569 Note: - 570 The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility. - 571 """ - 572 - 573 if path is not None: - 574 # Prepare path - 575 path = path.replace('/',ntpath.sep) - 576 if ntpath.sep in path: - 577 path = path.strip(ntpath.sep).split(ntpath.sep) - 578 else: - 579 path = [path] + 494 if share and self.smb_share is not None: + 495 share_name = self.available_shares.get(self.smb_share.lower(), "")["name"] + 496 share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"] + 497 share_type = self.available_shares.get(self.smb_share.lower(), "")["type"] + 498 share_type =', '.join([s.replace("STYPE_","") for s in share_type]) + 499 share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"] + 500 if self.config.no_colors: + 501 self.logger.print("\n[+] Share:") + 502 self.logger.print(" ├─ Name ──────────── : %s" % (share_name)) + 503 self.logger.print(" ├─ Description ───── : %s" % (share_comment)) + 504 self.logger.print(" ├─ Type ──────────── : %s" % (share_type)) + 505 self.logger.print(" └─ Raw type value ── : %s" % (share_rawtype)) + 506 else: + 507 self.logger.print("\n[+] Share:") + 508 self.logger.print(" ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name)) + 509 self.logger.print(" ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment)) + 510 self.logger.print(" ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type)) + 511 self.logger.print(" └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype)) + 512 + 513 def list_contents(self, path=None): + 514 """ + 515 Lists the contents of a specified directory on the SMB share. + 516 + 517 This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path` + 518 is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with + 519 the long names of the files and directories as keys and their respective SMB entry objects as values. + 520 + 521 Args: + 522 shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None. + 523 path (str, optional): The directory path to list contents from. Defaults to the current path if None. + 524 + 525 Returns: + 526 dict: A dictionary with file and directory names as keys and their SMB entry objects as values. + 527 """ + 528 + 529 dest_path = [self.smb_cwd.rstrip(ntpath.sep),] + 530 if path is not None and len(path) > 0: + 531 dest_path.append(path.rstrip(ntpath.sep)) + 532 dest_path.append('*') + 533 path = ntpath.sep.join(dest_path) + 534 + 535 contents = {} + 536 entries = self.smbClient.listPath( + 537 shareName=self.smb_share, + 538 path=path + 539 ) + 540 for entry in entries: + 541 contents[entry.get_longname()] = entry + 542 + 543 return contents + 544 + 545 def list_shares(self): + 546 """ + 547 Lists all the shares available on the connected SMB server. + 548 + 549 This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary + 550 with key-value pairs where the key is the share name and the value is a dictionary containing details about the share + 551 such as its name, type, raw type, and any comments associated with the share. + 552 + 553 Returns: + 554 dict: A dictionary containing information about each share available on the server. + 555 """ + 556 + 557 self.available_shares = {} + 558 + 559 if self.connected: + 560 if self.smbClient is not None: + 561 resp = self.smbClient.listShares() + 562 + 563 for share in resp: + 564 # SHARE_INFO_1 structure (lmshare.h) + 565 # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1 + 566 sharename = share["shi1_netname"][:-1] + 567 sharecomment = share["shi1_remark"][:-1] + 568 sharetype = share["shi1_type"] + 569 + 570 self.available_shares[sharename.lower()] = { + 571 "name": sharename, + 572 "type": STYPE_MASK(sharetype), + 573 "rawtype": sharetype, + 574 "comment": sharecomment + 575 } + 576 else: + 577 self.logger.error("Error: SMBSession.smbClient is None.") + 578 + 579 return self.available_shares 580 - 581 # Create each dir in the path - 582 for depth in range(1, len(path)+1): - 583 tmp_path = ntpath.sep.join(path[:depth]) - 584 try: - 585 self.smbClient.createDirectory( - 586 shareName=self.smb_share, - 587 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep) - 588 ) - 589 except impacket.smbconnection.SessionError as err: - 590 if err.getErrorCode() == 0xc0000035: - 591 # STATUS_OBJECT_NAME_COLLISION - 592 # Remote directory already created, this is normal - 593 # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19 - 594 pass - 595 else: - 596 print("[!] Failed to create directory '%s': %s" % (tmp_path, err)) - 597 if self.config.debug: - 598 traceback.print_exc() - 599 else: - 600 pass - 601 - 602 def mount(self, local_mount_point, remote_path): - 603 """ - 604 Generates the command to mount an SMB share on different platforms. - 605 - 606 This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform. - 607 It constructs the mount command using the provided parameters and executes it using the os.system() function. - 608 - 609 Args: - 610 local_mount_point (str): The local directory where the SMB share will be mounted. - 611 remote_path (str): The remote path on the SMB share to be mounted. - 612 - 613 Note: - 614 - For Windows platform, the command uses 'net use' to mount the share. - 615 - For Linux platform, the command uses 'mount' to mount the share. - 616 - For macOS platform, the command uses 'mount_smbfs' to mount the share. - 617 - If the platform is not supported, an error message is displayed. - 618 - 619 Returns: - 620 None - 621 """ - 622 - 623 if not os.path.exists(local_mount_point): - 624 pass - 625 - 626 if sys.platform.startswith('win'): - 627 remote_path = remote_path.replace('/',ntpath.sep) - 628 command = f"net use {local_mount_point} \\\\{self.address}\\{self.smb_share}\\{remote_path}" - 629 - 630 elif sys.platform.startswith('linux'): - 631 remote_path = remote_path.replace(ntpath.sep,'/') - 632 command = f"mount -t cifs //{self.address}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.username},password={self.password}" - 633 - 634 elif sys.platform.startswith('darwin'): - 635 remote_path = remote_path.replace(ntpath.sep,'/') - 636 command = f"mount_smbfs //{self.username}:{self.password}@{self.address}/{self.smb_share}/{remote_path} {local_mount_point}" - 637 - 638 else: - 639 command = None - 640 print("[!] Unsupported platform for mounting SMB share.") - 641 - 642 if command is not None: - 643 if self.config.debug: - 644 print("[debug] Executing: %s" % command) - 645 os.system(command) - 646 - 647 def path_exists(self, path=None): - 648 """ - 649 Checks if the specified path exists on the SMB share. - 650 - 651 This method determines if a given path exists on the SMB share by attempting to list the contents of the path. - 652 If the path listing is successful and returns one or more entries, the path is considered to exist. - 653 - 654 Args: - 655 path (str, optional): The path to check on the SMB share. Defaults to None. - 656 - 657 Returns: - 658 bool: True if the path exists, False otherwise or if an error occurs. - 659 """ - 660 - 661 if path is not None: - 662 path = path.replace('*','') - 663 try: - 664 contents = self.smbClient.listPath( - 665 shareName=self.smb_share, - 666 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) - 667 ) - 668 return (len(contents) != 0) - 669 except Exception as e: - 670 return False - 671 else: - 672 return False - 673 - 674 def path_isdir(self, pathFromRoot=None): - 675 """ - 676 Checks if the specified path is a directory on the SMB share. - 677 - 678 This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the - 679 contents of the path and filtering for entries that match the basename of the path and are marked as directories. - 680 - 681 Args: - 682 path (str, optional): The path to check on the SMB share. Defaults to None. + 581 def mkdir(self, path=None): + 582 """ + 583 Creates a directory at the specified path on the SMB share. + 584 + 585 This method takes a path and attempts to create the directory structure on the SMB share. If the path includes + 586 nested directories, it will create each directory in the sequence. If a directory already exists, it will skip + 587 the creation for that directory without raising an error. + 588 + 589 Args: + 590 path (str, optional): The full path of the directory to create on the SMB share. Defaults to None. + 591 + 592 Note: + 593 The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility. + 594 """ + 595 + 596 if path is not None: + 597 # Prepare path + 598 path = path.replace('/',ntpath.sep) + 599 if ntpath.sep in path: + 600 path = path.strip(ntpath.sep).split(ntpath.sep) + 601 else: + 602 path = [path] + 603 + 604 # Create each dir in the path + 605 for depth in range(1, len(path)+1): + 606 tmp_path = ntpath.sep.join(path[:depth]) + 607 try: + 608 self.smbClient.createDirectory( + 609 shareName=self.smb_share, + 610 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep) + 611 ) + 612 except impacket.smbconnection.SessionError as err: + 613 if err.getErrorCode() == 0xc0000035: + 614 # STATUS_OBJECT_NAME_COLLISION + 615 # Remote directory already created, this is normal + 616 # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19 + 617 pass + 618 else: + 619 self.logger.error("Failed to create directory '%s': %s" % (tmp_path, err)) + 620 if self.config.debug: + 621 traceback.print_exc() + 622 else: + 623 pass + 624 + 625 def mount(self, local_mount_point, remote_path): + 626 """ + 627 Generates the command to mount an SMB share on different platforms. + 628 + 629 This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform. + 630 It constructs the mount command using the provided parameters and executes it using the os.system() function. + 631 + 632 Args: + 633 local_mount_point (str): The local directory where the SMB share will be mounted. + 634 remote_path (str): The remote path on the SMB share to be mounted. + 635 + 636 Note: + 637 - For Windows platform, the command uses 'net use' to mount the share. + 638 - For Linux platform, the command uses 'mount' to mount the share. + 639 - For macOS platform, the command uses 'mount_smbfs' to mount the share. + 640 - If the platform is not supported, an error message is displayed. + 641 + 642 Returns: + 643 None + 644 """ + 645 + 646 if not os.path.exists(local_mount_point): + 647 pass + 648 + 649 if sys.platform.startswith('win'): + 650 remote_path = remote_path.replace('/',ntpath.sep) + 651 command = f"net use {local_mount_point} \\\\{self.host}\\{self.smb_share}\\{remote_path}" + 652 + 653 elif sys.platform.startswith('linux'): + 654 remote_path = remote_path.replace(ntpath.sep,'/') + 655 command = f"mount -t cifs //{self.host}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.credentials.username},password={self.credentials.password}" + 656 + 657 elif sys.platform.startswith('darwin'): + 658 remote_path = remote_path.replace(ntpath.sep,'/') + 659 command = f"mount_smbfs //{self.credentials.username}:{self.credentials.password}@{self.host}/{self.smb_share}/{remote_path} {local_mount_point}" + 660 + 661 else: + 662 command = None + 663 self.logger.error("Unsupported platform for mounting SMB share.") + 664 + 665 if command is not None: + 666 if self.config.debug: + 667 self.logger.debug("Executing: %s" % command) + 668 os.system(command) + 669 + 670 def path_exists(self, path=None): + 671 """ + 672 Checks if the specified path exists on the SMB share. + 673 + 674 This method determines if a given path exists on the SMB share by attempting to list the contents of the path. + 675 If the path listing is successful and returns one or more entries, the path is considered to exist. + 676 + 677 Args: + 678 path (str, optional): The path to check on the SMB share. Defaults to None. + 679 + 680 Returns: + 681 bool: True if the path exists, False otherwise or if an error occurs. + 682 """ 683 - 684 Returns: - 685 bool: True if the path is a directory, False otherwise or if an error occurs. - 686 """ - 687 - 688 if pathFromRoot is not None: - 689 # Replace slashes if any - 690 path = pathFromRoot.replace('/', ntpath.sep) - 691 - 692 # Strip wildcards to avoid injections - 693 path = path.replace('*','') - 694 - 695 # Normalize path and strip leading backslash - 696 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) - 697 - 698 if path.strip() in ['', '.', '..']: - 699 # By defininition they exist on the filesystem - 700 return True - 701 else: - 702 try: - 703 contents = self.smbClient.listPath( - 704 shareName=self.smb_share, - 705 path=path+'*' - 706 ) - 707 # Filter on directories - 708 contents = [ - 709 c for c in contents - 710 if c.get_longname() == ntpath.basename(path) and c.is_directory() - 711 ] - 712 return (len(contents) != 0) - 713 except Exception as e: - 714 return False - 715 else: - 716 return False + 684 if path is not None: + 685 path = path.replace('*','') + 686 path = path.replace('/', ntpath.sep) + 687 try: + 688 contents = self.smbClient.listPath( + 689 shareName=self.smb_share, + 690 path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep) + 691 ) + 692 return (len(contents) != 0) + 693 except Exception as e: + 694 return False + 695 else: + 696 return False + 697 + 698 def path_isdir(self, pathFromRoot=None): + 699 """ + 700 Checks if the specified path is a directory on the SMB share. + 701 + 702 This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the + 703 contents of the path and filtering for entries that match the basename of the path and are marked as directories. + 704 + 705 Args: + 706 path (str, optional): The path to check on the SMB share. Defaults to None. + 707 + 708 Returns: + 709 bool: True if the path is a directory, False otherwise or if an error occurs. + 710 """ + 711 + 712 if pathFromRoot is not None: + 713 # Strip wildcards to avoid injections + 714 path = pathFromRoot.replace('*','') + 715 # Replace slashes if any + 716 path = path.replace('/', ntpath.sep) 717 - 718 def path_isfile(self, path=None): - 719 """ - 720 Checks if the specified path is a file on the SMB share. - 721 - 722 This method determines if a given path corresponds to a file on the SMB share. It does this by listing the - 723 contents of the path and filtering for entries that match the basename of the path and are not marked as directories. - 724 - 725 Args: - 726 path (str, optional): The path to check on the SMB share. Defaults to None. - 727 - 728 Returns: - 729 bool: True if the path is a file, False otherwise or if an error occurs. - 730 """ - 731 - 732 if path is not None: - 733 path = path.replace('*','') - 734 search_dir = ntpath.normpath(self.smb_cwd + ntpath.sep + path) - 735 search_dir = ntpath.dirname(search_dir) + ntpath.sep + '*' - 736 try: - 737 contents = self.smbClient.listPath( - 738 shareName=self.smb_share, - 739 path=search_dir - 740 ) - 741 # Filter on files - 742 contents = [ - 743 c for c in contents - 744 if c.get_longname() == ntpath.basename(path) and not c.is_directory() - 745 ] - 746 return (len(contents) != 0) - 747 except Exception as e: - 748 return False - 749 else: - 750 return False - 751 - 752 def ping_smb_session(self): - 753 """ - 754 Tests the connectivity to the SMB server by sending an echo command. - 755 - 756 This method attempts to send an echo command to the SMB server to check if the session is still active. - 757 It updates the `connected` attribute of the class based on the success or failure of the echo command. - 758 - 759 Returns: - 760 bool: True if the echo command succeeds (indicating the session is active), False otherwise. - 761 """ - 762 - 763 try: - 764 self.smbClient.getSMBServer().echo() - 765 except Exception as e: - 766 self.connected = False - 767 return self.connected - 768 - 769 def put_file(self, localpath=None): - 770 """ - 771 Uploads a single file to the SMB share. - 772 - 773 This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. - 774 It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. - 775 General exceptions are caught and logged, with a traceback provided if debugging is enabled. - 776 - 777 Args: - 778 localpath (str, optional): The local file path of the file to be uploaded. Defaults to None. - 779 """ - 780 - 781 # Parse path - 782 localpath = localpath.replace('/', os.path.sep) - 783 if os.path.sep in localpath: - 784 tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath)) - 785 else: - 786 tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep) - 787 # Parse filename - 788 filename = os.path.basename(localpath) - 789 - 790 # Search for the file - 791 matches = os.listdir(tmp_search_path) - 792 # Filter the entries - 793 matching_entries = [] - 794 for entry in matches: - 795 if entry == filename: - 796 matching_entries.append(entry) - 797 elif '*' in filename: - 798 regexp = filename.replace('.', '\\.').replace('*', '.*') - 799 if re.match(regexp, entry): - 800 matching_entries.append(entry) - 801 - 802 matching_entries = sorted(list(set(matching_entries))) + 718 # Normalize path and strip leading backslash + 719 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) + 720 + 721 if path.strip() in ['', '.', '..']: + 722 # By defininition they exist on the filesystem + 723 return True + 724 else: + 725 try: + 726 contents = self.smbClient.listPath( + 727 shareName=self.smb_share, + 728 path=path+'*' + 729 ) + 730 # Filter on directories + 731 contents = [ + 732 c for c in contents + 733 if c.get_longname() == ntpath.basename(path) and c.is_directory() + 734 ] + 735 return (len(contents) != 0) + 736 except Exception as e: + 737 return False + 738 else: + 739 return False + 740 + 741 def path_isfile(self, pathFromRoot=None): + 742 """ + 743 Checks if the specified path is a file on the SMB share. + 744 + 745 This method determines if a given path corresponds to a file on the SMB share. It does this by listing the + 746 contents of the path and filtering for entries that match the basename of the path and are not marked as directories. + 747 + 748 Args: + 749 path (str, optional): The path to check on the SMB share. Defaults to None. + 750 + 751 Returns: + 752 bool: True if the path is a file, False otherwise or if an error occurs. + 753 """ + 754 + 755 if pathFromRoot is not None: + 756 # Strip wildcards to avoid injections + 757 path = pathFromRoot.replace('*','') + 758 # Replace slashes if any + 759 path = path.replace('/', ntpath.sep) + 760 + 761 # Normalize path and strip leading backslash + 762 path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep) + 763 + 764 try: + 765 contents = self.smbClient.listPath( + 766 shareName=self.smb_share, + 767 path=ntpath.dirname(path) + ntpath.sep + '*' + 768 ) + 769 # Filter on files + 770 contents = [ + 771 c for c in contents + 772 if c.get_longname() == ntpath.basename(path) and not c.is_directory() + 773 ] + 774 return (len(contents) != 0) + 775 except Exception as e: + 776 return False + 777 else: + 778 return False + 779 + 780 def put_file(self, localpath=None): + 781 """ + 782 Uploads a single file to the SMB share. + 783 + 784 This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path. + 785 It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session. + 786 General exceptions are caught and logged, with a traceback provided if debugging is enabled. + 787 + 788 Args: + 789 localpath (str, optional): The local file path of the file to be uploaded. Defaults to None. + 790 """ + 791 + 792 # Parse path + 793 localpath = localpath.replace('/', os.path.sep) + 794 if os.path.sep in localpath: + 795 if localpath.startswith(os.path.sep): + 796 # Absolute path + 797 tmp_search_path = os.path.normpath(localpath) + 798 else: + 799 # Relative path + 800 tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath)) + 801 else: + 802 tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep) 803 - 804 # Loop and upload - 805 for localpath in matching_entries: - 806 if os.path.exists(localpath): - 807 if os.path.isfile(localpath): - 808 try: - 809 localfile = os.path.basename(localpath) - 810 f = LocalFileIO( - 811 mode="rb", - 812 path=localpath, - 813 debug=self.config.debug - 814 ) - 815 self.smbClient.putFile( - 816 shareName=self.smb_share, - 817 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), - 818 callback=f.read - 819 ) - 820 f.close() - 821 except (BrokenPipeError, KeyboardInterrupt) as err: - 822 print("[!] Interrupted.") - 823 self.close_smb_session() - 824 self.init_smb_session() - 825 except Exception as err: - 826 print("[!] Failed to upload '%s': %s" % (localfile, err)) - 827 if self.config.debug: - 828 traceback.print_exc() - 829 else: - 830 # [!] The specified localpath is a directory. Use 'put -r <directory>' instead. - 831 pass - 832 else: - 833 # [!] The specified localpath does not exist. - 834 pass - 835 - 836 def put_file_recursively(self, localpath=None): - 837 """ - 838 Recursively uploads files from a specified local directory to the SMB share. - 839 - 840 This method walks through the given local directory and all its subdirectories, uploading each file to the - 841 corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, - 842 it iterates over all files and directories within the local path, creating necessary directories on the SMB share - 843 and uploading files. If the local path is not a directory, it prints an error message. - 844 - 845 Args: - 846 localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None. - 847 """ - 848 - 849 if os.path.exists(localpath): - 850 if os.path.isfile(localpath): - 851 # Iterate over all files and directories within the local path - 852 local_files = {} - 853 for root, dirs, files in os.walk(localpath): - 854 if len(files) != 0: - 855 local_files[root] = files - 856 - 857 # Iterate over the found files - 858 for local_dir_path in sorted(local_files.keys()): - 859 print("[>] Putting files of '%s'" % local_dir_path) - 860 - 861 # Create remote directory - 862 remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep) - 863 self.mkdir( - 864 path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep) - 865 ) - 866 - 867 for local_file_path in local_files[local_dir_path]: - 868 try: - 869 f = LocalFileIO( - 870 mode="rb", - 871 path=local_dir_path + os.path.sep + local_file_path, - 872 debug=self.config.debug - 873 ) - 874 self.smbClient.putFile( - 875 shareName=self.smb_share, - 876 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), - 877 callback=f.read - 878 ) - 879 f.close() + 804 # Parse filename + 805 filename = os.path.basename(localpath) + 806 + 807 # Search for the file + 808 matches = os.listdir(tmp_search_path) + 809 # Filter the entries + 810 matching_entries = [] + 811 for entry in matches: + 812 if entry == filename: + 813 matching_entries.append(entry) + 814 elif '*' in filename: + 815 regexp = filename.replace('.', '\\.').replace('*', '.*') + 816 if re.match(regexp, entry): + 817 matching_entries.append(entry) + 818 + 819 matching_entries = sorted(list(set(matching_entries))) + 820 + 821 # Loop and upload + 822 for localpath in matching_entries: + 823 if os.path.exists(localpath): + 824 if os.path.isfile(localpath): + 825 try: + 826 localfile = os.path.basename(localpath) + 827 f = LocalFileIO( + 828 mode="rb", + 829 path=localpath, + 830 debug=self.config.debug + 831 ) + 832 self.smbClient.putFile( + 833 shareName=self.smb_share, + 834 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), + 835 callback=f.read + 836 ) + 837 f.close() + 838 + 839 except (BrokenPipeError, KeyboardInterrupt) as err: + 840 self.logger.error("Interrupted.") + 841 self.close_smb_session() + 842 self.init_smb_session() + 843 + 844 except (Exception, PermissionError) as err: + 845 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) + 846 f.close(remove=False) + 847 if self.config.debug: + 848 traceback.print_exc() + 849 else: + 850 # [!] The specified localpath is a directory. Use 'put -r <directory>' instead. + 851 pass + 852 else: + 853 # [!] The specified localpath does not exist. + 854 pass + 855 + 856 def put_file_recursively(self, localpath=None): + 857 """ + 858 Recursively uploads files from a specified local directory to the SMB share. + 859 + 860 This method walks through the given local directory and all its subdirectories, uploading each file to the + 861 corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is, + 862 it iterates over all files and directories within the local path, creating necessary directories on the SMB share + 863 and uploading files. If the local path is not a directory, it prints an error message. + 864 + 865 Args: + 866 localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None. + 867 """ + 868 + 869 if os.path.exists(localpath): + 870 if os.path.isdir(localpath): + 871 # Iterate over all files and directories within the local path + 872 local_files = {} + 873 for root, dirs, files in os.walk(localpath): + 874 if len(files) != 0: + 875 local_files[root] = files + 876 + 877 # Iterate over the found files + 878 for local_dir_path in sorted(local_files.keys()): + 879 self.logger.print("[>] Putting files of '%s'" % local_dir_path) 880 - 881 except BrokenPipeError as err: - 882 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) - 883 f.close(remove=True) - 884 break - 885 except Exception as err: - 886 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) - 887 f.close(remove=True) - 888 else: - 889 print("[!] The specified localpath is a file. Use 'put <file>' instead.") - 890 else: - 891 print("[!] The specified localpath does not exist.") - 892 - 893 def rmdir(self, path=None): - 894 """ - 895 Removes a directory from the SMB share at the specified path. - 896 - 897 This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, - 898 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints - 899 the stack trace of the exception. + 881 # Create remote directory + 882 remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep) + 883 self.mkdir( + 884 path=ntpath.normpath(remote_dir_path + ntpath.sep) + 885 ) + 886 + 887 for local_file_path in local_files[local_dir_path]: + 888 try: + 889 f = LocalFileIO( + 890 mode="rb", + 891 path=local_dir_path + os.path.sep + local_file_path, + 892 debug=self.config.debug + 893 ) + 894 self.smbClient.putFile( + 895 shareName=self.smb_share, + 896 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), + 897 callback=f.read + 898 ) + 899 f.close() 900 - 901 Args: - 902 path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None. - 903 """ - 904 try: - 905 self.smbClient.deleteDirectory( - 906 shareName=self.smb_share, - 907 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), - 908 ) - 909 except Exception as err: - 910 print("[!] Failed to remove directory '%s': %s" % (path, err)) - 911 if self.config.debug: - 912 traceback.print_exc() - 913 - 914 def rm(self, path=None): - 915 """ - 916 Removes a file from the SMB share at the specified path. - 917 - 918 This method attempts to delete a file located at the given path on the SMB share. If the operation fails, - 919 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints - 920 the stack trace of the exception. - 921 - 922 Args: - 923 path (str, optional): The path of the file to be removed on the SMB share. Defaults to None. - 924 """ - 925 - 926 # Parse path - 927 path = path.replace('/', ntpath.sep) - 928 if ntpath.sep in path: - 929 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path)) - 930 else: - 931 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep) - 932 # Parse filename - 933 filename = ntpath.basename(path) - 934 - 935 # Search for the file - 936 matches = self.smbClient.listPath( - 937 shareName=self.smb_share, - 938 path=tmp_search_path + ntpath.sep + '*' - 939 ) + 901 except (BrokenPipeError, KeyboardInterrupt) as err: + 902 self.logger.error("Interrupted.") + 903 self.close_smb_session() + 904 self.init_smb_session() + 905 + 906 except (Exception, PermissionError) as err: + 907 f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err)) + 908 f.close(remove=False) + 909 if self.config.debug: + 910 traceback.print_exc() + 911 else: + 912 self.logger.error("The specified localpath is a file. Use 'put <file>' instead.") + 913 else: + 914 self.logger.error("The specified localpath does not exist.") + 915 + 916 def read_file(self, path=None): + 917 """ + 918 Reads a file from the SMB share. + 919 + 920 This method attempts to read the contents of a file specified by the `path` parameter from the SMB share. + 921 It constructs the full path to the file, checks if the path is a valid file, and then reads the file content + 922 into a byte stream which is returned to the caller. + 923 + 924 Args: + 925 path (str, optional): The path of the file to be read from the SMB share. Defaults to None. + 926 + 927 Returns: + 928 bytes: The content of the file as a byte stream, or None if the file does not exist or an error occurs. + 929 """ + 930 + 931 if self.path_isfile(pathFromRoot=path): + 932 path = path.replace('/', ntpath.sep) + 933 if path.startswith(ntpath.sep): + 934 # Absolute path + 935 tmp_file_path = ntpath.normpath(path) + 936 else: + 937 # Relative path + 938 tmp_file_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path) + 939 tmp_file_path = tmp_file_path.lstrip(ntpath.sep) 940 - 941 # Filter the entries - 942 matching_entries = [] - 943 for entry in matches: - 944 if entry.is_directory(): - 945 # Skip directories - 946 continue - 947 if entry.get_longname() == filename: - 948 matching_entries.append(entry) - 949 elif '*' in filename: - 950 regexp = filename.replace('.', '\\.').replace('*', '.*') - 951 if re.match(regexp, entry.get_longname()): - 952 matching_entries.append(entry) - 953 - 954 matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname()) - 955 - 956 for entry in matching_entries: - 957 try: - 958 self.smbClient.deleteFile( - 959 shareName=self.smb_share, - 960 pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), - 961 ) - 962 except Exception as err: - 963 print("[!] Failed to remove file '%s': %s" % (path, err)) - 964 if self.config.debug: - 965 traceback.print_exc() - 966 - 967 def tree(self, path=None): - 968 """ - 969 Recursively lists the directory structure of the SMB share starting from the specified path. - 970 - 971 This function prints a visual representation of the directory tree of the remote SMB share. It uses - 972 recursion to navigate through directories and lists all files and subdirectories in each directory. - 973 The output is color-coded and formatted to enhance readability, with directories highlighted in cyan. + 941 fh = io.BytesIO() + 942 try: + 943 # opening the files in streams instead of mounting shares allows + 944 # for running the script from unprivileged containers + 945 self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write) + 946 except impacket.smbconnection.SessionError as e: + 947 return None + 948 rawdata = fh.getvalue() + 949 fh.close() + 950 return rawdata + 951 else: + 952 return None + 953 + 954 def rmdir(self, path=None): + 955 """ + 956 Removes a directory from the SMB share at the specified path. + 957 + 958 This method attempts to delete a directory located at the given path on the SMB share. If the operation fails, + 959 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints + 960 the stack trace of the exception. + 961 + 962 Args: + 963 path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None. + 964 """ + 965 try: + 966 self.smbClient.deleteDirectory( + 967 shareName=self.smb_share, + 968 pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), + 969 ) + 970 except Exception as err: + 971 self.logger.error("Failed to remove directory '%s': %s" % (path, err)) + 972 if self.config.debug: + 973 traceback.print_exc() 974 - 975 Args: - 976 path (str, optional): The starting path on the SMB share from which to begin listing the tree. - 977 Defaults to the root of the current share. - 978 """ - 979 - 980 def recurse_action(base_dir="", path=[], prompt=[]): - 981 bars = ["│ ", "├── ", "└── "] + 975 def rm(self, path=None): + 976 """ + 977 Removes a file from the SMB share at the specified path. + 978 + 979 This method attempts to delete a file located at the given path on the SMB share. If the operation fails, + 980 it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints + 981 the stack trace of the exception. 982 - 983 remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path)) - 984 - 985 entries = [] - 986 try: - 987 entries = self.smbClient.listPath( - 988 shareName=self.smb_share, - 989 path=remote_smb_path+'\\*' - 990 ) - 991 except impacket.smbconnection.SessionError as err: - 992 code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1] - 993 errmsg = "Error 0x%08x (%s): %s" % (code, const, text) - 994 if self.config.no_colors: - 995 print("%s%s" % (''.join(prompt+[bars[2]]), errmsg)) - 996 else: - 997 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg)) - 998 return - 999 -1000 entries = [e for e in entries if e.get_longname() not in [".", ".."]] -1001 entries = sorted(entries, key=lambda x:x.get_longname()) -1002 -1003 # -1004 if len(entries) > 1: -1005 index = 0 -1006 for entry in entries: -1007 index += 1 -1008 # This is the first entry -1009 if index == 0: -1010 if entry.is_directory(): -1011 if self.config.no_colors: -1012 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1013 else: -1014 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1015 recurse_action( -1016 base_dir=base_dir, -1017 path=path+[entry.get_longname()], -1018 prompt=prompt+["│ "] -1019 ) -1020 else: -1021 if self.config.no_colors: -1022 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1023 else: -1024 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1025 -1026 # This is the last entry -1027 elif index == len(entries): -1028 if entry.is_directory(): -1029 if self.config.no_colors: -1030 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1031 else: -1032 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1033 recurse_action( -1034 base_dir=base_dir, -1035 path=path+[entry.get_longname()], -1036 prompt=prompt+[" "] -1037 ) -1038 else: -1039 if self.config.no_colors: -1040 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1041 else: -1042 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1043 -1044 # These are entries in the middle -1045 else: -1046 if entry.is_directory(): -1047 if self.config.no_colors: -1048 print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1049 else: -1050 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1051 recurse_action( -1052 base_dir=base_dir, -1053 path=path+[entry.get_longname()], -1054 prompt=prompt+["│ "] -1055 ) -1056 else: -1057 if self.config.no_colors: -1058 print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1059 else: -1060 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) -1061 -1062 # -1063 elif len(entries) == 1: -1064 entry = entries[0] -1065 if entry.is_directory(): -1066 if self.config.no_colors: -1067 print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1068 else: -1069 print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1070 recurse_action( -1071 base_dir=base_dir, -1072 path=path+[entry.get_longname()], -1073 prompt=prompt+[" "] -1074 ) -1075 else: -1076 if self.config.no_colors: -1077 print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1078 else: -1079 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) -1080 -1081 # Entrypoint -1082 try: -1083 if self.config.no_colors: -1084 print("%s\\" % path) -1085 else: -1086 print("\x1b[1;96m%s\x1b[0m\\" % path) -1087 recurse_action( -1088 base_dir=self.smb_cwd, -1089 path=[path], -1090 prompt=[""] -1091 ) -1092 except (BrokenPipeError, KeyboardInterrupt) as e: -1093 print("[!] Interrupted.") -1094 self.close_smb_session() -1095 self.init_smb_session() -1096 -1097 def umount(self, local_mount_point): -1098 """ -1099 Unmounts the specified local mount point of the remote share. -1100 -1101 This method unmounts the specified local mount point of the remote share based on the platform. -1102 It supports Windows, Linux, and macOS platforms for unmounting. -1103 -1104 Parameters: -1105 local_mount_point (str): The local mount point to unmount. -1106 -1107 Raises: -1108 None -1109 """ -1110 -1111 if os.path.exists(local_mount_point): -1112 if sys.platform.startswith('win'): -1113 command = f"net use {local_mount_point} /delete" -1114 -1115 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'): -1116 command = f"umount {local_mount_point}" -1117 -1118 else: -1119 command = None -1120 print("[!] Unsupported platform for unmounting SMB share.") -1121 -1122 if command is not None: -1123 if self.config.debug: -1124 print("[debug] Executing: %s" % command) -1125 os.system(command) -1126 else: -1127 print("[!] Cannot unmount a non existing path.") -1128 -1129 # Other functions -1130 -1131 def test_rights(self, sharename): -1132 """ -1133 Tests the read and write access rights of the current SMB session. -1134 -1135 This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories. -1136 -1137 Returns: -1138 dict: A dictionary containing the read and write access rights status. -1139 - "readable" (bool): Indicates if the session has read access rights. -1140 - "writable" (bool): Indicates if the session has write access rights. -1141 """ -1142 -1143 # Restore the current share -1144 current_share = self.smb_share -1145 self.set_share(shareName=sharename) -1146 -1147 access_rights = {"readable": False, "writable": False} -1148 try: -1149 self.smbClient.listPath(self.smb_share, '*', password=None) -1150 access_rights["readable"] = True -1151 except impacket.smbconnection.SessionError as e: -1152 access_rights["readable"] = False -1153 -1154 try: -1155 temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)])) -1156 self.smbClient.createDirectory(self.smb_share, temp_dir) -1157 self.smbClient.deleteDirectory(self.smb_share, temp_dir) -1158 access_rights["writable"] = True -1159 except impacket.smbconnection.SessionError as e: -1160 access_rights["writable"] = False + 983 Args: + 984 path (str, optional): The path of the file to be removed on the SMB share. Defaults to None. + 985 """ + 986 + 987 # Parse path + 988 path = path.replace('/', ntpath.sep) + 989 if ntpath.sep in path: + 990 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path)) + 991 else: + 992 tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep) + 993 # Parse filename + 994 filename = ntpath.basename(path) + 995 + 996 # Search for the file + 997 matches = self.smbClient.listPath( + 998 shareName=self.smb_share, + 999 path=tmp_search_path + ntpath.sep + '*' +1000 ) +1001 +1002 # Filter the entries +1003 matching_entries = [] +1004 for entry in matches: +1005 if entry.is_directory(): +1006 # Skip directories +1007 continue +1008 if entry.get_longname() == filename: +1009 matching_entries.append(entry) +1010 elif '*' in filename: +1011 regexp = filename.replace('.', '\\.').replace('*', '.*') +1012 if re.match(regexp, entry.get_longname()): +1013 matching_entries.append(entry) +1014 +1015 matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname()) +1016 +1017 for entry in matching_entries: +1018 try: +1019 self.smbClient.deleteFile( +1020 shareName=self.smb_share, +1021 pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), +1022 ) +1023 except Exception as err: +1024 self.logger.error("Failed to remove file '%s': %s" % (path, err)) +1025 if self.config.debug: +1026 traceback.print_exc() +1027 +1028 def tree(self, path=None): +1029 """ +1030 Recursively lists the directory structure of the SMB share starting from the specified path. +1031 +1032 This function prints a visual representation of the directory tree of the remote SMB share. It uses +1033 recursion to navigate through directories and lists all files and subdirectories in each directory. +1034 The output is color-coded and formatted to enhance readability, with directories highlighted in cyan. +1035 +1036 Args: +1037 path (str, optional): The starting path on the SMB share from which to begin listing the tree. +1038 Defaults to the root of the current share. +1039 """ +1040 +1041 def recurse_action(base_dir="", path=[], prompt=[]): +1042 bars = ["│ ", "├── ", "└── "] +1043 +1044 remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path)) +1045 +1046 entries = [] +1047 try: +1048 entries = self.smbClient.listPath( +1049 shareName=self.smb_share, +1050 path=remote_smb_path+'\\*' +1051 ) +1052 except impacket.smbconnection.SessionError as err: +1053 code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1] +1054 errmsg = "Error 0x%08x (%s): %s" % (code, const, text) +1055 if self.config.no_colors: +1056 self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), errmsg)) +1057 else: +1058 self.logger.print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg)) +1059 return +1060 +1061 entries = [e for e in entries if e.get_longname() not in [".", ".."]] +1062 entries = sorted(entries, key=lambda x:x.get_longname()) +1063 +1064 # +1065 if len(entries) > 1: +1066 index = 0 +1067 for entry in entries: +1068 index += 1 +1069 # This is the first entry +1070 if index == 0: +1071 if entry.is_directory(): +1072 if self.config.no_colors: +1073 self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1074 else: +1075 self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1076 recurse_action( +1077 base_dir=base_dir, +1078 path=path+[entry.get_longname()], +1079 prompt=prompt+["│ "] +1080 ) +1081 else: +1082 if self.config.no_colors: +1083 self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1084 else: +1085 self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1086 +1087 # This is the last entry +1088 elif index == len(entries): +1089 if entry.is_directory(): +1090 if self.config.no_colors: +1091 self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1092 else: +1093 self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1094 recurse_action( +1095 base_dir=base_dir, +1096 path=path+[entry.get_longname()], +1097 prompt=prompt+[" "] +1098 ) +1099 else: +1100 if self.config.no_colors: +1101 self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1102 else: +1103 self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1104 +1105 # These are entries in the middle +1106 else: +1107 if entry.is_directory(): +1108 if self.config.no_colors: +1109 self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1110 else: +1111 self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1112 recurse_action( +1113 base_dir=base_dir, +1114 path=path+[entry.get_longname()], +1115 prompt=prompt+["│ "] +1116 ) +1117 else: +1118 if self.config.no_colors: +1119 self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1120 else: +1121 self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname())) +1122 +1123 # +1124 elif len(entries) == 1: +1125 entry = entries[0] +1126 if entry.is_directory(): +1127 if self.config.no_colors: +1128 self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1129 else: +1130 self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1131 recurse_action( +1132 base_dir=base_dir, +1133 path=path+[entry.get_longname()], +1134 prompt=prompt+[" "] +1135 ) +1136 else: +1137 if self.config.no_colors: +1138 self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1139 else: +1140 self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname())) +1141 +1142 # Entrypoint +1143 try: +1144 if self.config.no_colors: +1145 self.logger.print("%s\\" % path) +1146 else: +1147 self.logger.print("\x1b[1;96m%s\x1b[0m\\" % path) +1148 recurse_action( +1149 base_dir=self.smb_cwd, +1150 path=[path], +1151 prompt=[""] +1152 ) +1153 except (BrokenPipeError, KeyboardInterrupt) as e: +1154 self.logger.error("Interrupted.") +1155 self.close_smb_session() +1156 self.init_smb_session() +1157 +1158 def umount(self, local_mount_point): +1159 """ +1160 Unmounts the specified local mount point of the remote share. 1161 -1162 # Restore the current share -1163 self.set_share(shareName=current_share) +1162 This method unmounts the specified local mount point of the remote share based on the platform. +1163 It supports Windows, Linux, and macOS platforms for unmounting. 1164 -1165 return access_rights -1166 -1167 # Setter / Getter -1168 -1169 def set_share(self, shareName): -1170 """ -1171 Sets the current SMB share to the specified share name. -1172 -1173 This method updates the SMB session to use the specified share name. It checks if the share name is valid -1174 and updates the smb_share attribute of the SMBSession instance. +1165 Parameters: +1166 local_mount_point (str): The local mount point to unmount. +1167 +1168 Raises: +1169 None +1170 """ +1171 +1172 if os.path.exists(local_mount_point): +1173 if sys.platform.startswith('win'): +1174 command = f"net use {local_mount_point} /delete" 1175 -1176 Parameters: -1177 shareName (str): The name of the share to set as the current SMB share. +1176 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'): +1177 command = f"umount {local_mount_point}" 1178 -1179 Raises: -1180 ValueError: If the shareName is None or an empty string. -1181 """ -1182 -1183 if shareName is not None: -1184 self.list_shares() -1185 if shareName.lower() in self.available_shares.keys(): -1186 # Doing this in order to keep the case of the share adevertised by the remote machine -1187 self.smb_share = self.available_shares[shareName.lower()]["name"] -1188 self.smb_cwd = "" -1189 # Connects the tree -1190 self.smb_tree_id = self.smbClient.connectTree(self.smb_share) -1191 else: -1192 print("[!] Could not set share '%s', it does not exist remotely." % shareName) -1193 else: -1194 self.smb_share = None -1195 -1196 def set_cwd(self, path=None): -1197 """ -1198 Sets the current working directory on the SMB share to the specified path. -1199 -1200 This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. -1201 If the specified path is not a directory, the cwd remains unchanged. +1179 else: +1180 command = None +1181 self.logger.error("Unsupported platform for unmounting SMB share.") +1182 +1183 if command is not None: +1184 self.logger.debug("Executing: %s" % command) +1185 os.system(command) +1186 else: +1187 self.logger.error("Cannot unmount a non existing path.") +1188 +1189 # Other functions +1190 +1191 def test_rights(self, sharename): +1192 """ +1193 Tests the read and write access rights of the current SMB session. +1194 +1195 This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories. +1196 +1197 Returns: +1198 dict: A dictionary containing the read and write access rights status. +1199 - "readable" (bool): Indicates if the session has read access rights. +1200 - "writable" (bool): Indicates if the session has write access rights. +1201 """ 1202 -1203 Parameters: -1204 path (str): The path to set as the current working directory. -1205 -1206 Raises: -1207 ValueError: If the specified path is not a directory. -1208 """ -1209 -1210 if path is not None: -1211 # Set path separators to ntpath sep -1212 if '/' in path: -1213 path = path.replace('/', ntpath.sep) -1214 -1215 if path.startswith(ntpath.sep): -1216 # Absolute path -1217 path = path + ntpath.sep -1218 else: -1219 # Relative path to the CWD -1220 if len(self.smb_cwd) == 0: -1221 path = path + ntpath.sep -1222 else: -1223 path = self.smb_cwd + ntpath.sep + path -1224 -1225 # Path normalization -1226 path = ntpath.normpath(path) -1227 path = re.sub(r'\\+', r'\\', path) +1203 # Restore the current share +1204 current_share = self.smb_share +1205 self.set_share(shareName=sharename) +1206 +1207 access_rights = {"readable": False, "writable": False} +1208 try: +1209 self.smbClient.listPath(self.smb_share, '*', password=None) +1210 access_rights["readable"] = True +1211 except impacket.smbconnection.SessionError as e: +1212 access_rights["readable"] = False +1213 +1214 try: +1215 temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)])) +1216 self.smbClient.createDirectory(self.smb_share, temp_dir) +1217 self.smbClient.deleteDirectory(self.smb_share, temp_dir) +1218 access_rights["writable"] = True +1219 except impacket.smbconnection.SessionError as e: +1220 access_rights["writable"] = False +1221 +1222 # Restore the current share +1223 self.set_share(shareName=current_share) +1224 +1225 return access_rights +1226 +1227 # Setter / Getter 1228 -1229 if path in ["", ".", ".."]: -1230 self.smb_cwd = "" -1231 else: -1232 if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)): -1233 # Path exists on the remote -1234 self.smb_cwd = ntpath.normpath(path) -1235 else: -1236 # Path does not exists or is not a directory on the remote -1237 print("[!] Remote directory '%s' does not exist." % path) +1229 def set_share(self, shareName): +1230 """ +1231 Sets the current SMB share to the specified share name. +1232 +1233 This method updates the SMB session to use the specified share name. It checks if the share name is valid +1234 and updates the smb_share attribute of the SMBSession instance. +1235 +1236 Parameters: +1237 shareName (str): The name of the share to set as the current SMB share. +1238 +1239 Raises: +1240 ValueError: If the shareName is None or an empty string. +1241 """ +1242 +1243 if shareName is not None: +1244 self.list_shares() +1245 if shareName.lower() in self.available_shares.keys(): +1246 # Doing this in order to keep the case of the share adevertised by the remote machine +1247 self.smb_share = self.available_shares[shareName.lower()]["name"] +1248 self.smb_cwd = "" +1249 # Connects the tree +1250 self.smb_tree_id = self.smbClient.connectTree(self.smb_share) +1251 else: +1252 self.logger.error("Could not set share '%s', it does not exist remotely." % shareName) +1253 else: +1254 self.smb_share = None +1255 +1256 def set_cwd(self, path=None): +1257 """ +1258 Sets the current working directory on the SMB share to the specified path. +1259 +1260 This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory. +1261 If the specified path is not a directory, the cwd remains unchanged. +1262 +1263 Parameters: +1264 path (str): The path to set as the current working directory. +1265 +1266 Raises: +1267 ValueError: If the specified path is not a directory. +1268 """ +1269 +1270 if path is not None: +1271 # Set path separators to ntpath sep +1272 if '/' in path: +1273 path = path.replace('/', ntpath.sep) +1274 +1275 if path.startswith(ntpath.sep): +1276 # Absolute path +1277 path = path + ntpath.sep +1278 else: +1279 # Relative path to the CWD +1280 if len(self.smb_cwd) == 0: +1281 path = path + ntpath.sep +1282 else: +1283 path = self.smb_cwd + ntpath.sep + path +1284 +1285 # Path normalization +1286 path = ntpath.normpath(path) +1287 path = re.sub(r'\\+', r'\\', path) +1288 +1289 if path in ["", ".", ".."]: +1290 self.smb_cwd = "" +1291 else: +1292 if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)): +1293 # Path exists on the remote +1294 self.smb_cwd = ntpath.normpath(path) +1295 else: +1296 # Path does not exists or is not a directory on the remote +1297 self.logger.error("Remote directory '%s' does not exist." % path)

    @@ -1434,1248 +1482,1317 @@

      21class SMBSession(object):
       22    """
    -  23    Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections.
    -  24    It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.
    -  25
    -  26    Attributes:
    -  27        address (str): The IP address or hostname of the SMB server.
    -  28        domain (str): The domain name for SMB server authentication.
    -  29        username (str): The username for SMB server authentication.
    -  30        password (str): The password for SMB server authentication.
    -  31        lmhash (str): The LM hash of the user's password, if available.
    -  32        nthash (str): The NT hash of the user's password, if available.
    -  33        use_kerberos (bool): A flag to determine whether to use Kerberos for authentication.
    -  34        kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication.
    -  35        debug (bool): A flag to enable debug output.
    -  36        smbClient (object): The SMB client object used for the connection.
    -  37        connected (bool): A flag to check the status of the connection.
    +  23    Represents an SMB session for interacting with an SMB server.
    +  24
    +  25    This class provides methods to manage and interact with an SMB server, including
    +  26    connecting to the server, listing shares, uploading and downloading files, and
    +  27    managing directories and files on the server. It handles session initialization,
    +  28    authentication, and cleanup.
    +  29
    +  30    Attributes:
    +  31        host (str): The hostname or IP address of the SMB server.
    +  32        port (int): The port number on which the SMB server is listening.
    +  33        credentials (dict): Authentication credentials for the SMB server.
    +  34        config (dict, optional): Configuration options for the SMB session.
    +  35        smbClient (impacket.smbconnection.SMBConnection): The SMB connection instance.
    +  36        connected (bool): Connection status to the SMB server.
    +  37        available_shares (dict): A dictionary of available SMB shares.
       38        smb_share (str): The current SMB share in use.
    -  39        smb_path (str): The current path within the SMB share.
    -  40
    -  41    Methods:
    -  42        __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False):
    -  43            Initializes the SMBSession with the specified parameters.
    -  44        init_smb_session():
    -  45            Initializes the SMB session by connecting to the server and authenticating using the specified method.
    -  46    """
    -  47
    -  48    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
    -  49        super(SMBSession, self).__init__()
    -  50        # Objects
    -  51        self.config = config
    -  52
    -  53        # Target server
    -  54        self.address = address
    -  55
    -  56        # Credentials
    -  57        self.domain = domain
    -  58        self.username = username
    -  59        self.password = password 
    -  60        self.lmhash = lmhash
    -  61        self.nthash = nthash
    -  62        self.use_kerberos = use_kerberos
    -  63        self.kdcHost = kdcHost
    -  64
    -  65        self.smbClient = None
    -  66        self.connected = False
    +  39        smb_cwd (str): The current working directory on the SMB share.
    +  40        smb_tree_id (int): The tree ID of the connected SMB share.
    +  41
    +  42    Methods:
    +  43        close_smb_session(): Closes the current SMB session.
    +  44        init_smb_session(): Initializes the SMB session with the server.
    +  45        list_shares(): Lists all shares available on the SMB server.
    +  46        set_share(shareName): Sets the current SMB share.
    +  47        set_cwd(path): Sets the current working directory on the SMB share.
    +  48        put_file(localpath): Uploads a file to the current SMB share.
    +  49        get_file(remotepath, localpath): Downloads a file from the SMB share.
    +  50        mkdir(path): Creates a directory on the SMB share.
    +  51        rmdir(path): Removes a directory from the SMB share.
    +  52        rm(path): Removes a file from the SMB share.
    +  53        read_file(path): Reads a file from the SMB share.
    +  54        test_rights(sharename): Tests read and write access rights on a share.
    +  55    """
    +  56
    +  57    def __init__(self, host, port, credentials, config=None, logger=None):
    +  58        super(SMBSession, self).__init__()
    +  59        # Objects
    +  60        self.config = config
    +  61        self.logger = logger
    +  62
    +  63        # Target server
    +  64        self.host = host
    +  65        # Target port (by default on 445)
    +  66        self.port = port
       67
    -  68        self.available_shares = {}
    -  69        self.smb_share = None
    -  70        self.smb_cwd = ""
    -  71        self.smb_tree_id = None
    -  72
    -  73        self.list_shares()
    -  74
    -  75    # Connect and disconnect SMB session
    -  76
    -  77    def init_smb_session(self):
    -  78        """
    -  79        Initializes and establishes a session with the SMB server.
    +  68        # Credentials
    +  69        self.credentials = credentials
    +  70
    +  71        self.smbClient = None
    +  72        self.connected = False
    +  73
    +  74        self.available_shares = {}
    +  75        self.smb_share = None
    +  76        self.smb_cwd = ""
    +  77        self.smb_tree_id = None
    +  78
    +  79        self.list_shares()
       80
    -  81        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
    -  82        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
    -  83
    -  84        The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True.
    -  85
    -  86        Returns:
    -  87            bool: True if the connection and authentication are successful, False otherwise.
    -  88        """
    +  81    # Connect and disconnect SMB session
    +  82
    +  83    def close_smb_session(self):
    +  84        """
    +  85        Closes the current SMB session by disconnecting the SMB client.
    +  86
    +  87        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
    +  88        and if so, it closes the connection and resets the connection status.
       89
    -  90        self.connected = False
    -  91
    -  92        if self.config.debug:
    -  93            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
    -  94        try:
    -  95            self.smbClient = impacket.smbconnection.SMBConnection(
    -  96                remoteName=self.address,
    -  97                remoteHost=self.address,
    -  98                sess_port=int(445)
    -  99            )
    - 100        except OSError as err:
    - 101            print("[!] %s" % err)
    - 102            self.smbClient = None
    +  90        Raises:
    +  91            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
    +  92        """
    +  93
    +  94        if self.smbClient is not None:
    +  95            if self.connected:
    +  96                self.smbClient.close()
    +  97                self.connected = False
    +  98                self.logger.debug("[+] SMB connection closed successfully.")
    +  99            else:
    + 100                self.logger.debug("[!] No active SMB connection to close.")
    + 101        else:
    + 102            raise Exception("SMB client is not initialized.")
      103
    - 104        if self.smbClient is not None:
    - 105            if self.use_kerberos:
    - 106                if self.config.debug:
    - 107                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
    - 108                try:
    - 109                    self.connected = self.smbClient.kerberosLogin(
    - 110                        user=self.username,
    - 111                        password=self.password,
    - 112                        domain=self.domain,
    - 113                        lmhash=self.lmhash,
    - 114                        nthash=self.nthash,
    - 115                        aesKey=self.aesKey,
    - 116                        kdcHost=self.kdcHost
    - 117                    )
    - 118                except impacket.smbconnection.SessionError as err:
    - 119                    if self.config.debug:
    - 120                        traceback.print_exc()
    - 121                    print("[!] Could not login: %s" % err)
    - 122                    self.connected = False
    - 123
    - 124            else:
    - 125                if self.config.debug:
    - 126                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
    - 127                try:
    - 128                    self.connected = self.smbClient.login(
    - 129                        user=self.username,
    - 130                        password=self.password,
    - 131                        domain=self.domain,
    - 132                        lmhash=self.lmhash,
    - 133                        nthash=self.nthash
    - 134                    )
    - 135                except impacket.smbconnection.SessionError as err:
    - 136                    if self.config.debug:
    - 137                        traceback.print_exc()
    - 138                    print("[!] Could not login: %s" % err)
    - 139                    self.connected = False
    - 140
    - 141            if self.connected:
    - 142                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
    - 143            else:
    - 144                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
    - 145
    - 146        return self.connected
    - 147
    - 148    def close_smb_session(self):
    - 149        """
    - 150        Closes the current SMB session by disconnecting the SMB client.
    - 151
    - 152        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
    - 153        and if so, it closes the connection and resets the connection status.
    + 104    def init_smb_session(self):
    + 105        """
    + 106        Initializes and establishes a session with the SMB server.
    + 107
    + 108        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
    + 109        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
    + 110
    + 111        The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True.
    + 112
    + 113        Returns:
    + 114            bool: True if the connection and authentication are successful, False otherwise.
    + 115        """
    + 116
    + 117        self.connected = False
    + 118
    + 119        self.logger.debug("[>] Connecting to remote SMB server '%s' ... " % self.host)
    + 120        
    + 121        try:
    + 122            if is_port_open(self.host, self.port):
    + 123                self.smbClient = impacket.smbconnection.SMBConnection(
    + 124                    remoteName=self.host,
    + 125                    remoteHost=self.host,
    + 126                    sess_port=int(self.port)
    + 127                )
    + 128            else:
    + 129                self.connected = False
    + 130        except OSError as err:
    + 131            if self.config.debug:
    + 132                traceback.print_exc()
    + 133            self.logger.error(err)
    + 134            self.smbClient = None
    + 135
    + 136        if self.smbClient is not None:
    + 137            if self.credentials.use_kerberos:
    + 138                self.logger.debug("[>] Authenticating as '%s\\%s' with kerberos ... " % (self.credentials.domain, self.credentials.username))
    + 139                try:
    + 140                    self.connected = self.smbClient.kerberosLogin(
    + 141                        user=self.credentials.username,
    + 142                        password=self.credentials.password,
    + 143                        domain=self.credentials.domain,
    + 144                        lmhash=self.credentials.lm_hex,
    + 145                        nthash=self.credentials.nt_hex,
    + 146                        aesKey=self.credentials.aesKey,
    + 147                        kdcHost=self.credentials.kdcHost
    + 148                    )
    + 149                except impacket.smbconnection.SessionError as err:
    + 150                    if self.config.debug:
    + 151                        traceback.print_exc()
    + 152                    self.logger.error("Could not login: %s" % err)
    + 153                    self.connected = False
      154
    - 155        Raises:
    - 156            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
    - 157        """
    - 158
    - 159        if self.smbClient is not None:
    - 160            if self.connected:
    - 161                self.smbClient.close()
    - 162                self.connected = False
    - 163                if self.config.debug:
    - 164                    print("[+] SMB connection closed successfully.")
    - 165            else:
    - 166                if self.config.debug:
    - 167                    print("[!] No active SMB connection to close.")
    - 168        else:
    - 169            raise Exception("SMB client is not initialized.")
    - 170
    - 171    # Operations
    - 172
    - 173    def read_file(self, path=None):
    - 174        if self.path_isfile(path=path):
    - 175            tmp_file_path = self.smb_cwd + ntpath.sep + path
    - 176            matches = self.smbClient.listPath(
    - 177                shareName=self.smb_share, 
    - 178                path=tmp_file_path
    - 179            )
    - 180
    - 181            fh = io.BytesIO()
    - 182            try:
    - 183                # opening the files in streams instead of mounting shares allows 
    - 184                # for running the script from unprivileged containers
    - 185                self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write)
    - 186            except impacket.smbconnection.SessionError as e:
    - 187                return None
    - 188            rawdata = fh.getvalue()
    - 189            fh.close()
    - 190            return rawdata
    - 191        else:
    - 192            print("[!] Remote path '%s' is not a file." % path)
    - 193
    - 194    def find(self, paths=[], callback=None):
    - 195        def recurse_action(paths=[], depth=0, callback=None):
    - 196            if callback is None:
    - 197                return []
    - 198            
    - 199            next_directories_to_explore = []
    - 200
    - 201            for path in paths:
    - 202                remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    - 203                entries = []
    - 204                
    - 205                try:
    - 206                    entries = self.smbClient.listPath(
    - 207                        shareName=self.smb_share, 
    - 208                        path=(remote_smb_path + ntpath.sep + '*')
    - 209                    )
    - 210                except impacket.smbconnection.SessionError as err:
    - 211                    continue 
    - 212                # Remove dot names
    - 213                entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    - 214                # Sort the entries ignoring case
    - 215                entries = sorted(entries, key=lambda x:x.get_longname().lower())
    - 216                
    - 217                for entry in entries:
    - 218                    if entry.is_directory():
    - 219                        callback(entry, path + ntpath.sep + entry.get_longname() + ntpath.sep, depth)
    - 220                    else:
    - 221                        callback(entry, path + ntpath.sep + entry.get_longname(), depth)
    - 222
    - 223                # Next directories to explore
    - 224                for entry in entries:
    - 225                    if entry.is_directory():
    - 226                        next_directories_to_explore.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
    - 227            
    - 228            return next_directories_to_explore
    - 229        # 
    - 230        if callback is not None:
    - 231            depth = 0
    - 232            while len(paths) != 0:
    - 233                paths = recurse_action(
    - 234                    paths=paths,
    - 235                    depth=depth,
    - 236                    callback=callback
    - 237                )
    - 238                depth = depth + 1
    - 239        else:
    - 240            print("[!] SMBSession.find(), callback function cannot be None.")
    - 241
    - 242    def get_file(self, path=None, keepRemotePath=False):
    - 243        """
    - 244        Retrieves a file from the specified path on the SMB share.
    - 245
    - 246        This method attempts to retrieve a file from the given path within the currently connected SMB share.
    - 247        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
    - 248        file object and writing the contents of the remote file to it using the SMB client's getFile method.
    - 249
    - 250        Parameters:
    - 251            path (str): The path of the file to retrieve. If None, uses the current smb_path.
    - 252
    - 253        Returns:
    - 254            None
    - 255        """
    - 256
    - 257        # Parse path
    - 258        path = path.replace('/', ntpath.sep)
    - 259        if ntpath.sep in path:
    - 260            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    + 155            else:
    + 156                self.logger.debug("[>] Authenticating as '%s\\%s' with NTLM ... " % (self.credentials.domain, self.credentials.username))
    + 157                
    + 158                try:
    + 159                    self.connected = self.smbClient.login(
    + 160                        user=self.credentials.username,
    + 161                        password=self.credentials.password,
    + 162                        domain=self.credentials.domain,
    + 163                        lmhash=self.credentials.lm_hex,
    + 164                        nthash=self.credentials.nt_hex
    + 165                    )
    + 166                except impacket.smbconnection.SessionError as err:
    + 167                    if self.config.debug:
    + 168                        traceback.print_exc()
    + 169                    self.logger.error("Could not login: %s" % err)
    + 170                    self.connected = False
    + 171
    + 172            if self.connected:
    + 173                self.logger.print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username))
    + 174            else:
    + 175                self.logger.error("Failed to authenticate to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username))
    + 176
    + 177        return self.connected
    + 178
    + 179    def ping_smb_session(self):
    + 180        """
    + 181        Tests the connectivity to the SMB server by sending an echo command.
    + 182
    + 183        This method attempts to send an echo command to the SMB server to check if the session is still active.
    + 184        It updates the `connected` attribute of the class based on the success or failure of the echo command.
    + 185
    + 186        Returns:
    + 187            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
    + 188        """
    + 189
    + 190        if not is_port_open(self.host, self.port):
    + 191            self.connected = False
    + 192        else:
    + 193            try:
    + 194                self.smbClient.getSMBServer().echo()
    + 195            except Exception as e:
    + 196                self.connected = False
    + 197
    + 198        return self.connected
    + 199
    + 200    # Operations
    + 201
    + 202    def find(self, paths=[], callback=None):
    + 203        """
    + 204        Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry.
    + 205
    + 206        This method traverses the specified paths on the SMB share, recursively exploring directories and invoking the callback
    + 207        function on each file or directory found. The callback function is called with three arguments: the entry object, the
    + 208        full path of the entry, and the current depth of recursion.
    + 209
    + 210        Args:
    + 211            paths (list, optional): A list of paths to start the search from. Defaults to an empty list.
    + 212            callback (function, optional): A function to be called on each entry found. The function should accept three arguments:
    + 213                                           the entry object, the full path of the entry, and the current depth of recursion. Defaults to None.
    + 214
    + 215        Note:
    + 216            If the callback function is None, the method will print an error message and return without performing any action.
    + 217        """
    + 218
    + 219        def recurse_action(paths=[], depth=0, callback=None):
    + 220            if callback is None:
    + 221                return []
    + 222            
    + 223            next_directories_to_explore = []
    + 224
    + 225            for path in paths:
    + 226                remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    + 227                entries = []
    + 228                
    + 229                try:
    + 230                    entries = self.smbClient.listPath(
    + 231                        shareName=self.smb_share, 
    + 232                        path=(remote_smb_path + ntpath.sep + '*')
    + 233                    )
    + 234                except impacket.smbconnection.SessionError as err:
    + 235                    continue 
    + 236                # Remove dot names
    + 237                entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    + 238                # Sort the entries ignoring case
    + 239                entries = sorted(entries, key=lambda x:x.get_longname().lower())
    + 240                
    + 241                for entry in entries:
    + 242                    if entry.is_directory():
    + 243                        fullpath = path + ntpath.sep + entry.get_longname() + ntpath.sep
    + 244                        next_directories_to_explore.append(fullpath)
    + 245                    else:
    + 246                        fullpath = path + ntpath.sep + entry.get_longname()
    + 247                    fullpath = re.sub(r'\\\\+', r'\\', fullpath)
    + 248                    callback(entry, fullpath, depth)
    + 249                     
    + 250            return next_directories_to_explore
    + 251        # 
    + 252        if callback is not None:
    + 253            depth = 0
    + 254            while len(paths) != 0:
    + 255                paths = recurse_action(
    + 256                    paths=paths,
    + 257                    depth=depth,
    + 258                    callback=callback
    + 259                )
    + 260                depth = depth + 1
      261        else:
    - 262            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    - 263        # Parse filename
    - 264        filename = ntpath.basename(path)
    - 265
    - 266        # Search for the file
    - 267        matches = self.smbClient.listPath(
    - 268            shareName=self.smb_share, 
    - 269            path=tmp_search_path + ntpath.sep + '*'
    - 270        )   
    + 262            self.logger.error("SMBSession.find(), callback function cannot be None.")
    + 263
    + 264    def get_file(self, path=None, keepRemotePath=False):
    + 265        """
    + 266        Retrieves a file from the specified path on the SMB share.
    + 267
    + 268        This method attempts to retrieve a file from the given path within the currently connected SMB share.
    + 269        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
    + 270        file object and writing the contents of the remote file to it using the SMB client's getFile method.
      271
    - 272        # Filter the entries
    - 273        matching_entries = []
    - 274        for entry in matches:
    - 275            if entry.is_directory():
    - 276                # Skip directories
    - 277                continue
    - 278            if entry.get_longname() == filename:
    - 279                matching_entries.append(entry)
    - 280            elif '*' in filename:
    - 281                regexp = filename.replace('.', '\\.').replace('*', '.*')
    - 282                if re.match(regexp, entry.get_longname()):
    - 283                    matching_entries.append(entry)
    - 284        
    - 285        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    - 286
    - 287        for entry in matching_entries:
    - 288            if entry.is_directory():
    - 289                if self.config.debug:
    - 290                    print("[debug] [>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname()))
    - 291            else:
    - 292                try:
    - 293                    if ntpath.sep in path:
    - 294                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
    - 295                    else:
    - 296                        outputfile = entry.get_longname()
    - 297                    f = LocalFileIO(
    - 298                        mode="wb", 
    - 299                        path=outputfile,
    - 300                        expected_size=entry.get_filesize(), 
    - 301                        debug=self.config.debug,
    - 302                        keepRemotePath=keepRemotePath
    - 303                    )
    - 304                    self.smbClient.getFile(
    - 305                        shareName=self.smb_share, 
    - 306                        pathName=tmp_search_path + ntpath.sep + entry.get_longname(), 
    - 307                        callback=f.write
    - 308                    )
    - 309                    f.close()
    - 310                except (BrokenPipeError, KeyboardInterrupt) as e:
    - 311                    f.close()
    - 312                    print("\x1b[v\x1b[o\r[!] Interrupted.")
    - 313                    self.close_smb_session()
    - 314                    self.init_smb_session()
    - 315                        
    - 316        return None
    - 317
    - 318    def get_file_recursively(self, path=None):
    - 319        """
    - 320        Recursively retrieves files from a specified path on the SMB share.
    - 321
    - 322        This method navigates through all directories starting from the given path,
    - 323        and downloads all files found. It handles directories recursively, ensuring
    - 324        that all nested files are retrieved. The method skips over directory entries
    - 325        and handles errors gracefully, attempting to continue the operation where possible.
    - 326
    - 327        Parameters:
    - 328            path (str): The initial directory path from which to start the recursive file retrieval.
    - 329                        If None, it starts from the root of the configured SMB share.
    - 330        """
    - 331        
    - 332        def recurse_action(base_dir="", path=[]):
    - 333            if len(base_dir) == 0:
    - 334                remote_smb_path = ntpath.sep.join(path)
    - 335            else:
    - 336                remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path)
    - 337            remote_smb_path = ntpath.normpath(remote_smb_path)
    + 272        Parameters:
    + 273            path (str): The path of the file to retrieve. If None, uses the current smb_path.
    + 274
    + 275        Returns:
    + 276            None
    + 277        """
    + 278
    + 279        # Parse path
    + 280        path = path.replace('/', ntpath.sep)
    + 281        if ntpath.sep in path:
    + 282            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    + 283        else:
    + 284            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    + 285        # Parse filename
    + 286        filename = ntpath.basename(path)
    + 287
    + 288        # Search for the file
    + 289        matches = self.smbClient.listPath(
    + 290            shareName=self.smb_share, 
    + 291            path=tmp_search_path + ntpath.sep + '*'
    + 292        )   
    + 293
    + 294        # Filter the entries
    + 295        matching_entries = []
    + 296        for entry in matches:
    + 297            if entry.is_directory():
    + 298                # Skip directories
    + 299                continue
    + 300            if entry.get_longname() == filename:
    + 301                matching_entries.append(entry)
    + 302            elif '*' in filename:
    + 303                regexp = filename.replace('.', '\\.').replace('*', '.*')
    + 304                if re.match(regexp, entry.get_longname()):
    + 305                    matching_entries.append(entry)
    + 306        
    + 307        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    + 308
    + 309        for entry in matching_entries:
    + 310            if entry.is_directory():
    + 311                self.logger.debug("[>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname()))
    + 312            else:
    + 313                try:
    + 314                    if ntpath.sep in path:
    + 315                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
    + 316                    else:
    + 317                        outputfile = entry.get_longname()
    + 318                    f = LocalFileIO(
    + 319                        mode="wb", 
    + 320                        path=outputfile,
    + 321                        expected_size=entry.get_filesize(), 
    + 322                        debug=self.config.debug,
    + 323                        keepRemotePath=keepRemotePath
    + 324                    )
    + 325                    self.smbClient.getFile(
    + 326                        shareName=self.smb_share, 
    + 327                        pathName=tmp_search_path + ntpath.sep + entry.get_longname(), 
    + 328                        callback=f.write
    + 329                    )
    + 330                    f.close()
    + 331                except (BrokenPipeError, KeyboardInterrupt) as e:
    + 332                    f.close()
    + 333                    print("\x1b[v\x1b[o\r[!] Interrupted.")
    + 334                    self.close_smb_session()
    + 335                    self.init_smb_session()
    + 336                        
    + 337        return None
      338
    - 339            entries = self.smbClient.listPath(
    - 340                shareName=self.smb_share, 
    - 341                path=remote_smb_path + '\\*'
    - 342            )
    - 343            if len(entries) != 0:
    - 344                files = [entry for entry in entries if not entry.is_directory()]
    - 345                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
    - 346
    - 347                # Files
    - 348                if len(files) != 0:
    - 349                    print("[>] Retrieving files of '%s'" % remote_smb_path)
    - 350                for entry_file in files:
    - 351                    if not entry_file.is_directory():
    - 352                        f = LocalFileIO(
    - 353                            mode="wb",
    - 354                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    - 355                            expected_size=entry_file.get_filesize(),
    - 356                            keepRemotePath=True,
    - 357                            debug=self.config.debug
    - 358                        )
    - 359                        try:
    - 360                            self.smbClient.getFile(
    - 361                                shareName=self.smb_share, 
    - 362                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    - 363                                callback=f.write
    - 364                            )
    - 365                            f.close()
    - 366                        except BrokenPipeError as err:
    - 367                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    - 368                            f.close(remove=True)
    - 369                            break
    - 370                        except Exception as err:
    - 371                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    - 372                            f.close(remove=True)
    - 373                
    - 374                # Directories
    - 375                for entry_directory in directories:
    - 376                    if entry_directory.is_directory():
    - 377                        recurse_action(
    - 378                            base_dir=self.smb_cwd, 
    - 379                            path=path+[entry_directory.get_longname()]
    - 380                        )                   
    - 381        # Entrypoint
    - 382        try:
    - 383            recurse_action(
    - 384                base_dir=self.smb_cwd, 
    - 385                path=[path]
    - 386            )
    - 387        except (BrokenPipeError, KeyboardInterrupt) as e:
    - 388            print("\x1b[v\x1b[o\r[!] Interrupted.")
    - 389            self.close_smb_session()
    - 390            self.init_smb_session()
    - 391
    - 392    def get_entry(self, path=None):
    - 393        """
    - 394        Retrieves information about a specific entry located at the provided path on the SMB share.
    - 395
    - 396        This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.
    - 397
    - 398        Args:
    - 399            path (str): The path of the entry to retrieve information about.
    - 400
    - 401        Returns:
    - 402            Entry: An object representing the entry at the specified path, or None if the entry is not found.
    - 403        """
    - 404
    - 405        if self.path_exists(path=path):
    - 406            matches = self.smbClient.listPath(shareName=self.smb_share, path=path)
    - 407
    - 408            if len(matches) == 1:
    - 409                return matches[0]
    - 410            else:
    - 411                return None
    - 412            
    - 413        else:
    - 414            return None 
    - 415
    - 416    def info(self, share=True, server=True):
    - 417        """
    - 418        Displays information about the server and optionally the shares.
    - 419
    - 420        This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share.
    + 339    def get_file_recursively(self, path=None):
    + 340        """
    + 341        Recursively retrieves files from a specified path on the SMB share.
    + 342
    + 343        This method navigates through all directories starting from the given path,
    + 344        and downloads all files found. It handles directories recursively, ensuring
    + 345        that all nested files are retrieved. The method skips over directory entries
    + 346        and handles errors gracefully, attempting to continue the operation where possible.
    + 347
    + 348        Parameters:
    + 349            path (str): The initial directory path from which to start the recursive file retrieval.
    + 350                        If None, it starts from the root of the configured SMB share.
    + 351        """
    + 352        
    + 353        def recurse_action(base_dir="", path=[]):
    + 354            if len(base_dir) == 0:
    + 355                remote_smb_path = ntpath.sep.join(path)
    + 356            else:
    + 357                remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path)
    + 358            remote_smb_path = ntpath.normpath(remote_smb_path)
    + 359
    + 360            entries = self.smbClient.listPath(
    + 361                shareName=self.smb_share, 
    + 362                path=remote_smb_path + '\\*'
    + 363            )
    + 364            if len(entries) != 0:
    + 365                files = [entry for entry in entries if not entry.is_directory()]
    + 366                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
    + 367
    + 368                # Files
    + 369                if len(files) != 0:
    + 370                    self.logger.print("[>] Retrieving files of '%s'" % remote_smb_path)
    + 371                for entry_file in files:
    + 372                    if not entry_file.is_directory():
    + 373                        f = LocalFileIO(
    + 374                            mode="wb",
    + 375                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    + 376                            expected_size=entry_file.get_filesize(),
    + 377                            keepRemotePath=True,
    + 378                            debug=self.config.debug
    + 379                        )
    + 380                        try:
    + 381                            self.smbClient.getFile(
    + 382                                shareName=self.smb_share, 
    + 383                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    + 384                                callback=f.write
    + 385                            )
    + 386                            f.close()
    + 387                        except BrokenPipeError as err:
    + 388                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    + 389                            f.close(remove=True)
    + 390                            break
    + 391                        except Exception as err:
    + 392                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    + 393                            f.close(remove=True)
    + 394                
    + 395                # Directories
    + 396                for entry_directory in directories:
    + 397                    if entry_directory.is_directory():
    + 398                        recurse_action(
    + 399                            base_dir=self.smb_cwd, 
    + 400                            path=path+[entry_directory.get_longname()]
    + 401                        )                   
    + 402        # Entrypoint
    + 403        try:
    + 404            recurse_action(
    + 405                base_dir=self.smb_cwd, 
    + 406                path=[path]
    + 407            )
    + 408        except (BrokenPipeError, KeyboardInterrupt) as e:
    + 409            print("\x1b[v\x1b[o\r[!] Interrupted.")
    + 410            self.close_smb_session()
    + 411            self.init_smb_session()
    + 412
    + 413    def get_entry(self, path=None):
    + 414        """
    + 415        Retrieves information about a specific entry located at the provided path on the SMB share.
    + 416
    + 417        This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.
    + 418
    + 419        Args:
    + 420            path (str): The path of the entry to retrieve information about.
      421
    - 422        Parameters:
    - 423            share (bool): If True, display information about the current share.
    - 424            server (bool): If True, display information about the server.
    + 422        Returns:
    + 423            Entry: An object representing the entry at the specified path, or None if the entry is not found.
    + 424        """
      425
    - 426        Returns:
    - 427            None
    - 428        """
    - 429
    - 430        if server:
    - 431            if self.config.no_colors:
    - 432                print("[+] Server:")
    - 433                print("  ├─NetBIOS:")
    - 434                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
    - 435                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
    - 436                print("  ├─DNS:")
    - 437                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
    - 438                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
    - 439                print("  ├─OS:")
    - 440                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
    - 441                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    - 442                print("  ├─Server:")
    - 443                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
    - 444                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
    - 445                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
    - 446                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    - 447                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
    - 448                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    - 449                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    - 450                print("  └─")
    - 451            else:
    - 452                print("[+] Server:")
    - 453                print("  ├─NetBIOS:")
    - 454                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
    - 455                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
    - 456                print("  ├─DNS:")
    - 457                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
    - 458                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
    - 459                print("  ├─OS:")
    - 460                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
    - 461                print("  │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    - 462                print("  ├─Server:")
    - 463                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
    - 464                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
    - 465                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
    - 466                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    - 467                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
    - 468                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    - 469                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    - 470                print("  └─")
    - 471
    - 472        if share and self.smb_share is not None:
    - 473            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
    - 474            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
    - 475            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
    - 476            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
    - 477            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
    - 478            if self.config.no_colors:
    - 479                print("\n[+] Share:")
    - 480                print("  ├─ Name ──────────── : %s" % (share_name))
    - 481                print("  ├─ Description ───── : %s" % (share_comment))
    - 482                print("  ├─ Type ──────────── : %s" % (share_type))
    - 483                print("  └─ Raw type value ── : %s" % (share_rawtype))
    - 484            else:
    - 485                print("\n[+] Share:")
    - 486                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
    - 487                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
    - 488                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
    - 489                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
    - 490
    - 491    def list_contents(self, path=None):
    - 492        """
    - 493        Lists the contents of a specified directory on the SMB share.
    + 426        if self.path_exists(path=path):
    + 427            matches = self.smbClient.listPath(
    + 428                shareName=self.smb_share,
    + 429                path=path
    + 430            )
    + 431
    + 432            if len(matches) == 1:
    + 433                return matches[0]
    + 434            else:
    + 435                return None
    + 436        else:
    + 437            return None 
    + 438
    + 439    def info(self, share=True, server=True):
    + 440        """
    + 441        Displays information about the server and optionally the shares.
    + 442
    + 443        This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share.
    + 444
    + 445        Parameters:
    + 446            share (bool): If True, display information about the current share.
    + 447            server (bool): If True, display information about the server.
    + 448
    + 449        Returns:
    + 450            None
    + 451        """
    + 452
    + 453        if server:
    + 454            if self.config.no_colors:
    + 455                self.logger.print("[+] Server:")
    + 456                self.logger.print("  ├─NetBIOS:")
    + 457                self.logger.print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
    + 458                self.logger.print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
    + 459                self.logger.print("  ├─DNS:")
    + 460                self.logger.print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
    + 461                self.logger.print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
    + 462                self.logger.print("  ├─OS:")
    + 463                self.logger.print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
    + 464                self.logger.print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    + 465                self.logger.print("  ├─Server:")
    + 466                self.logger.print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
    + 467                self.logger.print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
    + 468                self.logger.print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
    + 469                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    + 470                self.logger.print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
    + 471                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    + 472                self.logger.print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    + 473                self.logger.print("  └─")
    + 474            else:
    + 475                self.logger.print("[+] Server:")
    + 476                self.logger.print("  ├─NetBIOS:")
    + 477                self.logger.print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
    + 478                self.logger.print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
    + 479                self.logger.print("  ├─DNS:")
    + 480                self.logger.print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
    + 481                self.logger.print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
    + 482                self.logger.print("  ├─OS:")
    + 483                self.logger.print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
    + 484                self.logger.print("  │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    + 485                self.logger.print("  ├─Server:")
    + 486                self.logger.print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
    + 487                self.logger.print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
    + 488                self.logger.print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
    + 489                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    + 490                self.logger.print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
    + 491                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    + 492                self.logger.print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    + 493                self.logger.print("  └─")
      494
    - 495        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
    - 496        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
    - 497        the long names of the files and directories as keys and their respective SMB entry objects as values.
    - 498
    - 499        Args:
    - 500            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
    - 501            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
    - 502
    - 503        Returns:
    - 504            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
    - 505        """
    - 506        
    - 507        dest_path = [self.smb_cwd.rstrip(ntpath.sep),]
    - 508        if path is not None and len(path) > 0:
    - 509            dest_path.append(path.rstrip(ntpath.sep))
    - 510        dest_path.append('*')
    - 511        path = ntpath.sep.join(dest_path)
    - 512
    - 513        contents = {}
    - 514        entries = self.smbClient.listPath(
    - 515            shareName=self.smb_share, 
    - 516            path=path
    - 517        )
    - 518        for entry in entries:
    - 519            contents[entry.get_longname()] = entry
    - 520
    - 521        return contents
    - 522
    - 523    def list_shares(self):
    - 524        """
    - 525        Lists all the shares available on the connected SMB server.
    - 526
    - 527        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
    - 528        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
    - 529        such as its name, type, raw type, and any comments associated with the share.
    - 530
    - 531        Returns:
    - 532            dict: A dictionary containing information about each share available on the server.
    - 533        """
    - 534
    - 535        self.available_shares = {}
    - 536
    - 537        if self.connected:
    - 538            if self.smbClient is not None:
    - 539                resp = self.smbClient.listShares()
    - 540
    - 541                for share in resp:
    - 542                    # SHARE_INFO_1 structure (lmshare.h)
    - 543                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
    - 544                    sharename = share["shi1_netname"][:-1]
    - 545                    sharecomment = share["shi1_remark"][:-1]
    - 546                    sharetype = share["shi1_type"]
    - 547
    - 548                    self.available_shares[sharename.lower()] = {
    - 549                        "name": sharename, 
    - 550                        "type": STYPE_MASK(sharetype), 
    - 551                        "rawtype": sharetype, 
    - 552                        "comment": sharecomment
    - 553                    }
    - 554            else:
    - 555                print("[!] Error: SMBSession.smbClient is None.")
    - 556
    - 557        return self.available_shares
    - 558
    - 559    def mkdir(self, path=None):
    - 560        """
    - 561        Creates a directory at the specified path on the SMB share.
    - 562
    - 563        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
    - 564        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
    - 565        the creation for that directory without raising an error.
    - 566
    - 567        Args:
    - 568            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
    - 569
    - 570        Note:
    - 571            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
    - 572        """
    - 573
    - 574        if path is not None:
    - 575            # Prepare path
    - 576            path = path.replace('/',ntpath.sep)
    - 577            if ntpath.sep in path:
    - 578                path = path.strip(ntpath.sep).split(ntpath.sep)
    - 579            else:
    - 580                path = [path]
    + 495        if share and self.smb_share is not None:
    + 496            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
    + 497            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
    + 498            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
    + 499            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
    + 500            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
    + 501            if self.config.no_colors:
    + 502                self.logger.print("\n[+] Share:")
    + 503                self.logger.print("  ├─ Name ──────────── : %s" % (share_name))
    + 504                self.logger.print("  ├─ Description ───── : %s" % (share_comment))
    + 505                self.logger.print("  ├─ Type ──────────── : %s" % (share_type))
    + 506                self.logger.print("  └─ Raw type value ── : %s" % (share_rawtype))
    + 507            else:
    + 508                self.logger.print("\n[+] Share:")
    + 509                self.logger.print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
    + 510                self.logger.print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
    + 511                self.logger.print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
    + 512                self.logger.print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
    + 513
    + 514    def list_contents(self, path=None):
    + 515        """
    + 516        Lists the contents of a specified directory on the SMB share.
    + 517
    + 518        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
    + 519        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
    + 520        the long names of the files and directories as keys and their respective SMB entry objects as values.
    + 521
    + 522        Args:
    + 523            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
    + 524            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
    + 525
    + 526        Returns:
    + 527            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
    + 528        """
    + 529        
    + 530        dest_path = [self.smb_cwd.rstrip(ntpath.sep),]
    + 531        if path is not None and len(path) > 0:
    + 532            dest_path.append(path.rstrip(ntpath.sep))
    + 533        dest_path.append('*')
    + 534        path = ntpath.sep.join(dest_path)
    + 535
    + 536        contents = {}
    + 537        entries = self.smbClient.listPath(
    + 538            shareName=self.smb_share, 
    + 539            path=path
    + 540        )
    + 541        for entry in entries:
    + 542            contents[entry.get_longname()] = entry
    + 543
    + 544        return contents
    + 545
    + 546    def list_shares(self):
    + 547        """
    + 548        Lists all the shares available on the connected SMB server.
    + 549
    + 550        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
    + 551        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
    + 552        such as its name, type, raw type, and any comments associated with the share.
    + 553
    + 554        Returns:
    + 555            dict: A dictionary containing information about each share available on the server.
    + 556        """
    + 557
    + 558        self.available_shares = {}
    + 559
    + 560        if self.connected:
    + 561            if self.smbClient is not None:
    + 562                resp = self.smbClient.listShares()
    + 563
    + 564                for share in resp:
    + 565                    # SHARE_INFO_1 structure (lmshare.h)
    + 566                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
    + 567                    sharename = share["shi1_netname"][:-1]
    + 568                    sharecomment = share["shi1_remark"][:-1]
    + 569                    sharetype = share["shi1_type"]
    + 570
    + 571                    self.available_shares[sharename.lower()] = {
    + 572                        "name": sharename, 
    + 573                        "type": STYPE_MASK(sharetype), 
    + 574                        "rawtype": sharetype, 
    + 575                        "comment": sharecomment
    + 576                    }
    + 577            else:
    + 578                self.logger.error("Error: SMBSession.smbClient is None.")
    + 579
    + 580        return self.available_shares
      581
    - 582            # Create each dir in the path
    - 583            for depth in range(1, len(path)+1):
    - 584                tmp_path = ntpath.sep.join(path[:depth])
    - 585                try:
    - 586                    self.smbClient.createDirectory(
    - 587                        shareName=self.smb_share, 
    - 588                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
    - 589                    )
    - 590                except impacket.smbconnection.SessionError as err:
    - 591                    if err.getErrorCode() == 0xc0000035:
    - 592                        # STATUS_OBJECT_NAME_COLLISION
    - 593                        # Remote directory already created, this is normal
    - 594                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
    - 595                        pass
    - 596                    else:
    - 597                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
    - 598                        if self.config.debug:
    - 599                            traceback.print_exc()
    - 600        else:
    - 601            pass
    - 602
    - 603    def mount(self, local_mount_point, remote_path):
    - 604        """
    - 605        Generates the command to mount an SMB share on different platforms.
    - 606
    - 607        This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.
    - 608        It constructs the mount command using the provided parameters and executes it using the os.system() function.
    - 609
    - 610        Args:
    - 611            local_mount_point (str): The local directory where the SMB share will be mounted.
    - 612            remote_path (str): The remote path on the SMB share to be mounted.
    - 613
    - 614        Note:
    - 615            - For Windows platform, the command uses 'net use' to mount the share.
    - 616            - For Linux platform, the command uses 'mount' to mount the share.
    - 617            - For macOS platform, the command uses 'mount_smbfs' to mount the share.
    - 618            - If the platform is not supported, an error message is displayed.
    - 619
    - 620        Returns:
    - 621            None
    - 622        """
    - 623
    - 624        if not os.path.exists(local_mount_point):
    - 625            pass
    - 626
    - 627        if sys.platform.startswith('win'):
    - 628            remote_path = remote_path.replace('/',ntpath.sep)
    - 629            command = f"net use {local_mount_point} \\\\{self.address}\\{self.smb_share}\\{remote_path}"
    - 630        
    - 631        elif sys.platform.startswith('linux'):
    - 632            remote_path = remote_path.replace(ntpath.sep,'/')
    - 633            command = f"mount -t cifs //{self.address}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.username},password={self.password}"
    - 634        
    - 635        elif sys.platform.startswith('darwin'):
    - 636            remote_path = remote_path.replace(ntpath.sep,'/')
    - 637            command = f"mount_smbfs //{self.username}:{self.password}@{self.address}/{self.smb_share}/{remote_path} {local_mount_point}"
    - 638        
    - 639        else:
    - 640            command = None
    - 641            print("[!] Unsupported platform for mounting SMB share.")
    - 642        
    - 643        if command is not None:
    - 644            if self.config.debug:
    - 645                print("[debug] Executing: %s" % command)
    - 646            os.system(command)
    - 647
    - 648    def path_exists(self, path=None):
    - 649        """
    - 650        Checks if the specified path exists on the SMB share.
    - 651
    - 652        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
    - 653        If the path listing is successful and returns one or more entries, the path is considered to exist.
    - 654
    - 655        Args:
    - 656            path (str, optional): The path to check on the SMB share. Defaults to None.
    - 657
    - 658        Returns:
    - 659            bool: True if the path exists, False otherwise or if an error occurs.
    - 660        """
    - 661
    - 662        if path is not None:
    - 663            path = path.replace('*','')
    - 664            try:
    - 665                contents = self.smbClient.listPath(
    - 666                    shareName=self.smb_share,
    - 667                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
    - 668                )
    - 669                return (len(contents) != 0)
    - 670            except Exception as e:
    - 671                return False
    - 672        else:
    - 673            return False
    - 674   
    - 675    def path_isdir(self, pathFromRoot=None):
    - 676        """
    - 677        Checks if the specified path is a directory on the SMB share.
    - 678
    - 679        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
    - 680        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
    - 681
    - 682        Args:
    - 683            path (str, optional): The path to check on the SMB share. Defaults to None.
    + 582    def mkdir(self, path=None):
    + 583        """
    + 584        Creates a directory at the specified path on the SMB share.
    + 585
    + 586        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
    + 587        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
    + 588        the creation for that directory without raising an error.
    + 589
    + 590        Args:
    + 591            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
    + 592
    + 593        Note:
    + 594            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
    + 595        """
    + 596
    + 597        if path is not None:
    + 598            # Prepare path
    + 599            path = path.replace('/',ntpath.sep)
    + 600            if ntpath.sep in path:
    + 601                path = path.strip(ntpath.sep).split(ntpath.sep)
    + 602            else:
    + 603                path = [path]
    + 604
    + 605            # Create each dir in the path
    + 606            for depth in range(1, len(path)+1):
    + 607                tmp_path = ntpath.sep.join(path[:depth])
    + 608                try:
    + 609                    self.smbClient.createDirectory(
    + 610                        shareName=self.smb_share, 
    + 611                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
    + 612                    )
    + 613                except impacket.smbconnection.SessionError as err:
    + 614                    if err.getErrorCode() == 0xc0000035:
    + 615                        # STATUS_OBJECT_NAME_COLLISION
    + 616                        # Remote directory already created, this is normal
    + 617                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
    + 618                        pass
    + 619                    else:
    + 620                        self.logger.error("Failed to create directory '%s': %s" % (tmp_path, err))
    + 621                        if self.config.debug:
    + 622                            traceback.print_exc()
    + 623        else:
    + 624            pass
    + 625
    + 626    def mount(self, local_mount_point, remote_path):
    + 627        """
    + 628        Generates the command to mount an SMB share on different platforms.
    + 629
    + 630        This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.
    + 631        It constructs the mount command using the provided parameters and executes it using the os.system() function.
    + 632
    + 633        Args:
    + 634            local_mount_point (str): The local directory where the SMB share will be mounted.
    + 635            remote_path (str): The remote path on the SMB share to be mounted.
    + 636
    + 637        Note:
    + 638            - For Windows platform, the command uses 'net use' to mount the share.
    + 639            - For Linux platform, the command uses 'mount' to mount the share.
    + 640            - For macOS platform, the command uses 'mount_smbfs' to mount the share.
    + 641            - If the platform is not supported, an error message is displayed.
    + 642
    + 643        Returns:
    + 644            None
    + 645        """
    + 646
    + 647        if not os.path.exists(local_mount_point):
    + 648            pass
    + 649
    + 650        if sys.platform.startswith('win'):
    + 651            remote_path = remote_path.replace('/',ntpath.sep)
    + 652            command = f"net use {local_mount_point} \\\\{self.host}\\{self.smb_share}\\{remote_path}"
    + 653        
    + 654        elif sys.platform.startswith('linux'):
    + 655            remote_path = remote_path.replace(ntpath.sep,'/')
    + 656            command = f"mount -t cifs //{self.host}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.credentials.username},password={self.credentials.password}"
    + 657        
    + 658        elif sys.platform.startswith('darwin'):
    + 659            remote_path = remote_path.replace(ntpath.sep,'/')
    + 660            command = f"mount_smbfs //{self.credentials.username}:{self.credentials.password}@{self.host}/{self.smb_share}/{remote_path} {local_mount_point}"
    + 661        
    + 662        else:
    + 663            command = None
    + 664            self.logger.error("Unsupported platform for mounting SMB share.")
    + 665        
    + 666        if command is not None:
    + 667            if self.config.debug:
    + 668                self.logger.debug("Executing: %s" % command)
    + 669            os.system(command)
    + 670
    + 671    def path_exists(self, path=None):
    + 672        """
    + 673        Checks if the specified path exists on the SMB share.
    + 674
    + 675        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
    + 676        If the path listing is successful and returns one or more entries, the path is considered to exist.
    + 677
    + 678        Args:
    + 679            path (str, optional): The path to check on the SMB share. Defaults to None.
    + 680
    + 681        Returns:
    + 682            bool: True if the path exists, False otherwise or if an error occurs.
    + 683        """
      684
    - 685        Returns:
    - 686            bool: True if the path is a directory, False otherwise or if an error occurs.
    - 687        """
    - 688
    - 689        if pathFromRoot is not None:
    - 690            # Replace slashes if any
    - 691            path = pathFromRoot.replace('/', ntpath.sep)
    - 692            
    - 693            # Strip wildcards to avoid injections
    - 694            path = path.replace('*','')
    - 695
    - 696            # Normalize path and strip leading backslash
    - 697            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    - 698
    - 699            if path.strip() in ['', '.', '..']:
    - 700                # By defininition they exist on the filesystem
    - 701                return True
    - 702            else:
    - 703                try:
    - 704                    contents = self.smbClient.listPath(
    - 705                        shareName=self.smb_share,
    - 706                        path=path+'*'
    - 707                    )
    - 708                    # Filter on directories
    - 709                    contents = [
    - 710                        c for c in contents
    - 711                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
    - 712                    ]
    - 713                    return (len(contents) != 0)
    - 714                except Exception as e:
    - 715                    return False
    - 716        else:
    - 717            return False
    + 685        if path is not None:
    + 686            path = path.replace('*','')
    + 687            path = path.replace('/', ntpath.sep)
    + 688            try:
    + 689                contents = self.smbClient.listPath(
    + 690                    shareName=self.smb_share,
    + 691                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
    + 692                )
    + 693                return (len(contents) != 0)
    + 694            except Exception as e:
    + 695                return False
    + 696        else:
    + 697            return False
    + 698   
    + 699    def path_isdir(self, pathFromRoot=None):
    + 700        """
    + 701        Checks if the specified path is a directory on the SMB share.
    + 702
    + 703        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
    + 704        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
    + 705
    + 706        Args:
    + 707            path (str, optional): The path to check on the SMB share. Defaults to None.
    + 708
    + 709        Returns:
    + 710            bool: True if the path is a directory, False otherwise or if an error occurs.
    + 711        """
    + 712
    + 713        if pathFromRoot is not None: 
    + 714            # Strip wildcards to avoid injections
    + 715            path = pathFromRoot.replace('*','')
    + 716            # Replace slashes if any
    + 717            path = path.replace('/', ntpath.sep)
      718
    - 719    def path_isfile(self, path=None):
    - 720        """
    - 721        Checks if the specified path is a file on the SMB share.
    - 722
    - 723        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
    - 724        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
    - 725
    - 726        Args:
    - 727            path (str, optional): The path to check on the SMB share. Defaults to None.
    - 728
    - 729        Returns:
    - 730            bool: True if the path is a file, False otherwise or if an error occurs.
    - 731        """
    - 732
    - 733        if path is not None:
    - 734            path = path.replace('*','')
    - 735            search_dir = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    - 736            search_dir = ntpath.dirname(search_dir) + ntpath.sep + '*'
    - 737            try:
    - 738                contents = self.smbClient.listPath(
    - 739                    shareName=self.smb_share,
    - 740                    path=search_dir
    - 741                )
    - 742                # Filter on files
    - 743                contents = [
    - 744                    c for c in contents
    - 745                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
    - 746                ]
    - 747                return (len(contents) != 0)
    - 748            except Exception as e:
    - 749                return False
    - 750        else:
    - 751            return False
    - 752
    - 753    def ping_smb_session(self):
    - 754        """
    - 755        Tests the connectivity to the SMB server by sending an echo command.
    - 756
    - 757        This method attempts to send an echo command to the SMB server to check if the session is still active.
    - 758        It updates the `connected` attribute of the class based on the success or failure of the echo command.
    - 759
    - 760        Returns:
    - 761            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
    - 762        """
    - 763
    - 764        try:
    - 765            self.smbClient.getSMBServer().echo()
    - 766        except Exception as e:
    - 767            self.connected = False
    - 768        return self.connected
    - 769
    - 770    def put_file(self, localpath=None):
    - 771        """
    - 772        Uploads a single file to the SMB share.
    - 773
    - 774        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
    - 775        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
    - 776        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
    - 777
    - 778        Args:
    - 779            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
    - 780        """
    - 781
    - 782        # Parse path
    - 783        localpath = localpath.replace('/', os.path.sep)
    - 784        if os.path.sep in localpath:
    - 785            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath))
    - 786        else:
    - 787            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep)
    - 788        # Parse filename
    - 789        filename = os.path.basename(localpath)
    - 790
    - 791        # Search for the file
    - 792        matches = os.listdir(tmp_search_path)
    - 793        # Filter the entries
    - 794        matching_entries = []
    - 795        for entry in matches:
    - 796            if entry == filename:
    - 797                matching_entries.append(entry)
    - 798            elif '*' in filename:
    - 799                regexp = filename.replace('.', '\\.').replace('*', '.*')
    - 800                if re.match(regexp, entry):
    - 801                    matching_entries.append(entry)
    - 802
    - 803        matching_entries = sorted(list(set(matching_entries)))
    + 719            # Normalize path and strip leading backslash
    + 720            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    + 721
    + 722            if path.strip() in ['', '.', '..']:
    + 723                # By defininition they exist on the filesystem
    + 724                return True
    + 725            else:
    + 726                try:
    + 727                    contents = self.smbClient.listPath(
    + 728                        shareName=self.smb_share,
    + 729                        path=path+'*'
    + 730                    )
    + 731                    # Filter on directories
    + 732                    contents = [
    + 733                        c for c in contents
    + 734                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
    + 735                    ]
    + 736                    return (len(contents) != 0)
    + 737                except Exception as e:
    + 738                    return False
    + 739        else:
    + 740            return False
    + 741
    + 742    def path_isfile(self, pathFromRoot=None):
    + 743        """
    + 744        Checks if the specified path is a file on the SMB share.
    + 745
    + 746        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
    + 747        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
    + 748
    + 749        Args:
    + 750            path (str, optional): The path to check on the SMB share. Defaults to None.
    + 751
    + 752        Returns:
    + 753            bool: True if the path is a file, False otherwise or if an error occurs.
    + 754        """
    + 755
    + 756        if pathFromRoot is not None: 
    + 757            # Strip wildcards to avoid injections
    + 758            path = pathFromRoot.replace('*','')
    + 759            # Replace slashes if any
    + 760            path = path.replace('/', ntpath.sep)
    + 761
    + 762            # Normalize path and strip leading backslash
    + 763            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    + 764
    + 765            try:
    + 766                contents = self.smbClient.listPath(
    + 767                    shareName=self.smb_share,
    + 768                    path=ntpath.dirname(path) + ntpath.sep + '*'
    + 769                )
    + 770                # Filter on files
    + 771                contents = [
    + 772                    c for c in contents
    + 773                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
    + 774                ]
    + 775                return (len(contents) != 0)
    + 776            except Exception as e:
    + 777                return False
    + 778        else:
    + 779            return False
    + 780
    + 781    def put_file(self, localpath=None):
    + 782        """
    + 783        Uploads a single file to the SMB share.
    + 784
    + 785        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
    + 786        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
    + 787        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
    + 788
    + 789        Args:
    + 790            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
    + 791        """
    + 792
    + 793        # Parse path
    + 794        localpath = localpath.replace('/', os.path.sep)
    + 795        if os.path.sep in localpath:
    + 796            if localpath.startswith(os.path.sep):
    + 797                # Absolute path
    + 798                tmp_search_path = os.path.normpath(localpath)
    + 799            else:
    + 800                # Relative path
    + 801                tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath))
    + 802        else:
    + 803            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep)
      804
    - 805        # Loop and upload
    - 806        for localpath in matching_entries:
    - 807            if os.path.exists(localpath):
    - 808                if os.path.isfile(localpath):
    - 809                    try:
    - 810                        localfile = os.path.basename(localpath)
    - 811                        f = LocalFileIO(
    - 812                            mode="rb", 
    - 813                            path=localpath, 
    - 814                            debug=self.config.debug
    - 815                        )
    - 816                        self.smbClient.putFile(
    - 817                            shareName=self.smb_share, 
    - 818                            pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
    - 819                            callback=f.read
    - 820                        )
    - 821                        f.close()
    - 822                    except (BrokenPipeError, KeyboardInterrupt) as err:
    - 823                        print("[!] Interrupted.")
    - 824                        self.close_smb_session()
    - 825                        self.init_smb_session()
    - 826                    except Exception as err:
    - 827                        print("[!] Failed to upload '%s': %s" % (localfile, err))
    - 828                        if self.config.debug:
    - 829                            traceback.print_exc()
    - 830                else:
    - 831                    # [!] The specified localpath is a directory. Use 'put -r <directory>' instead.
    - 832                    pass
    - 833            else:
    - 834                # [!] The specified localpath does not exist.
    - 835                pass
    - 836
    - 837    def put_file_recursively(self, localpath=None):
    - 838        """
    - 839        Recursively uploads files from a specified local directory to the SMB share.
    - 840
    - 841        This method walks through the given local directory and all its subdirectories, uploading each file to the
    - 842        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
    - 843        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
    - 844        and uploading files. If the local path is not a directory, it prints an error message.
    - 845
    - 846        Args:
    - 847            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
    - 848        """
    - 849
    - 850        if os.path.exists(localpath):
    - 851            if os.path.isfile(localpath):
    - 852                # Iterate over all files and directories within the local path
    - 853                local_files = {}
    - 854                for root, dirs, files in os.walk(localpath):
    - 855                    if len(files) != 0:
    - 856                        local_files[root] = files
    - 857
    - 858                # Iterate over the found files
    - 859                for local_dir_path in sorted(local_files.keys()):
    - 860                    print("[>] Putting files of '%s'" % local_dir_path)
    - 861
    - 862                    # Create remote directory
    - 863                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
    - 864                    self.mkdir(
    - 865                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
    - 866                    )
    - 867
    - 868                    for local_file_path in local_files[local_dir_path]:
    - 869                        try:
    - 870                            f = LocalFileIO(
    - 871                                mode="rb", 
    - 872                                path=local_dir_path + os.path.sep + local_file_path, 
    - 873                                debug=self.config.debug
    - 874                            )
    - 875                            self.smbClient.putFile(
    - 876                                shareName=self.smb_share, 
    - 877                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
    - 878                                callback=f.read
    - 879                            )
    - 880                            f.close()
    + 805        # Parse filename
    + 806        filename = os.path.basename(localpath)
    + 807
    + 808        # Search for the file
    + 809        matches = os.listdir(tmp_search_path)
    + 810        # Filter the entries
    + 811        matching_entries = []
    + 812        for entry in matches:
    + 813            if entry == filename:
    + 814                matching_entries.append(entry)
    + 815            elif '*' in filename:
    + 816                regexp = filename.replace('.', '\\.').replace('*', '.*')
    + 817                if re.match(regexp, entry):
    + 818                    matching_entries.append(entry)
    + 819
    + 820        matching_entries = sorted(list(set(matching_entries)))
    + 821
    + 822        # Loop and upload
    + 823        for localpath in matching_entries:
    + 824            if os.path.exists(localpath):
    + 825                if os.path.isfile(localpath):
    + 826                    try:
    + 827                        localfile = os.path.basename(localpath)
    + 828                        f = LocalFileIO(
    + 829                            mode="rb", 
    + 830                            path=localpath, 
    + 831                            debug=self.config.debug
    + 832                        )
    + 833                        self.smbClient.putFile(
    + 834                            shareName=self.smb_share, 
    + 835                            pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
    + 836                            callback=f.read
    + 837                        )
    + 838                        f.close()
    + 839
    + 840                    except (BrokenPipeError, KeyboardInterrupt) as err:
    + 841                        self.logger.error("Interrupted.")
    + 842                        self.close_smb_session()
    + 843                        self.init_smb_session()
    + 844
    + 845                    except (Exception, PermissionError) as err:
    + 846                        f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    + 847                        f.close(remove=False)
    + 848                        if self.config.debug:
    + 849                            traceback.print_exc()
    + 850                else:
    + 851                    # [!] The specified localpath is a directory. Use 'put -r <directory>' instead.
    + 852                    pass
    + 853            else:
    + 854                # [!] The specified localpath does not exist.
    + 855                pass
    + 856
    + 857    def put_file_recursively(self, localpath=None):
    + 858        """
    + 859        Recursively uploads files from a specified local directory to the SMB share.
    + 860
    + 861        This method walks through the given local directory and all its subdirectories, uploading each file to the
    + 862        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
    + 863        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
    + 864        and uploading files. If the local path is not a directory, it prints an error message.
    + 865
    + 866        Args:
    + 867            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
    + 868        """
    + 869
    + 870        if os.path.exists(localpath):
    + 871            if os.path.isdir(localpath):
    + 872                # Iterate over all files and directories within the local path
    + 873                local_files = {}
    + 874                for root, dirs, files in os.walk(localpath):
    + 875                    if len(files) != 0:
    + 876                        local_files[root] = files
    + 877
    + 878                # Iterate over the found files
    + 879                for local_dir_path in sorted(local_files.keys()):
    + 880                    self.logger.print("[>] Putting files of '%s'" % local_dir_path)
      881
    - 882                        except BrokenPipeError as err:
    - 883                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    - 884                            f.close(remove=True)
    - 885                            break
    - 886                        except Exception as err:
    - 887                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    - 888                            f.close(remove=True)
    - 889                else:
    - 890                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
    - 891        else:
    - 892            print("[!] The specified localpath does not exist.")
    - 893
    - 894    def rmdir(self, path=None):
    - 895        """
    - 896        Removes a directory from the SMB share at the specified path.
    - 897
    - 898        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
    - 899        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    - 900        the stack trace of the exception.
    + 882                    # Create remote directory
    + 883                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
    + 884                    self.mkdir(
    + 885                        path=ntpath.normpath(remote_dir_path + ntpath.sep)
    + 886                    )
    + 887
    + 888                    for local_file_path in local_files[local_dir_path]:
    + 889                        try:
    + 890                            f = LocalFileIO(
    + 891                                mode="rb", 
    + 892                                path=local_dir_path + os.path.sep + local_file_path, 
    + 893                                debug=self.config.debug
    + 894                            )
    + 895                            self.smbClient.putFile(
    + 896                                shareName=self.smb_share, 
    + 897                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
    + 898                                callback=f.read
    + 899                            )
    + 900                            f.close()
      901
    - 902        Args:
    - 903            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
    - 904        """
    - 905        try:
    - 906            self.smbClient.deleteDirectory(
    - 907                shareName=self.smb_share, 
    - 908                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
    - 909            )
    - 910        except Exception as err:
    - 911            print("[!] Failed to remove directory '%s': %s" % (path, err))
    - 912            if self.config.debug:
    - 913                traceback.print_exc()
    - 914
    - 915    def rm(self, path=None):
    - 916        """
    - 917        Removes a file from the SMB share at the specified path.
    - 918
    - 919        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
    - 920        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    - 921        the stack trace of the exception.
    - 922
    - 923        Args:
    - 924            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
    - 925        """
    - 926
    - 927        # Parse path
    - 928        path = path.replace('/', ntpath.sep)
    - 929        if ntpath.sep in path:
    - 930            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    - 931        else:
    - 932            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    - 933        # Parse filename
    - 934        filename = ntpath.basename(path)
    - 935
    - 936        # Search for the file
    - 937        matches = self.smbClient.listPath(
    - 938            shareName=self.smb_share, 
    - 939            path=tmp_search_path + ntpath.sep + '*'
    - 940        )   
    + 902                        except (BrokenPipeError, KeyboardInterrupt) as err:
    + 903                            self.logger.error("Interrupted.")
    + 904                            self.close_smb_session()
    + 905                            self.init_smb_session()
    + 906                            
    + 907                        except (Exception, PermissionError) as err:
    + 908                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    + 909                            f.close(remove=False)
    + 910                            if self.config.debug:
    + 911                                traceback.print_exc()
    + 912                else:
    + 913                    self.logger.error("The specified localpath is a file. Use 'put <file>' instead.")
    + 914        else:
    + 915            self.logger.error("The specified localpath does not exist.")
    + 916
    + 917    def read_file(self, path=None):
    + 918        """
    + 919        Reads a file from the SMB share.
    + 920
    + 921        This method attempts to read the contents of a file specified by the `path` parameter from the SMB share.
    + 922        It constructs the full path to the file, checks if the path is a valid file, and then reads the file content
    + 923        into a byte stream which is returned to the caller.
    + 924
    + 925        Args:
    + 926            path (str, optional): The path of the file to be read from the SMB share. Defaults to None.
    + 927
    + 928        Returns:
    + 929            bytes: The content of the file as a byte stream, or None if the file does not exist or an error occurs.
    + 930        """
    + 931
    + 932        if self.path_isfile(pathFromRoot=path):
    + 933            path = path.replace('/', ntpath.sep)
    + 934            if path.startswith(ntpath.sep):
    + 935                # Absolute path
    + 936                tmp_file_path = ntpath.normpath(path)
    + 937            else:
    + 938                # Relative path
    + 939                tmp_file_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    + 940            tmp_file_path = tmp_file_path.lstrip(ntpath.sep)
      941
    - 942        # Filter the entries
    - 943        matching_entries = []
    - 944        for entry in matches:
    - 945            if entry.is_directory():
    - 946                # Skip directories
    - 947                continue
    - 948            if entry.get_longname() == filename:
    - 949                matching_entries.append(entry)
    - 950            elif '*' in filename:
    - 951                regexp = filename.replace('.', '\\.').replace('*', '.*')
    - 952                if re.match(regexp, entry.get_longname()):
    - 953                    matching_entries.append(entry)
    - 954        
    - 955        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    - 956
    - 957        for entry in matching_entries:
    - 958            try:
    - 959                self.smbClient.deleteFile(
    - 960                    shareName=self.smb_share, 
    - 961                    pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), 
    - 962                )
    - 963            except Exception as err:
    - 964                print("[!] Failed to remove file '%s': %s" % (path, err))
    - 965                if self.config.debug:
    - 966                    traceback.print_exc()
    - 967
    - 968    def tree(self, path=None):
    - 969        """
    - 970        Recursively lists the directory structure of the SMB share starting from the specified path.
    - 971
    - 972        This function prints a visual representation of the directory tree of the remote SMB share. It uses
    - 973        recursion to navigate through directories and lists all files and subdirectories in each directory.
    - 974        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
    + 942            fh = io.BytesIO()
    + 943            try:
    + 944                # opening the files in streams instead of mounting shares allows 
    + 945                # for running the script from unprivileged containers
    + 946                self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write)
    + 947            except impacket.smbconnection.SessionError as e:
    + 948                return None
    + 949            rawdata = fh.getvalue()
    + 950            fh.close()
    + 951            return rawdata
    + 952        else:
    + 953            return None
    + 954
    + 955    def rmdir(self, path=None):
    + 956        """
    + 957        Removes a directory from the SMB share at the specified path.
    + 958
    + 959        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
    + 960        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    + 961        the stack trace of the exception.
    + 962
    + 963        Args:
    + 964            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
    + 965        """
    + 966        try:
    + 967            self.smbClient.deleteDirectory(
    + 968                shareName=self.smb_share, 
    + 969                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
    + 970            )
    + 971        except Exception as err:
    + 972            self.logger.error("Failed to remove directory '%s': %s" % (path, err))
    + 973            if self.config.debug:
    + 974                traceback.print_exc()
      975
    - 976        Args:
    - 977            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
    - 978                                  Defaults to the root of the current share.
    - 979        """
    - 980        
    - 981        def recurse_action(base_dir="", path=[], prompt=[]):
    - 982            bars = ["│   ", "├── ", "└── "]
    + 976    def rm(self, path=None):
    + 977        """
    + 978        Removes a file from the SMB share at the specified path.
    + 979
    + 980        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
    + 981        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    + 982        the stack trace of the exception.
      983
    - 984            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
    - 985
    - 986            entries = []
    - 987            try:
    - 988                entries = self.smbClient.listPath(
    - 989                    shareName=self.smb_share, 
    - 990                    path=remote_smb_path+'\\*'
    - 991                )
    - 992            except impacket.smbconnection.SessionError as err:
    - 993                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
    - 994                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
    - 995                if self.config.no_colors:
    - 996                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
    - 997                else:
    - 998                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
    - 999                return 
    -1000
    -1001            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    -1002            entries = sorted(entries, key=lambda x:x.get_longname())
    -1003
    -1004            # 
    -1005            if len(entries) > 1:
    -1006                index = 0
    -1007                for entry in entries:
    -1008                    index += 1
    -1009                    # This is the first entry 
    -1010                    if index == 0:
    -1011                        if entry.is_directory():
    -1012                            if self.config.no_colors:
    -1013                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1014                            else:
    -1015                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1016                            recurse_action(
    -1017                                base_dir=base_dir, 
    -1018                                path=path+[entry.get_longname()],
    -1019                                prompt=prompt+["│   "]
    -1020                            )
    -1021                        else:
    -1022                            if self.config.no_colors:
    -1023                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1024                            else:
    -1025                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1026
    -1027                    # This is the last entry
    -1028                    elif index == len(entries):
    -1029                        if entry.is_directory():
    -1030                            if self.config.no_colors:
    -1031                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1032                            else:
    -1033                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1034                            recurse_action(
    -1035                                base_dir=base_dir, 
    -1036                                path=path+[entry.get_longname()],
    -1037                                prompt=prompt+["    "]
    -1038                            )
    -1039                        else:
    -1040                            if self.config.no_colors:
    -1041                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1042                            else:
    -1043                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1044                        
    -1045                    # These are entries in the middle
    -1046                    else:
    -1047                        if entry.is_directory():
    -1048                            if self.config.no_colors:
    -1049                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1050                            else:
    -1051                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1052                            recurse_action(
    -1053                                base_dir=base_dir, 
    -1054                                path=path+[entry.get_longname()],
    -1055                                prompt=prompt+["│   "]
    -1056                            )
    -1057                        else:
    -1058                            if self.config.no_colors:
    -1059                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1060                            else:
    -1061                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1062
    -1063            # 
    -1064            elif len(entries) == 1:
    -1065                entry = entries[0]
    -1066                if entry.is_directory():
    -1067                    if self.config.no_colors:
    -1068                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1069                    else:
    -1070                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1071                    recurse_action(
    -1072                        base_dir=base_dir, 
    -1073                        path=path+[entry.get_longname()],
    -1074                        prompt=prompt+["    "]
    -1075                    )
    -1076                else:
    -1077                    if self.config.no_colors:
    -1078                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1079                    else:
    -1080                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1081
    -1082        # Entrypoint
    -1083        try:
    -1084            if self.config.no_colors:
    -1085                print("%s\\" % path)
    -1086            else:
    -1087                print("\x1b[1;96m%s\x1b[0m\\" % path)
    -1088            recurse_action(
    -1089                base_dir=self.smb_cwd, 
    -1090                path=[path],
    -1091                prompt=[""]
    -1092            )
    -1093        except (BrokenPipeError, KeyboardInterrupt) as e:
    -1094            print("[!] Interrupted.")
    -1095            self.close_smb_session()
    -1096            self.init_smb_session()
    -1097
    -1098    def umount(self, local_mount_point):
    -1099        """
    -1100        Unmounts the specified local mount point of the remote share.
    -1101
    -1102        This method unmounts the specified local mount point of the remote share based on the platform.
    -1103        It supports Windows, Linux, and macOS platforms for unmounting.
    -1104
    -1105        Parameters:
    -1106            local_mount_point (str): The local mount point to unmount.
    -1107
    -1108        Raises:
    -1109            None
    -1110        """
    -1111
    -1112        if os.path.exists(local_mount_point):
    -1113            if sys.platform.startswith('win'):
    -1114                command = f"net use {local_mount_point} /delete"
    -1115
    -1116            elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    -1117                command = f"umount {local_mount_point}"
    -1118
    -1119            else:
    -1120                command = None
    -1121                print("[!] Unsupported platform for unmounting SMB share.")
    -1122        
    -1123            if command is not None:
    -1124                if self.config.debug:
    -1125                    print("[debug] Executing: %s" % command)
    -1126                os.system(command)
    -1127        else:
    -1128            print("[!] Cannot unmount a non existing path.")        
    -1129
    -1130    # Other functions
    -1131
    -1132    def test_rights(self, sharename): 
    -1133        """
    -1134        Tests the read and write access rights of the current SMB session.
    -1135
    -1136        This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.
    -1137        
    -1138        Returns:
    -1139            dict: A dictionary containing the read and write access rights status.
    -1140                - "readable" (bool): Indicates if the session has read access rights.
    -1141                - "writable" (bool): Indicates if the session has write access rights.
    -1142        """
    -1143
    -1144        # Restore the current share
    -1145        current_share = self.smb_share
    -1146        self.set_share(shareName=sharename)
    -1147
    -1148        access_rights = {"readable": False, "writable": False}
    -1149        try:
    -1150            self.smbClient.listPath(self.smb_share, '*', password=None)
    -1151            access_rights["readable"] = True
    -1152        except impacket.smbconnection.SessionError as e:
    -1153            access_rights["readable"] = False
    -1154
    -1155        try:
    -1156            temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)]))
    -1157            self.smbClient.createDirectory(self.smb_share, temp_dir)
    -1158            self.smbClient.deleteDirectory(self.smb_share, temp_dir)
    -1159            access_rights["writable"] = True
    -1160        except impacket.smbconnection.SessionError as e:
    -1161            access_rights["writable"] = False
    + 984        Args:
    + 985            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
    + 986        """
    + 987
    + 988        # Parse path
    + 989        path = path.replace('/', ntpath.sep)
    + 990        if ntpath.sep in path:
    + 991            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    + 992        else:
    + 993            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    + 994        # Parse filename
    + 995        filename = ntpath.basename(path)
    + 996
    + 997        # Search for the file
    + 998        matches = self.smbClient.listPath(
    + 999            shareName=self.smb_share, 
    +1000            path=tmp_search_path + ntpath.sep + '*'
    +1001        )   
    +1002
    +1003        # Filter the entries
    +1004        matching_entries = []
    +1005        for entry in matches:
    +1006            if entry.is_directory():
    +1007                # Skip directories
    +1008                continue
    +1009            if entry.get_longname() == filename:
    +1010                matching_entries.append(entry)
    +1011            elif '*' in filename:
    +1012                regexp = filename.replace('.', '\\.').replace('*', '.*')
    +1013                if re.match(regexp, entry.get_longname()):
    +1014                    matching_entries.append(entry)
    +1015        
    +1016        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    +1017
    +1018        for entry in matching_entries:
    +1019            try:
    +1020                self.smbClient.deleteFile(
    +1021                    shareName=self.smb_share, 
    +1022                    pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), 
    +1023                )
    +1024            except Exception as err:
    +1025                self.logger.error("Failed to remove file '%s': %s" % (path, err))
    +1026                if self.config.debug:
    +1027                    traceback.print_exc()
    +1028
    +1029    def tree(self, path=None):
    +1030        """
    +1031        Recursively lists the directory structure of the SMB share starting from the specified path.
    +1032
    +1033        This function prints a visual representation of the directory tree of the remote SMB share. It uses
    +1034        recursion to navigate through directories and lists all files and subdirectories in each directory.
    +1035        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
    +1036
    +1037        Args:
    +1038            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
    +1039                                  Defaults to the root of the current share.
    +1040        """
    +1041        
    +1042        def recurse_action(base_dir="", path=[], prompt=[]):
    +1043            bars = ["│   ", "├── ", "└── "]
    +1044
    +1045            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
    +1046
    +1047            entries = []
    +1048            try:
    +1049                entries = self.smbClient.listPath(
    +1050                    shareName=self.smb_share, 
    +1051                    path=remote_smb_path+'\\*'
    +1052                )
    +1053            except impacket.smbconnection.SessionError as err:
    +1054                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
    +1055                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
    +1056                if self.config.no_colors:
    +1057                    self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
    +1058                else:
    +1059                    self.logger.print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
    +1060                return 
    +1061
    +1062            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    +1063            entries = sorted(entries, key=lambda x:x.get_longname())
    +1064
    +1065            # 
    +1066            if len(entries) > 1:
    +1067                index = 0
    +1068                for entry in entries:
    +1069                    index += 1
    +1070                    # This is the first entry 
    +1071                    if index == 0:
    +1072                        if entry.is_directory():
    +1073                            if self.config.no_colors:
    +1074                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1075                            else:
    +1076                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1077                            recurse_action(
    +1078                                base_dir=base_dir, 
    +1079                                path=path+[entry.get_longname()],
    +1080                                prompt=prompt+["│   "]
    +1081                            )
    +1082                        else:
    +1083                            if self.config.no_colors:
    +1084                                self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1085                            else:
    +1086                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1087
    +1088                    # This is the last entry
    +1089                    elif index == len(entries):
    +1090                        if entry.is_directory():
    +1091                            if self.config.no_colors:
    +1092                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1093                            else:
    +1094                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1095                            recurse_action(
    +1096                                base_dir=base_dir, 
    +1097                                path=path+[entry.get_longname()],
    +1098                                prompt=prompt+["    "]
    +1099                            )
    +1100                        else:
    +1101                            if self.config.no_colors:
    +1102                                self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1103                            else:
    +1104                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1105                        
    +1106                    # These are entries in the middle
    +1107                    else:
    +1108                        if entry.is_directory():
    +1109                            if self.config.no_colors:
    +1110                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1111                            else:
    +1112                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1113                            recurse_action(
    +1114                                base_dir=base_dir, 
    +1115                                path=path+[entry.get_longname()],
    +1116                                prompt=prompt+["│   "]
    +1117                            )
    +1118                        else:
    +1119                            if self.config.no_colors:
    +1120                                self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1121                            else:
    +1122                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1123
    +1124            # 
    +1125            elif len(entries) == 1:
    +1126                entry = entries[0]
    +1127                if entry.is_directory():
    +1128                    if self.config.no_colors:
    +1129                        self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1130                    else:
    +1131                        self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1132                    recurse_action(
    +1133                        base_dir=base_dir, 
    +1134                        path=path+[entry.get_longname()],
    +1135                        prompt=prompt+["    "]
    +1136                    )
    +1137                else:
    +1138                    if self.config.no_colors:
    +1139                        self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1140                    else:
    +1141                        self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1142
    +1143        # Entrypoint
    +1144        try:
    +1145            if self.config.no_colors:
    +1146                self.logger.print("%s\\" % path)
    +1147            else:
    +1148                self.logger.print("\x1b[1;96m%s\x1b[0m\\" % path)
    +1149            recurse_action(
    +1150                base_dir=self.smb_cwd, 
    +1151                path=[path],
    +1152                prompt=[""]
    +1153            )
    +1154        except (BrokenPipeError, KeyboardInterrupt) as e:
    +1155            self.logger.error("Interrupted.")
    +1156            self.close_smb_session()
    +1157            self.init_smb_session()
    +1158
    +1159    def umount(self, local_mount_point):
    +1160        """
    +1161        Unmounts the specified local mount point of the remote share.
     1162
    -1163        # Restore the current share
    -1164        self.set_share(shareName=current_share)
    +1163        This method unmounts the specified local mount point of the remote share based on the platform.
    +1164        It supports Windows, Linux, and macOS platforms for unmounting.
     1165
    -1166        return access_rights
    -1167
    -1168    # Setter / Getter
    -1169
    -1170    def set_share(self, shareName):
    -1171        """
    -1172        Sets the current SMB share to the specified share name.
    -1173
    -1174        This method updates the SMB session to use the specified share name. It checks if the share name is valid
    -1175        and updates the smb_share attribute of the SMBSession instance.
    +1166        Parameters:
    +1167            local_mount_point (str): The local mount point to unmount.
    +1168
    +1169        Raises:
    +1170            None
    +1171        """
    +1172
    +1173        if os.path.exists(local_mount_point):
    +1174            if sys.platform.startswith('win'):
    +1175                command = f"net use {local_mount_point} /delete"
     1176
    -1177        Parameters:
    -1178            shareName (str): The name of the share to set as the current SMB share.
    +1177            elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    +1178                command = f"umount {local_mount_point}"
     1179
    -1180        Raises:
    -1181            ValueError: If the shareName is None or an empty string.
    -1182        """
    -1183
    -1184        if shareName is not None:
    -1185            self.list_shares()
    -1186            if shareName.lower() in self.available_shares.keys():
    -1187                # Doing this in order to keep the case of the share adevertised by the remote machine
    -1188                self.smb_share = self.available_shares[shareName.lower()]["name"]
    -1189                self.smb_cwd = ""
    -1190                # Connects the tree
    -1191                self.smb_tree_id = self.smbClient.connectTree(self.smb_share)
    -1192            else:
    -1193                print("[!] Could not set share '%s', it does not exist remotely." % shareName)
    -1194        else:
    -1195            self.smb_share = None
    -1196            
    -1197    def set_cwd(self, path=None):
    -1198        """
    -1199        Sets the current working directory on the SMB share to the specified path.
    -1200
    -1201        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
    -1202        If the specified path is not a directory, the cwd remains unchanged.
    +1180            else:
    +1181                command = None
    +1182                self.logger.error("Unsupported platform for unmounting SMB share.")
    +1183        
    +1184            if command is not None:
    +1185                self.logger.debug("Executing: %s" % command)
    +1186                os.system(command)
    +1187        else:
    +1188            self.logger.error("Cannot unmount a non existing path.")        
    +1189
    +1190    # Other functions
    +1191
    +1192    def test_rights(self, sharename): 
    +1193        """
    +1194        Tests the read and write access rights of the current SMB session.
    +1195
    +1196        This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.
    +1197        
    +1198        Returns:
    +1199            dict: A dictionary containing the read and write access rights status.
    +1200                - "readable" (bool): Indicates if the session has read access rights.
    +1201                - "writable" (bool): Indicates if the session has write access rights.
    +1202        """
     1203
    -1204        Parameters:
    -1205            path (str): The path to set as the current working directory.
    -1206
    -1207        Raises:
    -1208            ValueError: If the specified path is not a directory.
    -1209        """
    -1210
    -1211        if path is not None:
    -1212            # Set path separators to ntpath sep 
    -1213            if '/' in path:
    -1214                path = path.replace('/', ntpath.sep)
    -1215
    -1216            if path.startswith(ntpath.sep):
    -1217                # Absolute path
    -1218                path = path + ntpath.sep
    -1219            else:
    -1220                # Relative path to the CWD
    -1221                if len(self.smb_cwd) == 0:
    -1222                    path = path + ntpath.sep
    -1223                else:
    -1224                    path = self.smb_cwd + ntpath.sep + path
    -1225            
    -1226            # Path normalization
    -1227            path = ntpath.normpath(path)
    -1228            path = re.sub(r'\\+', r'\\', path)
    +1204        # Restore the current share
    +1205        current_share = self.smb_share
    +1206        self.set_share(shareName=sharename)
    +1207
    +1208        access_rights = {"readable": False, "writable": False}
    +1209        try:
    +1210            self.smbClient.listPath(self.smb_share, '*', password=None)
    +1211            access_rights["readable"] = True
    +1212        except impacket.smbconnection.SessionError as e:
    +1213            access_rights["readable"] = False
    +1214
    +1215        try:
    +1216            temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)]))
    +1217            self.smbClient.createDirectory(self.smb_share, temp_dir)
    +1218            self.smbClient.deleteDirectory(self.smb_share, temp_dir)
    +1219            access_rights["writable"] = True
    +1220        except impacket.smbconnection.SessionError as e:
    +1221            access_rights["writable"] = False
    +1222
    +1223        # Restore the current share
    +1224        self.set_share(shareName=current_share)
    +1225
    +1226        return access_rights
    +1227
    +1228    # Setter / Getter
     1229
    -1230            if path in ["", ".", ".."]:
    -1231                self.smb_cwd = ""
    -1232            else:
    -1233                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
    -1234                    # Path exists on the remote 
    -1235                    self.smb_cwd = ntpath.normpath(path)
    -1236                else:
    -1237                    # Path does not exists or is not a directory on the remote 
    -1238                    print("[!] Remote directory '%s' does not exist." % path)
    +1230    def set_share(self, shareName):
    +1231        """
    +1232        Sets the current SMB share to the specified share name.
    +1233
    +1234        This method updates the SMB session to use the specified share name. It checks if the share name is valid
    +1235        and updates the smb_share attribute of the SMBSession instance.
    +1236
    +1237        Parameters:
    +1238            shareName (str): The name of the share to set as the current SMB share.
    +1239
    +1240        Raises:
    +1241            ValueError: If the shareName is None or an empty string.
    +1242        """
    +1243
    +1244        if shareName is not None:
    +1245            self.list_shares()
    +1246            if shareName.lower() in self.available_shares.keys():
    +1247                # Doing this in order to keep the case of the share adevertised by the remote machine
    +1248                self.smb_share = self.available_shares[shareName.lower()]["name"]
    +1249                self.smb_cwd = ""
    +1250                # Connects the tree
    +1251                self.smb_tree_id = self.smbClient.connectTree(self.smb_share)
    +1252            else:
    +1253                self.logger.error("Could not set share '%s', it does not exist remotely." % shareName)
    +1254        else:
    +1255            self.smb_share = None
    +1256            
    +1257    def set_cwd(self, path=None):
    +1258        """
    +1259        Sets the current working directory on the SMB share to the specified path.
    +1260
    +1261        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
    +1262        If the specified path is not a directory, the cwd remains unchanged.
    +1263
    +1264        Parameters:
    +1265            path (str): The path to set as the current working directory.
    +1266
    +1267        Raises:
    +1268            ValueError: If the specified path is not a directory.
    +1269        """
    +1270
    +1271        if path is not None:
    +1272            # Set path separators to ntpath sep 
    +1273            if '/' in path:
    +1274                path = path.replace('/', ntpath.sep)
    +1275
    +1276            if path.startswith(ntpath.sep):
    +1277                # Absolute path
    +1278                path = path + ntpath.sep
    +1279            else:
    +1280                # Relative path to the CWD
    +1281                if len(self.smb_cwd) == 0:
    +1282                    path = path + ntpath.sep
    +1283                else:
    +1284                    path = self.smb_cwd + ntpath.sep + path
    +1285            
    +1286            # Path normalization
    +1287            path = ntpath.normpath(path)
    +1288            path = re.sub(r'\\+', r'\\', path)
    +1289
    +1290            if path in ["", ".", ".."]:
    +1291                self.smb_cwd = ""
    +1292            else:
    +1293                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
    +1294                    # Path exists on the remote 
    +1295                    self.smb_cwd = ntpath.normpath(path)
    +1296                else:
    +1297                    # Path does not exists or is not a directory on the remote 
    +1298                    self.logger.error("Remote directory '%s' does not exist." % path)
     
    -

    Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections. -It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.

    +

    Represents an SMB session for interacting with an SMB server.

    + +

    This class provides methods to manage and interact with an SMB server, including +connecting to the server, listing shares, uploading and downloading files, and +managing directories and files on the server. It handles session initialization, +authentication, and cleanup.

    Attributes: - address (str): The IP address or hostname of the SMB server. - domain (str): The domain name for SMB server authentication. - username (str): The username for SMB server authentication. - password (str): The password for SMB server authentication. - lmhash (str): The LM hash of the user's password, if available. - nthash (str): The NT hash of the user's password, if available. - use_kerberos (bool): A flag to determine whether to use Kerberos for authentication. - kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication. - debug (bool): A flag to enable debug output. - smbClient (object): The SMB client object used for the connection. - connected (bool): A flag to check the status of the connection. + host (str): The hostname or IP address of the SMB server. + port (int): The port number on which the SMB server is listening. + credentials (dict): Authentication credentials for the SMB server. + config (dict, optional): Configuration options for the SMB session. + smbClient (impacket.smbconnection.SMBConnection): The SMB connection instance. + connected (bool): Connection status to the SMB server. + available_shares (dict): A dictionary of available SMB shares. smb_share (str): The current SMB share in use. - smb_path (str): The current path within the SMB share.

    + smb_cwd (str): The current working directory on the SMB share. + smb_tree_id (int): The tree ID of the connected SMB share.

    Methods: - __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False): - Initializes the SMBSession with the specified parameters. - init_smb_session(): - Initializes the SMB session by connecting to the server and authenticating using the specified method.

    + close_smb_session(): Closes the current SMB session. + init_smb_session(): Initializes the SMB session with the server. + list_shares(): Lists all shares available on the SMB server. + set_share(shareName): Sets the current SMB share. + set_cwd(path): Sets the current working directory on the SMB share. + put_file(localpath): Uploads a file to the current SMB share. + get_file(remotepath, localpath): Downloads a file from the SMB share. + mkdir(path): Creates a directory on the SMB share. + rmdir(path): Removes a directory from the SMB share. + rm(path): Removes a file from the SMB share. + read_file(path): Reads a file from the SMB share. + test_rights(sharename): Tests read and write access rights on a share.

    @@ -2683,38 +2800,35 @@

    - SMBSession( address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None) + SMBSession(host, port, credentials, config=None, logger=None)
    -
    48    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
    -49        super(SMBSession, self).__init__()
    -50        # Objects
    -51        self.config = config
    -52
    -53        # Target server
    -54        self.address = address
    -55
    -56        # Credentials
    -57        self.domain = domain
    -58        self.username = username
    -59        self.password = password 
    -60        self.lmhash = lmhash
    -61        self.nthash = nthash
    -62        self.use_kerberos = use_kerberos
    -63        self.kdcHost = kdcHost
    -64
    -65        self.smbClient = None
    -66        self.connected = False
    +            
    57    def __init__(self, host, port, credentials, config=None, logger=None):
    +58        super(SMBSession, self).__init__()
    +59        # Objects
    +60        self.config = config
    +61        self.logger = logger
    +62
    +63        # Target server
    +64        self.host = host
    +65        # Target port (by default on 445)
    +66        self.port = port
     67
    -68        self.available_shares = {}
    -69        self.smb_share = None
    -70        self.smb_cwd = ""
    -71        self.smb_tree_id = None
    -72
    -73        self.list_shares()
    +68        # Credentials
    +69        self.credentials = credentials
    +70
    +71        self.smbClient = None
    +72        self.connected = False
    +73
    +74        self.available_shares = {}
    +75        self.smb_share = None
    +76        self.smb_cwd = ""
    +77        self.smb_tree_id = None
    +78
    +79        self.list_shares()
     
    @@ -2732,90 +2846,46 @@

    -
    -
    - address - - -
    - - - - -
    -
    -
    - domain - - -
    - - - - -
    -
    -
    - username - - -
    - - - - -
    -
    +
    - password + logger
    - +
    -
    +
    - lmhash + host
    - +
    -
    +
    - nthash + port
    - +
    -
    +
    - use_kerberos + credentials
    - - - - -
    -
    -
    - kdcHost - - -
    - + @@ -2886,185 +2956,195 @@

    -
    - +
    +
    def - init_smb_session(self): + close_smb_session(self): - +
    - -
     77    def init_smb_session(self):
    - 78        """
    - 79        Initializes and establishes a session with the SMB server.
    - 80
    - 81        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
    - 82        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
    - 83
    - 84        The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True.
    - 85
    - 86        Returns:
    - 87            bool: True if the connection and authentication are successful, False otherwise.
    - 88        """
    - 89
    - 90        self.connected = False
    - 91
    - 92        if self.config.debug:
    - 93            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
    - 94        try:
    - 95            self.smbClient = impacket.smbconnection.SMBConnection(
    - 96                remoteName=self.address,
    - 97                remoteHost=self.address,
    - 98                sess_port=int(445)
    - 99            )
    -100        except OSError as err:
    -101            print("[!] %s" % err)
    -102            self.smbClient = None
    -103
    -104        if self.smbClient is not None:
    -105            if self.use_kerberos:
    -106                if self.config.debug:
    -107                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
    -108                try:
    -109                    self.connected = self.smbClient.kerberosLogin(
    -110                        user=self.username,
    -111                        password=self.password,
    -112                        domain=self.domain,
    -113                        lmhash=self.lmhash,
    -114                        nthash=self.nthash,
    -115                        aesKey=self.aesKey,
    -116                        kdcHost=self.kdcHost
    -117                    )
    -118                except impacket.smbconnection.SessionError as err:
    -119                    if self.config.debug:
    -120                        traceback.print_exc()
    -121                    print("[!] Could not login: %s" % err)
    -122                    self.connected = False
    -123
    -124            else:
    -125                if self.config.debug:
    -126                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
    -127                try:
    -128                    self.connected = self.smbClient.login(
    -129                        user=self.username,
    -130                        password=self.password,
    -131                        domain=self.domain,
    -132                        lmhash=self.lmhash,
    -133                        nthash=self.nthash
    -134                    )
    -135                except impacket.smbconnection.SessionError as err:
    -136                    if self.config.debug:
    -137                        traceback.print_exc()
    -138                    print("[!] Could not login: %s" % err)
    -139                    self.connected = False
    -140
    -141            if self.connected:
    -142                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
    -143            else:
    -144                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
    -145
    -146        return self.connected
    +    
    +            
     83    def close_smb_session(self):
    + 84        """
    + 85        Closes the current SMB session by disconnecting the SMB client.
    + 86
    + 87        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
    + 88        and if so, it closes the connection and resets the connection status.
    + 89
    + 90        Raises:
    + 91            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
    + 92        """
    + 93
    + 94        if self.smbClient is not None:
    + 95            if self.connected:
    + 96                self.smbClient.close()
    + 97                self.connected = False
    + 98                self.logger.debug("[+] SMB connection closed successfully.")
    + 99            else:
    +100                self.logger.debug("[!] No active SMB connection to close.")
    +101        else:
    +102            raise Exception("SMB client is not initialized.")
     
    -

    Initializes and establishes a session with the SMB server.

    - -

    This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. -It attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.

    +

    Closes the current SMB session by disconnecting the SMB client.

    -

    The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.

    +

    This method ensures that the SMB client connection is properly closed. It checks if the client is connected +and if so, it closes the connection and resets the connection status.

    -

    Returns: - bool: True if the connection and authentication are successful, False otherwise.

    +

    Raises: + Exception: If the SMB client is not initialized or if there's an error during the disconnection process.

    -
    - +
    +
    def - close_smb_session(self): + init_smb_session(self): - +
    - -
    148    def close_smb_session(self):
    -149        """
    -150        Closes the current SMB session by disconnecting the SMB client.
    -151
    -152        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
    -153        and if so, it closes the connection and resets the connection status.
    -154
    -155        Raises:
    -156            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
    -157        """
    -158
    -159        if self.smbClient is not None:
    -160            if self.connected:
    -161                self.smbClient.close()
    -162                self.connected = False
    -163                if self.config.debug:
    -164                    print("[+] SMB connection closed successfully.")
    -165            else:
    -166                if self.config.debug:
    -167                    print("[!] No active SMB connection to close.")
    -168        else:
    -169            raise Exception("SMB client is not initialized.")
    +    
    +            
    104    def init_smb_session(self):
    +105        """
    +106        Initializes and establishes a session with the SMB server.
    +107
    +108        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
    +109        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
    +110
    +111        The method will print debug information if the `debug` attribute is set to True. Upon successful connection and authentication, it sets the `connected` attribute to True.
    +112
    +113        Returns:
    +114            bool: True if the connection and authentication are successful, False otherwise.
    +115        """
    +116
    +117        self.connected = False
    +118
    +119        self.logger.debug("[>] Connecting to remote SMB server '%s' ... " % self.host)
    +120        
    +121        try:
    +122            if is_port_open(self.host, self.port):
    +123                self.smbClient = impacket.smbconnection.SMBConnection(
    +124                    remoteName=self.host,
    +125                    remoteHost=self.host,
    +126                    sess_port=int(self.port)
    +127                )
    +128            else:
    +129                self.connected = False
    +130        except OSError as err:
    +131            if self.config.debug:
    +132                traceback.print_exc()
    +133            self.logger.error(err)
    +134            self.smbClient = None
    +135
    +136        if self.smbClient is not None:
    +137            if self.credentials.use_kerberos:
    +138                self.logger.debug("[>] Authenticating as '%s\\%s' with kerberos ... " % (self.credentials.domain, self.credentials.username))
    +139                try:
    +140                    self.connected = self.smbClient.kerberosLogin(
    +141                        user=self.credentials.username,
    +142                        password=self.credentials.password,
    +143                        domain=self.credentials.domain,
    +144                        lmhash=self.credentials.lm_hex,
    +145                        nthash=self.credentials.nt_hex,
    +146                        aesKey=self.credentials.aesKey,
    +147                        kdcHost=self.credentials.kdcHost
    +148                    )
    +149                except impacket.smbconnection.SessionError as err:
    +150                    if self.config.debug:
    +151                        traceback.print_exc()
    +152                    self.logger.error("Could not login: %s" % err)
    +153                    self.connected = False
    +154
    +155            else:
    +156                self.logger.debug("[>] Authenticating as '%s\\%s' with NTLM ... " % (self.credentials.domain, self.credentials.username))
    +157                
    +158                try:
    +159                    self.connected = self.smbClient.login(
    +160                        user=self.credentials.username,
    +161                        password=self.credentials.password,
    +162                        domain=self.credentials.domain,
    +163                        lmhash=self.credentials.lm_hex,
    +164                        nthash=self.credentials.nt_hex
    +165                    )
    +166                except impacket.smbconnection.SessionError as err:
    +167                    if self.config.debug:
    +168                        traceback.print_exc()
    +169                    self.logger.error("Could not login: %s" % err)
    +170                    self.connected = False
    +171
    +172            if self.connected:
    +173                self.logger.print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username))
    +174            else:
    +175                self.logger.error("Failed to authenticate to '%s' as '%s\\%s'!" % (self.host, self.credentials.domain, self.credentials.username))
    +176
    +177        return self.connected
     
    -

    Closes the current SMB session by disconnecting the SMB client.

    +

    Initializes and establishes a session with the SMB server.

    -

    This method ensures that the SMB client connection is properly closed. It checks if the client is connected -and if so, it closes the connection and resets the connection status.

    +

    This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration. +It attempts to connect to the SMB server specified by the address attribute and authenticate using the credentials provided during the object's initialization.

    -

    Raises: - Exception: If the SMB client is not initialized or if there's an error during the disconnection process.

    +

    The method will print debug information if the debug attribute is set to True. Upon successful connection and authentication, it sets the connected attribute to True.

    + +

    Returns: + bool: True if the connection and authentication are successful, False otherwise.

    -
    - +
    +
    def - read_file(self, path=None): + ping_smb_session(self): - +
    - -
    173    def read_file(self, path=None):
    -174        if self.path_isfile(path=path):
    -175            tmp_file_path = self.smb_cwd + ntpath.sep + path
    -176            matches = self.smbClient.listPath(
    -177                shareName=self.smb_share, 
    -178                path=tmp_file_path
    -179            )
    -180
    -181            fh = io.BytesIO()
    -182            try:
    -183                # opening the files in streams instead of mounting shares allows 
    -184                # for running the script from unprivileged containers
    -185                self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write)
    -186            except impacket.smbconnection.SessionError as e:
    -187                return None
    -188            rawdata = fh.getvalue()
    -189            fh.close()
    -190            return rawdata
    -191        else:
    -192            print("[!] Remote path '%s' is not a file." % path)
    +    
    +            
    179    def ping_smb_session(self):
    +180        """
    +181        Tests the connectivity to the SMB server by sending an echo command.
    +182
    +183        This method attempts to send an echo command to the SMB server to check if the session is still active.
    +184        It updates the `connected` attribute of the class based on the success or failure of the echo command.
    +185
    +186        Returns:
    +187            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
    +188        """
    +189
    +190        if not is_port_open(self.host, self.port):
    +191            self.connected = False
    +192        else:
    +193            try:
    +194                self.smbClient.getSMBServer().echo()
    +195            except Exception as e:
    +196                self.connected = False
    +197
    +198        return self.connected
     
    - +

    Tests the connectivity to the SMB server by sending an echo command.

    + +

    This method attempts to send an echo command to the SMB server to check if the session is still active. +It updates the connected attribute of the class based on the success or failure of the echo command.

    + +

    Returns: + bool: True if the echo command succeeds (indicating the session is active), False otherwise.

    +
    +
    @@ -3078,57 +3158,85 @@

    -
    194    def find(self, paths=[], callback=None):
    -195        def recurse_action(paths=[], depth=0, callback=None):
    -196            if callback is None:
    -197                return []
    -198            
    -199            next_directories_to_explore = []
    -200
    -201            for path in paths:
    -202                remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    -203                entries = []
    -204                
    -205                try:
    -206                    entries = self.smbClient.listPath(
    -207                        shareName=self.smb_share, 
    -208                        path=(remote_smb_path + ntpath.sep + '*')
    -209                    )
    -210                except impacket.smbconnection.SessionError as err:
    -211                    continue 
    -212                # Remove dot names
    -213                entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    -214                # Sort the entries ignoring case
    -215                entries = sorted(entries, key=lambda x:x.get_longname().lower())
    -216                
    -217                for entry in entries:
    -218                    if entry.is_directory():
    -219                        callback(entry, path + ntpath.sep + entry.get_longname() + ntpath.sep, depth)
    -220                    else:
    -221                        callback(entry, path + ntpath.sep + entry.get_longname(), depth)
    -222
    -223                # Next directories to explore
    -224                for entry in entries:
    -225                    if entry.is_directory():
    -226                        next_directories_to_explore.append(path + ntpath.sep + entry.get_longname() + ntpath.sep)
    -227            
    -228            return next_directories_to_explore
    -229        # 
    -230        if callback is not None:
    -231            depth = 0
    -232            while len(paths) != 0:
    -233                paths = recurse_action(
    -234                    paths=paths,
    -235                    depth=depth,
    -236                    callback=callback
    -237                )
    -238                depth = depth + 1
    -239        else:
    -240            print("[!] SMBSession.find(), callback function cannot be None.")
    +            
    202    def find(self, paths=[], callback=None):
    +203        """
    +204        Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry.
    +205
    +206        This method traverses the specified paths on the SMB share, recursively exploring directories and invoking the callback
    +207        function on each file or directory found. The callback function is called with three arguments: the entry object, the
    +208        full path of the entry, and the current depth of recursion.
    +209
    +210        Args:
    +211            paths (list, optional): A list of paths to start the search from. Defaults to an empty list.
    +212            callback (function, optional): A function to be called on each entry found. The function should accept three arguments:
    +213                                           the entry object, the full path of the entry, and the current depth of recursion. Defaults to None.
    +214
    +215        Note:
    +216            If the callback function is None, the method will print an error message and return without performing any action.
    +217        """
    +218
    +219        def recurse_action(paths=[], depth=0, callback=None):
    +220            if callback is None:
    +221                return []
    +222            
    +223            next_directories_to_explore = []
    +224
    +225            for path in paths:
    +226                remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    +227                entries = []
    +228                
    +229                try:
    +230                    entries = self.smbClient.listPath(
    +231                        shareName=self.smb_share, 
    +232                        path=(remote_smb_path + ntpath.sep + '*')
    +233                    )
    +234                except impacket.smbconnection.SessionError as err:
    +235                    continue 
    +236                # Remove dot names
    +237                entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    +238                # Sort the entries ignoring case
    +239                entries = sorted(entries, key=lambda x:x.get_longname().lower())
    +240                
    +241                for entry in entries:
    +242                    if entry.is_directory():
    +243                        fullpath = path + ntpath.sep + entry.get_longname() + ntpath.sep
    +244                        next_directories_to_explore.append(fullpath)
    +245                    else:
    +246                        fullpath = path + ntpath.sep + entry.get_longname()
    +247                    fullpath = re.sub(r'\\\\+', r'\\', fullpath)
    +248                    callback(entry, fullpath, depth)
    +249                     
    +250            return next_directories_to_explore
    +251        # 
    +252        if callback is not None:
    +253            depth = 0
    +254            while len(paths) != 0:
    +255                paths = recurse_action(
    +256                    paths=paths,
    +257                    depth=depth,
    +258                    callback=callback
    +259                )
    +260                depth = depth + 1
    +261        else:
    +262            self.logger.error("SMBSession.find(), callback function cannot be None.")
     
    - +

    Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry.

    + +

    This method traverses the specified paths on the SMB share, recursively exploring directories and invoking the callback +function on each file or directory found. The callback function is called with three arguments: the entry object, the +full path of the entry, and the current depth of recursion.

    + +

    Args: + paths (list, optional): A list of paths to start the search from. Defaults to an empty list. + callback (function, optional): A function to be called on each entry found. The function should accept three arguments: + the entry object, the full path of the entry, and the current depth of recursion. Defaults to None.

    + +

    Note: + If the callback function is None, the method will print an error message and return without performing any action.

    +
    +
    @@ -3142,81 +3250,80 @@

    -
    242    def get_file(self, path=None, keepRemotePath=False):
    -243        """
    -244        Retrieves a file from the specified path on the SMB share.
    -245
    -246        This method attempts to retrieve a file from the given path within the currently connected SMB share.
    -247        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
    -248        file object and writing the contents of the remote file to it using the SMB client's getFile method.
    -249
    -250        Parameters:
    -251            path (str): The path of the file to retrieve. If None, uses the current smb_path.
    -252
    -253        Returns:
    -254            None
    -255        """
    -256
    -257        # Parse path
    -258        path = path.replace('/', ntpath.sep)
    -259        if ntpath.sep in path:
    -260            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    -261        else:
    -262            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    -263        # Parse filename
    -264        filename = ntpath.basename(path)
    -265
    -266        # Search for the file
    -267        matches = self.smbClient.listPath(
    -268            shareName=self.smb_share, 
    -269            path=tmp_search_path + ntpath.sep + '*'
    -270        )   
    +            
    264    def get_file(self, path=None, keepRemotePath=False):
    +265        """
    +266        Retrieves a file from the specified path on the SMB share.
    +267
    +268        This method attempts to retrieve a file from the given path within the currently connected SMB share.
    +269        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
    +270        file object and writing the contents of the remote file to it using the SMB client's getFile method.
     271
    -272        # Filter the entries
    -273        matching_entries = []
    -274        for entry in matches:
    -275            if entry.is_directory():
    -276                # Skip directories
    -277                continue
    -278            if entry.get_longname() == filename:
    -279                matching_entries.append(entry)
    -280            elif '*' in filename:
    -281                regexp = filename.replace('.', '\\.').replace('*', '.*')
    -282                if re.match(regexp, entry.get_longname()):
    -283                    matching_entries.append(entry)
    -284        
    -285        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    -286
    -287        for entry in matching_entries:
    -288            if entry.is_directory():
    -289                if self.config.debug:
    -290                    print("[debug] [>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname()))
    -291            else:
    -292                try:
    -293                    if ntpath.sep in path:
    -294                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
    -295                    else:
    -296                        outputfile = entry.get_longname()
    -297                    f = LocalFileIO(
    -298                        mode="wb", 
    -299                        path=outputfile,
    -300                        expected_size=entry.get_filesize(), 
    -301                        debug=self.config.debug,
    -302                        keepRemotePath=keepRemotePath
    -303                    )
    -304                    self.smbClient.getFile(
    -305                        shareName=self.smb_share, 
    -306                        pathName=tmp_search_path + ntpath.sep + entry.get_longname(), 
    -307                        callback=f.write
    -308                    )
    -309                    f.close()
    -310                except (BrokenPipeError, KeyboardInterrupt) as e:
    -311                    f.close()
    -312                    print("\x1b[v\x1b[o\r[!] Interrupted.")
    -313                    self.close_smb_session()
    -314                    self.init_smb_session()
    -315                        
    -316        return None
    +272        Parameters:
    +273            path (str): The path of the file to retrieve. If None, uses the current smb_path.
    +274
    +275        Returns:
    +276            None
    +277        """
    +278
    +279        # Parse path
    +280        path = path.replace('/', ntpath.sep)
    +281        if ntpath.sep in path:
    +282            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    +283        else:
    +284            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    +285        # Parse filename
    +286        filename = ntpath.basename(path)
    +287
    +288        # Search for the file
    +289        matches = self.smbClient.listPath(
    +290            shareName=self.smb_share, 
    +291            path=tmp_search_path + ntpath.sep + '*'
    +292        )   
    +293
    +294        # Filter the entries
    +295        matching_entries = []
    +296        for entry in matches:
    +297            if entry.is_directory():
    +298                # Skip directories
    +299                continue
    +300            if entry.get_longname() == filename:
    +301                matching_entries.append(entry)
    +302            elif '*' in filename:
    +303                regexp = filename.replace('.', '\\.').replace('*', '.*')
    +304                if re.match(regexp, entry.get_longname()):
    +305                    matching_entries.append(entry)
    +306        
    +307        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    +308
    +309        for entry in matching_entries:
    +310            if entry.is_directory():
    +311                self.logger.debug("[>] Skipping '%s' because it is a directory." % (tmp_search_path + ntpath.sep + entry.get_longname()))
    +312            else:
    +313                try:
    +314                    if ntpath.sep in path:
    +315                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
    +316                    else:
    +317                        outputfile = entry.get_longname()
    +318                    f = LocalFileIO(
    +319                        mode="wb", 
    +320                        path=outputfile,
    +321                        expected_size=entry.get_filesize(), 
    +322                        debug=self.config.debug,
    +323                        keepRemotePath=keepRemotePath
    +324                    )
    +325                    self.smbClient.getFile(
    +326                        shareName=self.smb_share, 
    +327                        pathName=tmp_search_path + ntpath.sep + entry.get_longname(), 
    +328                        callback=f.write
    +329                    )
    +330                    f.close()
    +331                except (BrokenPipeError, KeyboardInterrupt) as e:
    +332                    f.close()
    +333                    print("\x1b[v\x1b[o\r[!] Interrupted.")
    +334                    self.close_smb_session()
    +335                    self.init_smb_session()
    +336                        
    +337        return None
     
    @@ -3246,79 +3353,79 @@

    -
    318    def get_file_recursively(self, path=None):
    -319        """
    -320        Recursively retrieves files from a specified path on the SMB share.
    -321
    -322        This method navigates through all directories starting from the given path,
    -323        and downloads all files found. It handles directories recursively, ensuring
    -324        that all nested files are retrieved. The method skips over directory entries
    -325        and handles errors gracefully, attempting to continue the operation where possible.
    -326
    -327        Parameters:
    -328            path (str): The initial directory path from which to start the recursive file retrieval.
    -329                        If None, it starts from the root of the configured SMB share.
    -330        """
    -331        
    -332        def recurse_action(base_dir="", path=[]):
    -333            if len(base_dir) == 0:
    -334                remote_smb_path = ntpath.sep.join(path)
    -335            else:
    -336                remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path)
    -337            remote_smb_path = ntpath.normpath(remote_smb_path)
    -338
    -339            entries = self.smbClient.listPath(
    -340                shareName=self.smb_share, 
    -341                path=remote_smb_path + '\\*'
    -342            )
    -343            if len(entries) != 0:
    -344                files = [entry for entry in entries if not entry.is_directory()]
    -345                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
    -346
    -347                # Files
    -348                if len(files) != 0:
    -349                    print("[>] Retrieving files of '%s'" % remote_smb_path)
    -350                for entry_file in files:
    -351                    if not entry_file.is_directory():
    -352                        f = LocalFileIO(
    -353                            mode="wb",
    -354                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    -355                            expected_size=entry_file.get_filesize(),
    -356                            keepRemotePath=True,
    -357                            debug=self.config.debug
    -358                        )
    -359                        try:
    -360                            self.smbClient.getFile(
    -361                                shareName=self.smb_share, 
    -362                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    -363                                callback=f.write
    -364                            )
    -365                            f.close()
    -366                        except BrokenPipeError as err:
    -367                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    -368                            f.close(remove=True)
    -369                            break
    -370                        except Exception as err:
    -371                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    -372                            f.close(remove=True)
    -373                
    -374                # Directories
    -375                for entry_directory in directories:
    -376                    if entry_directory.is_directory():
    -377                        recurse_action(
    -378                            base_dir=self.smb_cwd, 
    -379                            path=path+[entry_directory.get_longname()]
    -380                        )                   
    -381        # Entrypoint
    -382        try:
    -383            recurse_action(
    -384                base_dir=self.smb_cwd, 
    -385                path=[path]
    -386            )
    -387        except (BrokenPipeError, KeyboardInterrupt) as e:
    -388            print("\x1b[v\x1b[o\r[!] Interrupted.")
    -389            self.close_smb_session()
    -390            self.init_smb_session()
    +            
    339    def get_file_recursively(self, path=None):
    +340        """
    +341        Recursively retrieves files from a specified path on the SMB share.
    +342
    +343        This method navigates through all directories starting from the given path,
    +344        and downloads all files found. It handles directories recursively, ensuring
    +345        that all nested files are retrieved. The method skips over directory entries
    +346        and handles errors gracefully, attempting to continue the operation where possible.
    +347
    +348        Parameters:
    +349            path (str): The initial directory path from which to start the recursive file retrieval.
    +350                        If None, it starts from the root of the configured SMB share.
    +351        """
    +352        
    +353        def recurse_action(base_dir="", path=[]):
    +354            if len(base_dir) == 0:
    +355                remote_smb_path = ntpath.sep.join(path)
    +356            else:
    +357                remote_smb_path = base_dir + ntpath.sep + ntpath.sep.join(path)
    +358            remote_smb_path = ntpath.normpath(remote_smb_path)
    +359
    +360            entries = self.smbClient.listPath(
    +361                shareName=self.smb_share, 
    +362                path=remote_smb_path + '\\*'
    +363            )
    +364            if len(entries) != 0:
    +365                files = [entry for entry in entries if not entry.is_directory()]
    +366                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
    +367
    +368                # Files
    +369                if len(files) != 0:
    +370                    self.logger.print("[>] Retrieving files of '%s'" % remote_smb_path)
    +371                for entry_file in files:
    +372                    if not entry_file.is_directory():
    +373                        f = LocalFileIO(
    +374                            mode="wb",
    +375                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    +376                            expected_size=entry_file.get_filesize(),
    +377                            keepRemotePath=True,
    +378                            debug=self.config.debug
    +379                        )
    +380                        try:
    +381                            self.smbClient.getFile(
    +382                                shareName=self.smb_share, 
    +383                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
    +384                                callback=f.write
    +385                            )
    +386                            f.close()
    +387                        except BrokenPipeError as err:
    +388                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    +389                            f.close(remove=True)
    +390                            break
    +391                        except Exception as err:
    +392                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
    +393                            f.close(remove=True)
    +394                
    +395                # Directories
    +396                for entry_directory in directories:
    +397                    if entry_directory.is_directory():
    +398                        recurse_action(
    +399                            base_dir=self.smb_cwd, 
    +400                            path=path+[entry_directory.get_longname()]
    +401                        )                   
    +402        # Entrypoint
    +403        try:
    +404            recurse_action(
    +405                base_dir=self.smb_cwd, 
    +406                path=[path]
    +407            )
    +408        except (BrokenPipeError, KeyboardInterrupt) as e:
    +409            print("\x1b[v\x1b[o\r[!] Interrupted.")
    +410            self.close_smb_session()
    +411            self.init_smb_session()
     
    @@ -3347,29 +3454,31 @@

    -
    392    def get_entry(self, path=None):
    -393        """
    -394        Retrieves information about a specific entry located at the provided path on the SMB share.
    -395
    -396        This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.
    -397
    -398        Args:
    -399            path (str): The path of the entry to retrieve information about.
    -400
    -401        Returns:
    -402            Entry: An object representing the entry at the specified path, or None if the entry is not found.
    -403        """
    -404
    -405        if self.path_exists(path=path):
    -406            matches = self.smbClient.listPath(shareName=self.smb_share, path=path)
    -407
    -408            if len(matches) == 1:
    -409                return matches[0]
    -410            else:
    -411                return None
    -412            
    -413        else:
    -414            return None 
    +            
    413    def get_entry(self, path=None):
    +414        """
    +415        Retrieves information about a specific entry located at the provided path on the SMB share.
    +416
    +417        This method checks if the specified path exists on the SMB share. If the path exists, it retrieves the details of the entry at that path, including the directory name and file name. If the entry is found, it returns the entry object; otherwise, it returns None.
    +418
    +419        Args:
    +420            path (str): The path of the entry to retrieve information about.
    +421
    +422        Returns:
    +423            Entry: An object representing the entry at the specified path, or None if the entry is not found.
    +424        """
    +425
    +426        if self.path_exists(path=path):
    +427            matches = self.smbClient.listPath(
    +428                shareName=self.smb_share,
    +429                path=path
    +430            )
    +431
    +432            if len(matches) == 1:
    +433                return matches[0]
    +434            else:
    +435                return None
    +436        else:
    +437            return None 
     
    @@ -3397,80 +3506,80 @@

    -
    416    def info(self, share=True, server=True):
    -417        """
    -418        Displays information about the server and optionally the shares.
    -419
    -420        This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share.
    -421
    -422        Parameters:
    -423            share (bool): If True, display information about the current share.
    -424            server (bool): If True, display information about the server.
    -425
    -426        Returns:
    -427            None
    -428        """
    -429
    -430        if server:
    -431            if self.config.no_colors:
    -432                print("[+] Server:")
    -433                print("  ├─NetBIOS:")
    -434                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
    -435                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
    -436                print("  ├─DNS:")
    -437                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
    -438                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
    -439                print("  ├─OS:")
    -440                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
    -441                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    -442                print("  ├─Server:")
    -443                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
    -444                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
    -445                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
    -446                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    -447                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
    -448                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    -449                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    -450                print("  └─")
    -451            else:
    -452                print("[+] Server:")
    -453                print("  ├─NetBIOS:")
    -454                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
    -455                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
    -456                print("  ├─DNS:")
    -457                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
    -458                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
    -459                print("  ├─OS:")
    -460                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
    -461                print("  │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    -462                print("  ├─Server:")
    -463                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
    -464                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
    -465                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
    -466                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    -467                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
    -468                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    -469                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    -470                print("  └─")
    -471
    -472        if share and self.smb_share is not None:
    -473            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
    -474            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
    -475            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
    -476            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
    -477            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
    -478            if self.config.no_colors:
    -479                print("\n[+] Share:")
    -480                print("  ├─ Name ──────────── : %s" % (share_name))
    -481                print("  ├─ Description ───── : %s" % (share_comment))
    -482                print("  ├─ Type ──────────── : %s" % (share_type))
    -483                print("  └─ Raw type value ── : %s" % (share_rawtype))
    -484            else:
    -485                print("\n[+] Share:")
    -486                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
    -487                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
    -488                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
    -489                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
    +            
    439    def info(self, share=True, server=True):
    +440        """
    +441        Displays information about the server and optionally the shares.
    +442
    +443        This method prints detailed information about the server's characteristics such as NetBIOS names, DNS details, OS information, and SMB capabilities. If the `share` parameter is set to True and a share is currently set, it will also attempt to display information about the share.
    +444
    +445        Parameters:
    +446            share (bool): If True, display information about the current share.
    +447            server (bool): If True, display information about the server.
    +448
    +449        Returns:
    +450            None
    +451        """
    +452
    +453        if server:
    +454            if self.config.no_colors:
    +455                self.logger.print("[+] Server:")
    +456                self.logger.print("  ├─NetBIOS:")
    +457                self.logger.print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
    +458                self.logger.print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
    +459                self.logger.print("  ├─DNS:")
    +460                self.logger.print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
    +461                self.logger.print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
    +462                self.logger.print("  ├─OS:")
    +463                self.logger.print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
    +464                self.logger.print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    +465                self.logger.print("  ├─Server:")
    +466                self.logger.print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
    +467                self.logger.print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
    +468                self.logger.print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
    +469                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    +470                self.logger.print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
    +471                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    +472                self.logger.print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    +473                self.logger.print("  └─")
    +474            else:
    +475                self.logger.print("[+] Server:")
    +476                self.logger.print("  ├─NetBIOS:")
    +477                self.logger.print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
    +478                self.logger.print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
    +479                self.logger.print("  ├─DNS:")
    +480                self.logger.print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
    +481                self.logger.print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
    +482                self.logger.print("  ├─OS:")
    +483                self.logger.print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
    +484                self.logger.print("  │ └─ \x1b[94mOS Version\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s.%s.%s\x1b[0m" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
    +485                self.logger.print("  ├─Server:")
    +486                self.logger.print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
    +487                self.logger.print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
    +488                self.logger.print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
    +489                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
    +490                self.logger.print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
    +491                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
    +492                self.logger.print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
    +493                self.logger.print("  └─")
    +494
    +495        if share and self.smb_share is not None:
    +496            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
    +497            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
    +498            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
    +499            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
    +500            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
    +501            if self.config.no_colors:
    +502                self.logger.print("\n[+] Share:")
    +503                self.logger.print("  ├─ Name ──────────── : %s" % (share_name))
    +504                self.logger.print("  ├─ Description ───── : %s" % (share_comment))
    +505                self.logger.print("  ├─ Type ──────────── : %s" % (share_type))
    +506                self.logger.print("  └─ Raw type value ── : %s" % (share_rawtype))
    +507            else:
    +508                self.logger.print("\n[+] Share:")
    +509                self.logger.print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
    +510                self.logger.print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
    +511                self.logger.print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
    +512                self.logger.print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
     
    @@ -3499,37 +3608,37 @@

    -
    491    def list_contents(self, path=None):
    -492        """
    -493        Lists the contents of a specified directory on the SMB share.
    -494
    -495        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
    -496        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
    -497        the long names of the files and directories as keys and their respective SMB entry objects as values.
    -498
    -499        Args:
    -500            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
    -501            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
    -502
    -503        Returns:
    -504            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
    -505        """
    -506        
    -507        dest_path = [self.smb_cwd.rstrip(ntpath.sep),]
    -508        if path is not None and len(path) > 0:
    -509            dest_path.append(path.rstrip(ntpath.sep))
    -510        dest_path.append('*')
    -511        path = ntpath.sep.join(dest_path)
    -512
    -513        contents = {}
    -514        entries = self.smbClient.listPath(
    -515            shareName=self.smb_share, 
    -516            path=path
    -517        )
    -518        for entry in entries:
    -519            contents[entry.get_longname()] = entry
    -520
    -521        return contents
    +            
    514    def list_contents(self, path=None):
    +515        """
    +516        Lists the contents of a specified directory on the SMB share.
    +517
    +518        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
    +519        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
    +520        the long names of the files and directories as keys and their respective SMB entry objects as values.
    +521
    +522        Args:
    +523            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
    +524            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
    +525
    +526        Returns:
    +527            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
    +528        """
    +529        
    +530        dest_path = [self.smb_cwd.rstrip(ntpath.sep),]
    +531        if path is not None and len(path) > 0:
    +532            dest_path.append(path.rstrip(ntpath.sep))
    +533        dest_path.append('*')
    +534        path = ntpath.sep.join(dest_path)
    +535
    +536        contents = {}
    +537        entries = self.smbClient.listPath(
    +538            shareName=self.smb_share, 
    +539            path=path
    +540        )
    +541        for entry in entries:
    +542            contents[entry.get_longname()] = entry
    +543
    +544        return contents
     
    @@ -3560,41 +3669,41 @@

    -
    523    def list_shares(self):
    -524        """
    -525        Lists all the shares available on the connected SMB server.
    -526
    -527        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
    -528        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
    -529        such as its name, type, raw type, and any comments associated with the share.
    -530
    -531        Returns:
    -532            dict: A dictionary containing information about each share available on the server.
    -533        """
    -534
    -535        self.available_shares = {}
    -536
    -537        if self.connected:
    -538            if self.smbClient is not None:
    -539                resp = self.smbClient.listShares()
    -540
    -541                for share in resp:
    -542                    # SHARE_INFO_1 structure (lmshare.h)
    -543                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
    -544                    sharename = share["shi1_netname"][:-1]
    -545                    sharecomment = share["shi1_remark"][:-1]
    -546                    sharetype = share["shi1_type"]
    -547
    -548                    self.available_shares[sharename.lower()] = {
    -549                        "name": sharename, 
    -550                        "type": STYPE_MASK(sharetype), 
    -551                        "rawtype": sharetype, 
    -552                        "comment": sharecomment
    -553                    }
    -554            else:
    -555                print("[!] Error: SMBSession.smbClient is None.")
    -556
    -557        return self.available_shares
    +            
    546    def list_shares(self):
    +547        """
    +548        Lists all the shares available on the connected SMB server.
    +549
    +550        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
    +551        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
    +552        such as its name, type, raw type, and any comments associated with the share.
    +553
    +554        Returns:
    +555            dict: A dictionary containing information about each share available on the server.
    +556        """
    +557
    +558        self.available_shares = {}
    +559
    +560        if self.connected:
    +561            if self.smbClient is not None:
    +562                resp = self.smbClient.listShares()
    +563
    +564                for share in resp:
    +565                    # SHARE_INFO_1 structure (lmshare.h)
    +566                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
    +567                    sharename = share["shi1_netname"][:-1]
    +568                    sharecomment = share["shi1_remark"][:-1]
    +569                    sharetype = share["shi1_type"]
    +570
    +571                    self.available_shares[sharename.lower()] = {
    +572                        "name": sharename, 
    +573                        "type": STYPE_MASK(sharetype), 
    +574                        "rawtype": sharetype, 
    +575                        "comment": sharecomment
    +576                    }
    +577            else:
    +578                self.logger.error("Error: SMBSession.smbClient is None.")
    +579
    +580        return self.available_shares
     
    @@ -3621,49 +3730,49 @@

    -
    559    def mkdir(self, path=None):
    -560        """
    -561        Creates a directory at the specified path on the SMB share.
    -562
    -563        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
    -564        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
    -565        the creation for that directory without raising an error.
    -566
    -567        Args:
    -568            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
    -569
    -570        Note:
    -571            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
    -572        """
    -573
    -574        if path is not None:
    -575            # Prepare path
    -576            path = path.replace('/',ntpath.sep)
    -577            if ntpath.sep in path:
    -578                path = path.strip(ntpath.sep).split(ntpath.sep)
    -579            else:
    -580                path = [path]
    -581
    -582            # Create each dir in the path
    -583            for depth in range(1, len(path)+1):
    -584                tmp_path = ntpath.sep.join(path[:depth])
    -585                try:
    -586                    self.smbClient.createDirectory(
    -587                        shareName=self.smb_share, 
    -588                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
    -589                    )
    -590                except impacket.smbconnection.SessionError as err:
    -591                    if err.getErrorCode() == 0xc0000035:
    -592                        # STATUS_OBJECT_NAME_COLLISION
    -593                        # Remote directory already created, this is normal
    -594                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
    -595                        pass
    -596                    else:
    -597                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
    -598                        if self.config.debug:
    -599                            traceback.print_exc()
    -600        else:
    -601            pass
    +            
    582    def mkdir(self, path=None):
    +583        """
    +584        Creates a directory at the specified path on the SMB share.
    +585
    +586        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
    +587        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
    +588        the creation for that directory without raising an error.
    +589
    +590        Args:
    +591            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
    +592
    +593        Note:
    +594            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
    +595        """
    +596
    +597        if path is not None:
    +598            # Prepare path
    +599            path = path.replace('/',ntpath.sep)
    +600            if ntpath.sep in path:
    +601                path = path.strip(ntpath.sep).split(ntpath.sep)
    +602            else:
    +603                path = [path]
    +604
    +605            # Create each dir in the path
    +606            for depth in range(1, len(path)+1):
    +607                tmp_path = ntpath.sep.join(path[:depth])
    +608                try:
    +609                    self.smbClient.createDirectory(
    +610                        shareName=self.smb_share, 
    +611                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
    +612                    )
    +613                except impacket.smbconnection.SessionError as err:
    +614                    if err.getErrorCode() == 0xc0000035:
    +615                        # STATUS_OBJECT_NAME_COLLISION
    +616                        # Remote directory already created, this is normal
    +617                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
    +618                        pass
    +619                    else:
    +620                        self.logger.error("Failed to create directory '%s': %s" % (tmp_path, err))
    +621                        if self.config.debug:
    +622                            traceback.print_exc()
    +623        else:
    +624            pass
     
    @@ -3693,50 +3802,50 @@

    -
    603    def mount(self, local_mount_point, remote_path):
    -604        """
    -605        Generates the command to mount an SMB share on different platforms.
    -606
    -607        This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.
    -608        It constructs the mount command using the provided parameters and executes it using the os.system() function.
    -609
    -610        Args:
    -611            local_mount_point (str): The local directory where the SMB share will be mounted.
    -612            remote_path (str): The remote path on the SMB share to be mounted.
    -613
    -614        Note:
    -615            - For Windows platform, the command uses 'net use' to mount the share.
    -616            - For Linux platform, the command uses 'mount' to mount the share.
    -617            - For macOS platform, the command uses 'mount_smbfs' to mount the share.
    -618            - If the platform is not supported, an error message is displayed.
    -619
    -620        Returns:
    -621            None
    -622        """
    -623
    -624        if not os.path.exists(local_mount_point):
    -625            pass
    -626
    -627        if sys.platform.startswith('win'):
    -628            remote_path = remote_path.replace('/',ntpath.sep)
    -629            command = f"net use {local_mount_point} \\\\{self.address}\\{self.smb_share}\\{remote_path}"
    -630        
    -631        elif sys.platform.startswith('linux'):
    -632            remote_path = remote_path.replace(ntpath.sep,'/')
    -633            command = f"mount -t cifs //{self.address}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.username},password={self.password}"
    -634        
    -635        elif sys.platform.startswith('darwin'):
    -636            remote_path = remote_path.replace(ntpath.sep,'/')
    -637            command = f"mount_smbfs //{self.username}:{self.password}@{self.address}/{self.smb_share}/{remote_path} {local_mount_point}"
    -638        
    -639        else:
    -640            command = None
    -641            print("[!] Unsupported platform for mounting SMB share.")
    -642        
    -643        if command is not None:
    -644            if self.config.debug:
    -645                print("[debug] Executing: %s" % command)
    -646            os.system(command)
    +            
    626    def mount(self, local_mount_point, remote_path):
    +627        """
    +628        Generates the command to mount an SMB share on different platforms.
    +629
    +630        This method takes the local mount point and the remote path of the SMB share and generates the appropriate mount command based on the platform.
    +631        It constructs the mount command using the provided parameters and executes it using the os.system() function.
    +632
    +633        Args:
    +634            local_mount_point (str): The local directory where the SMB share will be mounted.
    +635            remote_path (str): The remote path on the SMB share to be mounted.
    +636
    +637        Note:
    +638            - For Windows platform, the command uses 'net use' to mount the share.
    +639            - For Linux platform, the command uses 'mount' to mount the share.
    +640            - For macOS platform, the command uses 'mount_smbfs' to mount the share.
    +641            - If the platform is not supported, an error message is displayed.
    +642
    +643        Returns:
    +644            None
    +645        """
    +646
    +647        if not os.path.exists(local_mount_point):
    +648            pass
    +649
    +650        if sys.platform.startswith('win'):
    +651            remote_path = remote_path.replace('/',ntpath.sep)
    +652            command = f"net use {local_mount_point} \\\\{self.host}\\{self.smb_share}\\{remote_path}"
    +653        
    +654        elif sys.platform.startswith('linux'):
    +655            remote_path = remote_path.replace(ntpath.sep,'/')
    +656            command = f"mount -t cifs //{self.host}/{self.smb_share}/{remote_path} {local_mount_point} -o username={self.credentials.username},password={self.credentials.password}"
    +657        
    +658        elif sys.platform.startswith('darwin'):
    +659            remote_path = remote_path.replace(ntpath.sep,'/')
    +660            command = f"mount_smbfs //{self.credentials.username}:{self.credentials.password}@{self.host}/{self.smb_share}/{remote_path} {local_mount_point}"
    +661        
    +662        else:
    +663            command = None
    +664            self.logger.error("Unsupported platform for mounting SMB share.")
    +665        
    +666        if command is not None:
    +667            if self.config.debug:
    +668                self.logger.debug("Executing: %s" % command)
    +669            os.system(command)
     
    @@ -3772,32 +3881,33 @@

    -
    648    def path_exists(self, path=None):
    -649        """
    -650        Checks if the specified path exists on the SMB share.
    -651
    -652        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
    -653        If the path listing is successful and returns one or more entries, the path is considered to exist.
    -654
    -655        Args:
    -656            path (str, optional): The path to check on the SMB share. Defaults to None.
    -657
    -658        Returns:
    -659            bool: True if the path exists, False otherwise or if an error occurs.
    -660        """
    -661
    -662        if path is not None:
    -663            path = path.replace('*','')
    -664            try:
    -665                contents = self.smbClient.listPath(
    -666                    shareName=self.smb_share,
    -667                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
    -668                )
    -669                return (len(contents) != 0)
    -670            except Exception as e:
    -671                return False
    -672        else:
    -673            return False
    +            
    671    def path_exists(self, path=None):
    +672        """
    +673        Checks if the specified path exists on the SMB share.
    +674
    +675        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
    +676        If the path listing is successful and returns one or more entries, the path is considered to exist.
    +677
    +678        Args:
    +679            path (str, optional): The path to check on the SMB share. Defaults to None.
    +680
    +681        Returns:
    +682            bool: True if the path exists, False otherwise or if an error occurs.
    +683        """
    +684
    +685        if path is not None:
    +686            path = path.replace('*','')
    +687            path = path.replace('/', ntpath.sep)
    +688            try:
    +689                contents = self.smbClient.listPath(
    +690                    shareName=self.smb_share,
    +691                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
    +692                )
    +693                return (len(contents) != 0)
    +694            except Exception as e:
    +695                return False
    +696        else:
    +697            return False
     
    @@ -3826,49 +3936,48 @@

    -
    675    def path_isdir(self, pathFromRoot=None):
    -676        """
    -677        Checks if the specified path is a directory on the SMB share.
    -678
    -679        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
    -680        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
    -681
    -682        Args:
    -683            path (str, optional): The path to check on the SMB share. Defaults to None.
    -684
    -685        Returns:
    -686            bool: True if the path is a directory, False otherwise or if an error occurs.
    -687        """
    -688
    -689        if pathFromRoot is not None:
    -690            # Replace slashes if any
    -691            path = pathFromRoot.replace('/', ntpath.sep)
    -692            
    -693            # Strip wildcards to avoid injections
    -694            path = path.replace('*','')
    -695
    -696            # Normalize path and strip leading backslash
    -697            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    -698
    -699            if path.strip() in ['', '.', '..']:
    -700                # By defininition they exist on the filesystem
    -701                return True
    -702            else:
    -703                try:
    -704                    contents = self.smbClient.listPath(
    -705                        shareName=self.smb_share,
    -706                        path=path+'*'
    -707                    )
    -708                    # Filter on directories
    -709                    contents = [
    -710                        c for c in contents
    -711                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
    -712                    ]
    -713                    return (len(contents) != 0)
    -714                except Exception as e:
    -715                    return False
    -716        else:
    -717            return False
    +            
    699    def path_isdir(self, pathFromRoot=None):
    +700        """
    +701        Checks if the specified path is a directory on the SMB share.
    +702
    +703        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
    +704        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
    +705
    +706        Args:
    +707            path (str, optional): The path to check on the SMB share. Defaults to None.
    +708
    +709        Returns:
    +710            bool: True if the path is a directory, False otherwise or if an error occurs.
    +711        """
    +712
    +713        if pathFromRoot is not None: 
    +714            # Strip wildcards to avoid injections
    +715            path = pathFromRoot.replace('*','')
    +716            # Replace slashes if any
    +717            path = path.replace('/', ntpath.sep)
    +718
    +719            # Normalize path and strip leading backslash
    +720            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    +721
    +722            if path.strip() in ['', '.', '..']:
    +723                # By defininition they exist on the filesystem
    +724                return True
    +725            else:
    +726                try:
    +727                    contents = self.smbClient.listPath(
    +728                        shareName=self.smb_share,
    +729                        path=path+'*'
    +730                    )
    +731                    # Filter on directories
    +732                    contents = [
    +733                        c for c in contents
    +734                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
    +735                    ]
    +736                    return (len(contents) != 0)
    +737                except Exception as e:
    +738                    return False
    +739        else:
    +740            return False
     
    @@ -3891,45 +4000,50 @@

    def - path_isfile(self, path=None): + path_isfile(self, pathFromRoot=None):
    -
    719    def path_isfile(self, path=None):
    -720        """
    -721        Checks if the specified path is a file on the SMB share.
    -722
    -723        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
    -724        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
    -725
    -726        Args:
    -727            path (str, optional): The path to check on the SMB share. Defaults to None.
    -728
    -729        Returns:
    -730            bool: True if the path is a file, False otherwise or if an error occurs.
    -731        """
    -732
    -733        if path is not None:
    -734            path = path.replace('*','')
    -735            search_dir = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    -736            search_dir = ntpath.dirname(search_dir) + ntpath.sep + '*'
    -737            try:
    -738                contents = self.smbClient.listPath(
    -739                    shareName=self.smb_share,
    -740                    path=search_dir
    -741                )
    -742                # Filter on files
    -743                contents = [
    -744                    c for c in contents
    -745                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
    -746                ]
    -747                return (len(contents) != 0)
    -748            except Exception as e:
    -749                return False
    -750        else:
    -751            return False
    +            
    742    def path_isfile(self, pathFromRoot=None):
    +743        """
    +744        Checks if the specified path is a file on the SMB share.
    +745
    +746        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
    +747        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
    +748
    +749        Args:
    +750            path (str, optional): The path to check on the SMB share. Defaults to None.
    +751
    +752        Returns:
    +753            bool: True if the path is a file, False otherwise or if an error occurs.
    +754        """
    +755
    +756        if pathFromRoot is not None: 
    +757            # Strip wildcards to avoid injections
    +758            path = pathFromRoot.replace('*','')
    +759            # Replace slashes if any
    +760            path = path.replace('/', ntpath.sep)
    +761
    +762            # Normalize path and strip leading backslash
    +763            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
    +764
    +765            try:
    +766                contents = self.smbClient.listPath(
    +767                    shareName=self.smb_share,
    +768                    path=ntpath.dirname(path) + ntpath.sep + '*'
    +769                )
    +770                # Filter on files
    +771                contents = [
    +772                    c for c in contents
    +773                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
    +774                ]
    +775                return (len(contents) != 0)
    +776            except Exception as e:
    +777                return False
    +778        else:
    +779            return False
     
    @@ -3946,47 +4060,6 @@

    -

    -
    - -
    - - def - ping_smb_session(self): - - - -
    - -
    753    def ping_smb_session(self):
    -754        """
    -755        Tests the connectivity to the SMB server by sending an echo command.
    -756
    -757        This method attempts to send an echo command to the SMB server to check if the session is still active.
    -758        It updates the `connected` attribute of the class based on the success or failure of the echo command.
    -759
    -760        Returns:
    -761            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
    -762        """
    -763
    -764        try:
    -765            self.smbClient.getSMBServer().echo()
    -766        except Exception as e:
    -767            self.connected = False
    -768        return self.connected
    -
    - - -

    Tests the connectivity to the SMB server by sending an echo command.

    - -

    This method attempts to send an echo command to the SMB server to check if the session is still active. -It updates the connected attribute of the class based on the success or failure of the echo command.

    - -

    Returns: - bool: True if the echo command succeeds (indicating the session is active), False otherwise.

    -
    - -
    @@ -3999,72 +4072,81 @@

    -
    770    def put_file(self, localpath=None):
    -771        """
    -772        Uploads a single file to the SMB share.
    -773
    -774        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
    -775        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
    -776        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
    -777
    -778        Args:
    -779            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
    -780        """
    -781
    -782        # Parse path
    -783        localpath = localpath.replace('/', os.path.sep)
    -784        if os.path.sep in localpath:
    -785            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath))
    -786        else:
    -787            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep)
    -788        # Parse filename
    -789        filename = os.path.basename(localpath)
    -790
    -791        # Search for the file
    -792        matches = os.listdir(tmp_search_path)
    -793        # Filter the entries
    -794        matching_entries = []
    -795        for entry in matches:
    -796            if entry == filename:
    -797                matching_entries.append(entry)
    -798            elif '*' in filename:
    -799                regexp = filename.replace('.', '\\.').replace('*', '.*')
    -800                if re.match(regexp, entry):
    -801                    matching_entries.append(entry)
    -802
    -803        matching_entries = sorted(list(set(matching_entries)))
    +            
    781    def put_file(self, localpath=None):
    +782        """
    +783        Uploads a single file to the SMB share.
    +784
    +785        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
    +786        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
    +787        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
    +788
    +789        Args:
    +790            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
    +791        """
    +792
    +793        # Parse path
    +794        localpath = localpath.replace('/', os.path.sep)
    +795        if os.path.sep in localpath:
    +796            if localpath.startswith(os.path.sep):
    +797                # Absolute path
    +798                tmp_search_path = os.path.normpath(localpath)
    +799            else:
    +800                # Relative path
    +801                tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep + os.path.dirname(localpath))
    +802        else:
    +803            tmp_search_path = os.path.normpath(os.getcwd() + os.path.sep)
     804
    -805        # Loop and upload
    -806        for localpath in matching_entries:
    -807            if os.path.exists(localpath):
    -808                if os.path.isfile(localpath):
    -809                    try:
    -810                        localfile = os.path.basename(localpath)
    -811                        f = LocalFileIO(
    -812                            mode="rb", 
    -813                            path=localpath, 
    -814                            debug=self.config.debug
    -815                        )
    -816                        self.smbClient.putFile(
    -817                            shareName=self.smb_share, 
    -818                            pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
    -819                            callback=f.read
    -820                        )
    -821                        f.close()
    -822                    except (BrokenPipeError, KeyboardInterrupt) as err:
    -823                        print("[!] Interrupted.")
    -824                        self.close_smb_session()
    -825                        self.init_smb_session()
    -826                    except Exception as err:
    -827                        print("[!] Failed to upload '%s': %s" % (localfile, err))
    -828                        if self.config.debug:
    -829                            traceback.print_exc()
    -830                else:
    -831                    # [!] The specified localpath is a directory. Use 'put -r <directory>' instead.
    -832                    pass
    -833            else:
    -834                # [!] The specified localpath does not exist.
    -835                pass
    +805        # Parse filename
    +806        filename = os.path.basename(localpath)
    +807
    +808        # Search for the file
    +809        matches = os.listdir(tmp_search_path)
    +810        # Filter the entries
    +811        matching_entries = []
    +812        for entry in matches:
    +813            if entry == filename:
    +814                matching_entries.append(entry)
    +815            elif '*' in filename:
    +816                regexp = filename.replace('.', '\\.').replace('*', '.*')
    +817                if re.match(regexp, entry):
    +818                    matching_entries.append(entry)
    +819
    +820        matching_entries = sorted(list(set(matching_entries)))
    +821
    +822        # Loop and upload
    +823        for localpath in matching_entries:
    +824            if os.path.exists(localpath):
    +825                if os.path.isfile(localpath):
    +826                    try:
    +827                        localfile = os.path.basename(localpath)
    +828                        f = LocalFileIO(
    +829                            mode="rb", 
    +830                            path=localpath, 
    +831                            debug=self.config.debug
    +832                        )
    +833                        self.smbClient.putFile(
    +834                            shareName=self.smb_share, 
    +835                            pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
    +836                            callback=f.read
    +837                        )
    +838                        f.close()
    +839
    +840                    except (BrokenPipeError, KeyboardInterrupt) as err:
    +841                        self.logger.error("Interrupted.")
    +842                        self.close_smb_session()
    +843                        self.init_smb_session()
    +844
    +845                    except (Exception, PermissionError) as err:
    +846                        f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    +847                        f.close(remove=False)
    +848                        if self.config.debug:
    +849                            traceback.print_exc()
    +850                else:
    +851                    # [!] The specified localpath is a directory. Use 'put -r <directory>' instead.
    +852                    pass
    +853            else:
    +854                # [!] The specified localpath does not exist.
    +855                pass
     
    @@ -4091,62 +4173,65 @@

    -
    837    def put_file_recursively(self, localpath=None):
    -838        """
    -839        Recursively uploads files from a specified local directory to the SMB share.
    -840
    -841        This method walks through the given local directory and all its subdirectories, uploading each file to the
    -842        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
    -843        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
    -844        and uploading files. If the local path is not a directory, it prints an error message.
    -845
    -846        Args:
    -847            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
    -848        """
    -849
    -850        if os.path.exists(localpath):
    -851            if os.path.isfile(localpath):
    -852                # Iterate over all files and directories within the local path
    -853                local_files = {}
    -854                for root, dirs, files in os.walk(localpath):
    -855                    if len(files) != 0:
    -856                        local_files[root] = files
    -857
    -858                # Iterate over the found files
    -859                for local_dir_path in sorted(local_files.keys()):
    -860                    print("[>] Putting files of '%s'" % local_dir_path)
    -861
    -862                    # Create remote directory
    -863                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
    -864                    self.mkdir(
    -865                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
    -866                    )
    -867
    -868                    for local_file_path in local_files[local_dir_path]:
    -869                        try:
    -870                            f = LocalFileIO(
    -871                                mode="rb", 
    -872                                path=local_dir_path + os.path.sep + local_file_path, 
    -873                                debug=self.config.debug
    -874                            )
    -875                            self.smbClient.putFile(
    -876                                shareName=self.smb_share, 
    -877                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
    -878                                callback=f.read
    -879                            )
    -880                            f.close()
    +            
    857    def put_file_recursively(self, localpath=None):
    +858        """
    +859        Recursively uploads files from a specified local directory to the SMB share.
    +860
    +861        This method walks through the given local directory and all its subdirectories, uploading each file to the
    +862        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
    +863        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
    +864        and uploading files. If the local path is not a directory, it prints an error message.
    +865
    +866        Args:
    +867            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
    +868        """
    +869
    +870        if os.path.exists(localpath):
    +871            if os.path.isdir(localpath):
    +872                # Iterate over all files and directories within the local path
    +873                local_files = {}
    +874                for root, dirs, files in os.walk(localpath):
    +875                    if len(files) != 0:
    +876                        local_files[root] = files
    +877
    +878                # Iterate over the found files
    +879                for local_dir_path in sorted(local_files.keys()):
    +880                    self.logger.print("[>] Putting files of '%s'" % local_dir_path)
     881
    -882                        except BrokenPipeError as err:
    -883                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    -884                            f.close(remove=True)
    -885                            break
    -886                        except Exception as err:
    -887                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    -888                            f.close(remove=True)
    -889                else:
    -890                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
    -891        else:
    -892            print("[!] The specified localpath does not exist.")
    +882                    # Create remote directory
    +883                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
    +884                    self.mkdir(
    +885                        path=ntpath.normpath(remote_dir_path + ntpath.sep)
    +886                    )
    +887
    +888                    for local_file_path in local_files[local_dir_path]:
    +889                        try:
    +890                            f = LocalFileIO(
    +891                                mode="rb", 
    +892                                path=local_dir_path + os.path.sep + local_file_path, 
    +893                                debug=self.config.debug
    +894                            )
    +895                            self.smbClient.putFile(
    +896                                shareName=self.smb_share, 
    +897                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
    +898                                callback=f.read
    +899                            )
    +900                            f.close()
    +901
    +902                        except (BrokenPipeError, KeyboardInterrupt) as err:
    +903                            self.logger.error("Interrupted.")
    +904                            self.close_smb_session()
    +905                            self.init_smb_session()
    +906                            
    +907                        except (Exception, PermissionError) as err:
    +908                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
    +909                            f.close(remove=False)
    +910                            if self.config.debug:
    +911                                traceback.print_exc()
    +912                else:
    +913                    self.logger.error("The specified localpath is a file. Use 'put <file>' instead.")
    +914        else:
    +915            self.logger.error("The specified localpath does not exist.")
     
    @@ -4162,6 +4247,72 @@

    +
    +
    + +
    + + def + read_file(self, path=None): + + + +
    + +
    917    def read_file(self, path=None):
    +918        """
    +919        Reads a file from the SMB share.
    +920
    +921        This method attempts to read the contents of a file specified by the `path` parameter from the SMB share.
    +922        It constructs the full path to the file, checks if the path is a valid file, and then reads the file content
    +923        into a byte stream which is returned to the caller.
    +924
    +925        Args:
    +926            path (str, optional): The path of the file to be read from the SMB share. Defaults to None.
    +927
    +928        Returns:
    +929            bytes: The content of the file as a byte stream, or None if the file does not exist or an error occurs.
    +930        """
    +931
    +932        if self.path_isfile(pathFromRoot=path):
    +933            path = path.replace('/', ntpath.sep)
    +934            if path.startswith(ntpath.sep):
    +935                # Absolute path
    +936                tmp_file_path = ntpath.normpath(path)
    +937            else:
    +938                # Relative path
    +939                tmp_file_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
    +940            tmp_file_path = tmp_file_path.lstrip(ntpath.sep)
    +941
    +942            fh = io.BytesIO()
    +943            try:
    +944                # opening the files in streams instead of mounting shares allows 
    +945                # for running the script from unprivileged containers
    +946                self.smbClient.getFile(self.smb_share, tmp_file_path, fh.write)
    +947            except impacket.smbconnection.SessionError as e:
    +948                return None
    +949            rawdata = fh.getvalue()
    +950            fh.close()
    +951            return rawdata
    +952        else:
    +953            return None
    +
    + + +

    Reads a file from the SMB share.

    + +

    This method attempts to read the contents of a file specified by the path parameter from the SMB share. +It constructs the full path to the file, checks if the path is a valid file, and then reads the file content +into a byte stream which is returned to the caller.

    + +

    Args: + path (str, optional): The path of the file to be read from the SMB share. Defaults to None.

    + +

    Returns: + bytes: The content of the file as a byte stream, or None if the file does not exist or an error occurs.

    +
    + +
    @@ -4174,26 +4325,26 @@

    -
    894    def rmdir(self, path=None):
    -895        """
    -896        Removes a directory from the SMB share at the specified path.
    -897
    -898        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
    -899        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    -900        the stack trace of the exception.
    -901
    -902        Args:
    -903            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
    -904        """
    -905        try:
    -906            self.smbClient.deleteDirectory(
    -907                shareName=self.smb_share, 
    -908                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
    -909            )
    -910        except Exception as err:
    -911            print("[!] Failed to remove directory '%s': %s" % (path, err))
    -912            if self.config.debug:
    -913                traceback.print_exc()
    +            
    955    def rmdir(self, path=None):
    +956        """
    +957        Removes a directory from the SMB share at the specified path.
    +958
    +959        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
    +960        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    +961        the stack trace of the exception.
    +962
    +963        Args:
    +964            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
    +965        """
    +966        try:
    +967            self.smbClient.deleteDirectory(
    +968                shareName=self.smb_share, 
    +969                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
    +970            )
    +971        except Exception as err:
    +972            self.logger.error("Failed to remove directory '%s': %s" % (path, err))
    +973            if self.config.debug:
    +974                traceback.print_exc()
     
    @@ -4220,58 +4371,58 @@

    -
    915    def rm(self, path=None):
    -916        """
    -917        Removes a file from the SMB share at the specified path.
    -918
    -919        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
    -920        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    -921        the stack trace of the exception.
    -922
    -923        Args:
    -924            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
    -925        """
    -926
    -927        # Parse path
    -928        path = path.replace('/', ntpath.sep)
    -929        if ntpath.sep in path:
    -930            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    -931        else:
    -932            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    -933        # Parse filename
    -934        filename = ntpath.basename(path)
    -935
    -936        # Search for the file
    -937        matches = self.smbClient.listPath(
    -938            shareName=self.smb_share, 
    -939            path=tmp_search_path + ntpath.sep + '*'
    -940        )   
    -941
    -942        # Filter the entries
    -943        matching_entries = []
    -944        for entry in matches:
    -945            if entry.is_directory():
    -946                # Skip directories
    -947                continue
    -948            if entry.get_longname() == filename:
    -949                matching_entries.append(entry)
    -950            elif '*' in filename:
    -951                regexp = filename.replace('.', '\\.').replace('*', '.*')
    -952                if re.match(regexp, entry.get_longname()):
    -953                    matching_entries.append(entry)
    -954        
    -955        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    -956
    -957        for entry in matching_entries:
    -958            try:
    -959                self.smbClient.deleteFile(
    -960                    shareName=self.smb_share, 
    -961                    pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), 
    -962                )
    -963            except Exception as err:
    -964                print("[!] Failed to remove file '%s': %s" % (path, err))
    -965                if self.config.debug:
    -966                    traceback.print_exc()
    +            
     976    def rm(self, path=None):
    + 977        """
    + 978        Removes a file from the SMB share at the specified path.
    + 979
    + 980        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
    + 981        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
    + 982        the stack trace of the exception.
    + 983
    + 984        Args:
    + 985            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
    + 986        """
    + 987
    + 988        # Parse path
    + 989        path = path.replace('/', ntpath.sep)
    + 990        if ntpath.sep in path:
    + 991            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep + ntpath.dirname(path))
    + 992        else:
    + 993            tmp_search_path = ntpath.normpath(self.smb_cwd + ntpath.sep)
    + 994        # Parse filename
    + 995        filename = ntpath.basename(path)
    + 996
    + 997        # Search for the file
    + 998        matches = self.smbClient.listPath(
    + 999            shareName=self.smb_share, 
    +1000            path=tmp_search_path + ntpath.sep + '*'
    +1001        )   
    +1002
    +1003        # Filter the entries
    +1004        matching_entries = []
    +1005        for entry in matches:
    +1006            if entry.is_directory():
    +1007                # Skip directories
    +1008                continue
    +1009            if entry.get_longname() == filename:
    +1010                matching_entries.append(entry)
    +1011            elif '*' in filename:
    +1012                regexp = filename.replace('.', '\\.').replace('*', '.*')
    +1013                if re.match(regexp, entry.get_longname()):
    +1014                    matching_entries.append(entry)
    +1015        
    +1016        matching_entries = sorted(list(set(matching_entries)), key=lambda x: x.get_longname())
    +1017
    +1018        for entry in matching_entries:
    +1019            try:
    +1020                self.smbClient.deleteFile(
    +1021                    shareName=self.smb_share, 
    +1022                    pathName=ntpath.normpath(tmp_search_path + ntpath.sep + entry.get_longname()), 
    +1023                )
    +1024            except Exception as err:
    +1025                self.logger.error("Failed to remove file '%s': %s" % (path, err))
    +1026                if self.config.debug:
    +1027                    traceback.print_exc()
     
    @@ -4298,135 +4449,135 @@

    -
     968    def tree(self, path=None):
    - 969        """
    - 970        Recursively lists the directory structure of the SMB share starting from the specified path.
    - 971
    - 972        This function prints a visual representation of the directory tree of the remote SMB share. It uses
    - 973        recursion to navigate through directories and lists all files and subdirectories in each directory.
    - 974        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
    - 975
    - 976        Args:
    - 977            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
    - 978                                  Defaults to the root of the current share.
    - 979        """
    - 980        
    - 981        def recurse_action(base_dir="", path=[], prompt=[]):
    - 982            bars = ["│   ", "├── ", "└── "]
    - 983
    - 984            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
    - 985
    - 986            entries = []
    - 987            try:
    - 988                entries = self.smbClient.listPath(
    - 989                    shareName=self.smb_share, 
    - 990                    path=remote_smb_path+'\\*'
    - 991                )
    - 992            except impacket.smbconnection.SessionError as err:
    - 993                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
    - 994                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
    - 995                if self.config.no_colors:
    - 996                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
    - 997                else:
    - 998                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
    - 999                return 
    -1000
    -1001            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    -1002            entries = sorted(entries, key=lambda x:x.get_longname())
    -1003
    -1004            # 
    -1005            if len(entries) > 1:
    -1006                index = 0
    -1007                for entry in entries:
    -1008                    index += 1
    -1009                    # This is the first entry 
    -1010                    if index == 0:
    -1011                        if entry.is_directory():
    -1012                            if self.config.no_colors:
    -1013                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1014                            else:
    -1015                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1016                            recurse_action(
    -1017                                base_dir=base_dir, 
    -1018                                path=path+[entry.get_longname()],
    -1019                                prompt=prompt+["│   "]
    -1020                            )
    -1021                        else:
    -1022                            if self.config.no_colors:
    -1023                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1024                            else:
    -1025                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1026
    -1027                    # This is the last entry
    -1028                    elif index == len(entries):
    -1029                        if entry.is_directory():
    -1030                            if self.config.no_colors:
    -1031                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1032                            else:
    -1033                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1034                            recurse_action(
    -1035                                base_dir=base_dir, 
    -1036                                path=path+[entry.get_longname()],
    -1037                                prompt=prompt+["    "]
    -1038                            )
    -1039                        else:
    -1040                            if self.config.no_colors:
    -1041                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1042                            else:
    -1043                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1044                        
    -1045                    # These are entries in the middle
    -1046                    else:
    -1047                        if entry.is_directory():
    -1048                            if self.config.no_colors:
    -1049                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1050                            else:
    -1051                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1052                            recurse_action(
    -1053                                base_dir=base_dir, 
    -1054                                path=path+[entry.get_longname()],
    -1055                                prompt=prompt+["│   "]
    -1056                            )
    -1057                        else:
    -1058                            if self.config.no_colors:
    -1059                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1060                            else:
    -1061                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    -1062
    -1063            # 
    -1064            elif len(entries) == 1:
    -1065                entry = entries[0]
    -1066                if entry.is_directory():
    -1067                    if self.config.no_colors:
    -1068                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1069                    else:
    -1070                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1071                    recurse_action(
    -1072                        base_dir=base_dir, 
    -1073                        path=path+[entry.get_longname()],
    -1074                        prompt=prompt+["    "]
    -1075                    )
    -1076                else:
    -1077                    if self.config.no_colors:
    -1078                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1079                    else:
    -1080                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    -1081
    -1082        # Entrypoint
    -1083        try:
    -1084            if self.config.no_colors:
    -1085                print("%s\\" % path)
    -1086            else:
    -1087                print("\x1b[1;96m%s\x1b[0m\\" % path)
    -1088            recurse_action(
    -1089                base_dir=self.smb_cwd, 
    -1090                path=[path],
    -1091                prompt=[""]
    -1092            )
    -1093        except (BrokenPipeError, KeyboardInterrupt) as e:
    -1094            print("[!] Interrupted.")
    -1095            self.close_smb_session()
    -1096            self.init_smb_session()
    +            
    1029    def tree(self, path=None):
    +1030        """
    +1031        Recursively lists the directory structure of the SMB share starting from the specified path.
    +1032
    +1033        This function prints a visual representation of the directory tree of the remote SMB share. It uses
    +1034        recursion to navigate through directories and lists all files and subdirectories in each directory.
    +1035        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
    +1036
    +1037        Args:
    +1038            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
    +1039                                  Defaults to the root of the current share.
    +1040        """
    +1041        
    +1042        def recurse_action(base_dir="", path=[], prompt=[]):
    +1043            bars = ["│   ", "├── ", "└── "]
    +1044
    +1045            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
    +1046
    +1047            entries = []
    +1048            try:
    +1049                entries = self.smbClient.listPath(
    +1050                    shareName=self.smb_share, 
    +1051                    path=remote_smb_path+'\\*'
    +1052                )
    +1053            except impacket.smbconnection.SessionError as err:
    +1054                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
    +1055                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
    +1056                if self.config.no_colors:
    +1057                    self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
    +1058                else:
    +1059                    self.logger.print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
    +1060                return 
    +1061
    +1062            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
    +1063            entries = sorted(entries, key=lambda x:x.get_longname())
    +1064
    +1065            # 
    +1066            if len(entries) > 1:
    +1067                index = 0
    +1068                for entry in entries:
    +1069                    index += 1
    +1070                    # This is the first entry 
    +1071                    if index == 0:
    +1072                        if entry.is_directory():
    +1073                            if self.config.no_colors:
    +1074                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1075                            else:
    +1076                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1077                            recurse_action(
    +1078                                base_dir=base_dir, 
    +1079                                path=path+[entry.get_longname()],
    +1080                                prompt=prompt+["│   "]
    +1081                            )
    +1082                        else:
    +1083                            if self.config.no_colors:
    +1084                                self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1085                            else:
    +1086                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1087
    +1088                    # This is the last entry
    +1089                    elif index == len(entries):
    +1090                        if entry.is_directory():
    +1091                            if self.config.no_colors:
    +1092                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1093                            else:
    +1094                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1095                            recurse_action(
    +1096                                base_dir=base_dir, 
    +1097                                path=path+[entry.get_longname()],
    +1098                                prompt=prompt+["    "]
    +1099                            )
    +1100                        else:
    +1101                            if self.config.no_colors:
    +1102                                self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1103                            else:
    +1104                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1105                        
    +1106                    # These are entries in the middle
    +1107                    else:
    +1108                        if entry.is_directory():
    +1109                            if self.config.no_colors:
    +1110                                self.logger.print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1111                            else:
    +1112                                self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1113                            recurse_action(
    +1114                                base_dir=base_dir, 
    +1115                                path=path+[entry.get_longname()],
    +1116                                prompt=prompt+["│   "]
    +1117                            )
    +1118                        else:
    +1119                            if self.config.no_colors:
    +1120                                self.logger.print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1121                            else:
    +1122                                self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
    +1123
    +1124            # 
    +1125            elif len(entries) == 1:
    +1126                entry = entries[0]
    +1127                if entry.is_directory():
    +1128                    if self.config.no_colors:
    +1129                        self.logger.print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1130                    else:
    +1131                        self.logger.print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1132                    recurse_action(
    +1133                        base_dir=base_dir, 
    +1134                        path=path+[entry.get_longname()],
    +1135                        prompt=prompt+["    "]
    +1136                    )
    +1137                else:
    +1138                    if self.config.no_colors:
    +1139                        self.logger.print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1140                    else:
    +1141                        self.logger.print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
    +1142
    +1143        # Entrypoint
    +1144        try:
    +1145            if self.config.no_colors:
    +1146                self.logger.print("%s\\" % path)
    +1147            else:
    +1148                self.logger.print("\x1b[1;96m%s\x1b[0m\\" % path)
    +1149            recurse_action(
    +1150                base_dir=self.smb_cwd, 
    +1151                path=[path],
    +1152                prompt=[""]
    +1153            )
    +1154        except (BrokenPipeError, KeyboardInterrupt) as e:
    +1155            self.logger.error("Interrupted.")
    +1156            self.close_smb_session()
    +1157            self.init_smb_session()
     
    @@ -4454,37 +4605,36 @@

    -
    1098    def umount(self, local_mount_point):
    -1099        """
    -1100        Unmounts the specified local mount point of the remote share.
    -1101
    -1102        This method unmounts the specified local mount point of the remote share based on the platform.
    -1103        It supports Windows, Linux, and macOS platforms for unmounting.
    -1104
    -1105        Parameters:
    -1106            local_mount_point (str): The local mount point to unmount.
    -1107
    -1108        Raises:
    -1109            None
    -1110        """
    -1111
    -1112        if os.path.exists(local_mount_point):
    -1113            if sys.platform.startswith('win'):
    -1114                command = f"net use {local_mount_point} /delete"
    -1115
    -1116            elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    -1117                command = f"umount {local_mount_point}"
    -1118
    -1119            else:
    -1120                command = None
    -1121                print("[!] Unsupported platform for unmounting SMB share.")
    -1122        
    -1123            if command is not None:
    -1124                if self.config.debug:
    -1125                    print("[debug] Executing: %s" % command)
    -1126                os.system(command)
    -1127        else:
    -1128            print("[!] Cannot unmount a non existing path.")        
    +            
    1159    def umount(self, local_mount_point):
    +1160        """
    +1161        Unmounts the specified local mount point of the remote share.
    +1162
    +1163        This method unmounts the specified local mount point of the remote share based on the platform.
    +1164        It supports Windows, Linux, and macOS platforms for unmounting.
    +1165
    +1166        Parameters:
    +1167            local_mount_point (str): The local mount point to unmount.
    +1168
    +1169        Raises:
    +1170            None
    +1171        """
    +1172
    +1173        if os.path.exists(local_mount_point):
    +1174            if sys.platform.startswith('win'):
    +1175                command = f"net use {local_mount_point} /delete"
    +1176
    +1177            elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    +1178                command = f"umount {local_mount_point}"
    +1179
    +1180            else:
    +1181                command = None
    +1182                self.logger.error("Unsupported platform for unmounting SMB share.")
    +1183        
    +1184            if command is not None:
    +1185                self.logger.debug("Executing: %s" % command)
    +1186                os.system(command)
    +1187        else:
    +1188            self.logger.error("Cannot unmount a non existing path.")        
     
    @@ -4513,41 +4663,41 @@

    -
    1132    def test_rights(self, sharename): 
    -1133        """
    -1134        Tests the read and write access rights of the current SMB session.
    -1135
    -1136        This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.
    -1137        
    -1138        Returns:
    -1139            dict: A dictionary containing the read and write access rights status.
    -1140                - "readable" (bool): Indicates if the session has read access rights.
    -1141                - "writable" (bool): Indicates if the session has write access rights.
    -1142        """
    -1143
    -1144        # Restore the current share
    -1145        current_share = self.smb_share
    -1146        self.set_share(shareName=sharename)
    -1147
    -1148        access_rights = {"readable": False, "writable": False}
    -1149        try:
    -1150            self.smbClient.listPath(self.smb_share, '*', password=None)
    -1151            access_rights["readable"] = True
    -1152        except impacket.smbconnection.SessionError as e:
    -1153            access_rights["readable"] = False
    -1154
    -1155        try:
    -1156            temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)]))
    -1157            self.smbClient.createDirectory(self.smb_share, temp_dir)
    -1158            self.smbClient.deleteDirectory(self.smb_share, temp_dir)
    -1159            access_rights["writable"] = True
    -1160        except impacket.smbconnection.SessionError as e:
    -1161            access_rights["writable"] = False
    -1162
    -1163        # Restore the current share
    -1164        self.set_share(shareName=current_share)
    -1165
    -1166        return access_rights
    +            
    1192    def test_rights(self, sharename): 
    +1193        """
    +1194        Tests the read and write access rights of the current SMB session.
    +1195
    +1196        This method checks the read and write access rights of the current SMB session by attempting to list paths and create/delete temporary directories.
    +1197        
    +1198        Returns:
    +1199            dict: A dictionary containing the read and write access rights status.
    +1200                - "readable" (bool): Indicates if the session has read access rights.
    +1201                - "writable" (bool): Indicates if the session has write access rights.
    +1202        """
    +1203
    +1204        # Restore the current share
    +1205        current_share = self.smb_share
    +1206        self.set_share(shareName=sharename)
    +1207
    +1208        access_rights = {"readable": False, "writable": False}
    +1209        try:
    +1210            self.smbClient.listPath(self.smb_share, '*', password=None)
    +1211            access_rights["readable"] = True
    +1212        except impacket.smbconnection.SessionError as e:
    +1213            access_rights["readable"] = False
    +1214
    +1215        try:
    +1216            temp_dir = ntpath.normpath("\\" + ''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ0123456759") for k in range(16)]))
    +1217            self.smbClient.createDirectory(self.smb_share, temp_dir)
    +1218            self.smbClient.deleteDirectory(self.smb_share, temp_dir)
    +1219            access_rights["writable"] = True
    +1220        except impacket.smbconnection.SessionError as e:
    +1221            access_rights["writable"] = False
    +1222
    +1223        # Restore the current share
    +1224        self.set_share(shareName=current_share)
    +1225
    +1226        return access_rights
     
    @@ -4574,32 +4724,32 @@

    -
    1170    def set_share(self, shareName):
    -1171        """
    -1172        Sets the current SMB share to the specified share name.
    -1173
    -1174        This method updates the SMB session to use the specified share name. It checks if the share name is valid
    -1175        and updates the smb_share attribute of the SMBSession instance.
    -1176
    -1177        Parameters:
    -1178            shareName (str): The name of the share to set as the current SMB share.
    -1179
    -1180        Raises:
    -1181            ValueError: If the shareName is None or an empty string.
    -1182        """
    -1183
    -1184        if shareName is not None:
    -1185            self.list_shares()
    -1186            if shareName.lower() in self.available_shares.keys():
    -1187                # Doing this in order to keep the case of the share adevertised by the remote machine
    -1188                self.smb_share = self.available_shares[shareName.lower()]["name"]
    -1189                self.smb_cwd = ""
    -1190                # Connects the tree
    -1191                self.smb_tree_id = self.smbClient.connectTree(self.smb_share)
    -1192            else:
    -1193                print("[!] Could not set share '%s', it does not exist remotely." % shareName)
    -1194        else:
    -1195            self.smb_share = None
    +            
    1230    def set_share(self, shareName):
    +1231        """
    +1232        Sets the current SMB share to the specified share name.
    +1233
    +1234        This method updates the SMB session to use the specified share name. It checks if the share name is valid
    +1235        and updates the smb_share attribute of the SMBSession instance.
    +1236
    +1237        Parameters:
    +1238            shareName (str): The name of the share to set as the current SMB share.
    +1239
    +1240        Raises:
    +1241            ValueError: If the shareName is None or an empty string.
    +1242        """
    +1243
    +1244        if shareName is not None:
    +1245            self.list_shares()
    +1246            if shareName.lower() in self.available_shares.keys():
    +1247                # Doing this in order to keep the case of the share adevertised by the remote machine
    +1248                self.smb_share = self.available_shares[shareName.lower()]["name"]
    +1249                self.smb_cwd = ""
    +1250                # Connects the tree
    +1251                self.smb_tree_id = self.smbClient.connectTree(self.smb_share)
    +1252            else:
    +1253                self.logger.error("Could not set share '%s', it does not exist remotely." % shareName)
    +1254        else:
    +1255            self.smb_share = None
     
    @@ -4628,48 +4778,48 @@

    -
    1197    def set_cwd(self, path=None):
    -1198        """
    -1199        Sets the current working directory on the SMB share to the specified path.
    -1200
    -1201        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
    -1202        If the specified path is not a directory, the cwd remains unchanged.
    -1203
    -1204        Parameters:
    -1205            path (str): The path to set as the current working directory.
    -1206
    -1207        Raises:
    -1208            ValueError: If the specified path is not a directory.
    -1209        """
    -1210
    -1211        if path is not None:
    -1212            # Set path separators to ntpath sep 
    -1213            if '/' in path:
    -1214                path = path.replace('/', ntpath.sep)
    -1215
    -1216            if path.startswith(ntpath.sep):
    -1217                # Absolute path
    -1218                path = path + ntpath.sep
    -1219            else:
    -1220                # Relative path to the CWD
    -1221                if len(self.smb_cwd) == 0:
    -1222                    path = path + ntpath.sep
    -1223                else:
    -1224                    path = self.smb_cwd + ntpath.sep + path
    -1225            
    -1226            # Path normalization
    -1227            path = ntpath.normpath(path)
    -1228            path = re.sub(r'\\+', r'\\', path)
    -1229
    -1230            if path in ["", ".", ".."]:
    -1231                self.smb_cwd = ""
    -1232            else:
    -1233                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
    -1234                    # Path exists on the remote 
    -1235                    self.smb_cwd = ntpath.normpath(path)
    -1236                else:
    -1237                    # Path does not exists or is not a directory on the remote 
    -1238                    print("[!] Remote directory '%s' does not exist." % path)
    +            
    1257    def set_cwd(self, path=None):
    +1258        """
    +1259        Sets the current working directory on the SMB share to the specified path.
    +1260
    +1261        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
    +1262        If the specified path is not a directory, the cwd remains unchanged.
    +1263
    +1264        Parameters:
    +1265            path (str): The path to set as the current working directory.
    +1266
    +1267        Raises:
    +1268            ValueError: If the specified path is not a directory.
    +1269        """
    +1270
    +1271        if path is not None:
    +1272            # Set path separators to ntpath sep 
    +1273            if '/' in path:
    +1274                path = path.replace('/', ntpath.sep)
    +1275
    +1276            if path.startswith(ntpath.sep):
    +1277                # Absolute path
    +1278                path = path + ntpath.sep
    +1279            else:
    +1280                # Relative path to the CWD
    +1281                if len(self.smb_cwd) == 0:
    +1282                    path = path + ntpath.sep
    +1283                else:
    +1284                    path = self.smb_cwd + ntpath.sep + path
    +1285            
    +1286            # Path normalization
    +1287            path = ntpath.normpath(path)
    +1288            path = re.sub(r'\\+', r'\\', path)
    +1289
    +1290            if path in ["", ".", ".."]:
    +1291                self.smb_cwd = ""
    +1292            else:
    +1293                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
    +1294                    # Path exists on the remote 
    +1295                    self.smb_cwd = ntpath.normpath(path)
    +1296                else:
    +1297                    # Path does not exists or is not a directory on the remote 
    +1298                    self.logger.error("Remote directory '%s' does not exist." % path)
     
    diff --git a/documentation/smbclientng/core/SessionsManager.html b/documentation/smbclientng/core/SessionsManager.html new file mode 100644 index 0000000..61f5cfa --- /dev/null +++ b/documentation/smbclientng/core/SessionsManager.html @@ -0,0 +1,1172 @@ + + + + + + + smbclientng.core.SessionsManager API documentation + + + + + + + + + +
    +
    +

    +smbclientng.core.SessionsManager

    + + + + + + +
      1#!/usr/bin/env python3
    +  2# -*- coding: utf-8 -*-
    +  3# File name          : SessionsManager.py
    +  4# Author             : Podalirius (@podalirius_)
    +  5# Date created       : 20 may 2024
    +  6
    +  7import datetime
    +  8from smbclientng.core.Credentials import Credentials
    +  9from smbclientng.core.ModuleArgumentParser import ModuleArgumentParser
    + 10from smbclientng.core.SMBSession import SMBSession
    + 11import time
    + 12
    + 13
    + 14class SessionsManager(object):
    + 15    """
    + 16    A class to manage SMB sessions.
    + 17
    + 18    This class is responsible for creating, managing, and switching between multiple SMB sessions. It allows for the creation of new sessions with specified credentials and hosts, and provides methods to switch between existing sessions. It also keeps track of the current session and its ID.
    + 19
    + 20    Attributes:
    + 21        next_session_id (int): The next available session ID.
    + 22        current_session (SMBSession): The currently active SMB session.
    + 23        current_session_id (int): The ID of the currently active session.
    + 24        sessions (dict): A dictionary of all active sessions, keyed by their session ID.
    + 25    """
    + 26
    + 27    next_session_id = 1
    + 28    current_session = None
    + 29    current_session_id = None
    + 30    sessions = {}
    + 31
    + 32    def __init__(self, config, logger):
    + 33        self.sessions = {}
    + 34        self.next_session_id = 1
    + 35        self.current_session = None
    + 36        self.current_session_id = None
    + 37
    + 38        self.config = config
    + 39        self.logger = logger
    + 40
    + 41    def create_new_session(self, credentials, host, port=445):
    + 42        """
    + 43        Creates a new session with the given session information.
    + 44
    + 45        Args:
    + 46            session_info (dict): Information necessary to start a new session.
    + 47
    + 48        Returns:
    + 49            None
    + 50        """
    + 51        
    + 52        smbSession = SMBSession(
    + 53            host=host,
    + 54            port=port,
    + 55            credentials=credentials,
    + 56            config=self.config,
    + 57            logger=self.logger
    + 58        )
    + 59        smbSession.init_smb_session()
    + 60        
    + 61        self.sessions[self.next_session_id] = {
    + 62            "id": self.next_session_id,
    + 63            "smbSession": smbSession,
    + 64            "created_at": int(time.time()),
    + 65        }
    + 66        self.switch_session(self.next_session_id)
    + 67        self.next_session_id += 1
    + 68
    + 69    def switch_session(self, session_id):
    + 70        """
    + 71        Switches the current session to the session with the specified ID.
    + 72
    + 73        Args:
    + 74            session_id (int): The ID of the session to switch to.
    + 75
    + 76        Returns:
    + 77            bool: True if the session was successfully switched, False otherwise.
    + 78        """
    + 79
    + 80        if session_id in self.sessions.keys():
    + 81            self.current_session = self.sessions[session_id]["smbSession"]
    + 82            self.current_session_id = session_id
    + 83            return True
    + 84        else:
    + 85            return False
    + 86
    + 87    def delete_session(self, session_id):
    + 88        """
    + 89        Deletes a session with the given session ID.
    + 90
    + 91        Args:
    + 92            session_id (int): The ID of the session to delete.
    + 93
    + 94        Returns:
    + 95            bool: True if the session was successfully deleted, False otherwise.
    + 96        """
    + 97
    + 98        if session_id in self.sessions.keys():
    + 99            self.sessions[session_id]["smbSession"].close_smb_session()
    +100            del self.sessions[session_id]
    +101            if self.current_session_id == session_id:
    +102                self.current_session = None
    +103                self.current_session_id = None
    +104            return True
    +105        return False
    +106
    +107    def process_command_line(self, arguments):
    +108        """
    +109        Processes command line arguments to manage SMB sessions.
    +110
    +111        This function parses the command line arguments provided to the application and determines the appropriate action to take,
    +112        such as creating, interacting, deleting, or listing SMB sessions, or executing a command in one or more sessions.
    +113
    +114        Args:
    +115            arguments (list of str): The command line arguments.
    +116
    +117        Returns:
    +118            None
    +119        """
    +120
    +121        parser = ModuleArgumentParser(add_help=True, prog="sessions", description="")
    +122
    +123        # interact
    +124        mode_interact = ModuleArgumentParser(add_help=False, description="Switch to the specified session.")
    +125        mode_interact.add_argument("-i", "--session-id", type=int, default=None, required=True, help="Session ID to interact with.")
    +126
    +127        # Create
    +128        mode_create = ModuleArgumentParser(add_help=False, description="Create a new session.")
    +129        group_target = mode_create.add_argument_group("Target")
    +130        group_target.add_argument("--host", action="store", metavar="HOST", required=True, type=str, help="IP address or hostname of the SMB Server to connect to.")  
    +131        group_target.add_argument("--port", action="store", metavar="PORT", type=int, default=445, help="Port of the SMB Server to connect to. (default: 445)")
    +132        authconn = mode_create.add_argument_group("Authentication & connection")
    +133        authconn.add_argument("--kdcHost", dest="kdcHost", action="store", metavar="FQDN KDC", help="FQDN of KDC for Kerberos.")
    +134        authconn.add_argument("-d", "--domain", dest="auth_domain", metavar="DOMAIN", action="store", default='.', help="(FQDN) domain to authenticate to.")
    +135        authconn.add_argument("-u", "--user", dest="auth_username", metavar="USER", action="store", help="User to authenticate with.")
    +136        secret = mode_create.add_argument_group()
    +137        cred = secret.add_mutually_exclusive_group()
    +138        cred.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k).")
    +139        cred.add_argument("-p", "--password", dest="auth_password", metavar="PASSWORD", action="store", nargs="?", help="Password to authenticate with.")
    +140        cred.add_argument("-H", "--hashes", dest="auth_hashes", action="store", metavar="[LMHASH:]NTHASH", help="NT/LM hashes, format is LMhash:NThash.")
    +141        cred.add_argument("--aes-key", dest="aesKey", action="store", metavar="hex key", help="AES key to use for Kerberos Authentication (128 or 256 bits).")
    +142        secret.add_argument("-k", "--kerberos", dest="use_kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line.")
    +143
    +144        # Delete
    +145        mode_delete = ModuleArgumentParser(add_help=False, description="Delete the specified session.")
    +146        group_sessions = mode_delete.add_mutually_exclusive_group(required=True)
    +147        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +148        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Delete all sessions.")
    +149
    +150        # Execute
    +151        mode_execute = ModuleArgumentParser(add_help=False, description="Send a smbclient-ng command line in one or more sessions.")
    +152        group_sessions = mode_execute.add_mutually_exclusive_group(required=True)
    +153        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +154        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Execute command in all sessions.")
    +155        mode_execute.add_argument("-c", "--command", type=str, required=True, help="Command to execute in the target sessions.")
    +156
    +157        # List
    +158        mode_list = ModuleArgumentParser(add_help=False, description="List the registered sessions.")
    +159
    +160        # Register subparsers
    +161        subparsers = parser.add_subparsers(help="Action", dest="action", required=True)
    +162        subparsers.add_parser("interact", parents=[mode_interact], help=mode_interact.description)
    +163        subparsers.add_parser("create", parents=[mode_create], help=mode_create.description)
    +164        subparsers.add_parser("delete", parents=[mode_delete], help=mode_delete.description)
    +165        subparsers.add_parser("execute", parents=[mode_execute], help=mode_execute.description)
    +166        subparsers.add_parser("list", parents=[mode_list], help=mode_list.description)
    +167
    +168        try:
    +169            options = parser.parse_args(arguments)
    +170        except SystemExit as e:
    +171            pass
    +172        
    +173        # Process actions
    +174
    +175        # 
    +176        if options.action == "interact":
    +177            if options.session_id is not None:
    +178                if options.session_id in self.sessions.keys():
    +179                    self.logger.info("Switching to session #%d" % options.session_id)
    +180                    self.switch_session(session_id=options.session_id)
    +181                else:
    +182                    self.logger.error("No session with id #%d" % options.session_id)
    +183
    +184        # 
    +185        elif options.action == "create":
    +186            credentials = Credentials(
    +187                domain=options.auth_domain,
    +188                username=options.auth_username,
    +189                password=options.auth_password,
    +190                hashes=options.auth_hashes,
    +191                use_kerberos=options.use_kerberos,
    +192                aesKey=options.aesKey,
    +193                kdcHost=options.kdcHost
    +194            )
    +195            self.create_new_session(
    +196                credentials=credentials,
    +197                host=options.host,
    +198                port=options.port
    +199            )
    +200        
    +201        # 
    +202        elif options.action == "delete":
    +203            if len(options.session_id) != 0:
    +204                for session_id in options.session_id:
    +205                    if session_id in self.sessions.keys():
    +206                        self.logger.info("Closing and deleting session #%d" % session_id)
    +207                        self.delete_session(session_id=session_id)
    +208                    else:
    +209                        self.logger.error("No session with id #%d" % session_id)
    +210            elif options.all == True:
    +211                all_session_ids = list(self.sessions.keys())
    +212                for session_id in all_session_ids:
    +213                    print("[+] Closing and deleting session #%d" % session_id)
    +214                    self.delete_session(session_id=session_id)
    +215
    +216        # 
    +217        elif options.action == "execute":
    +218            if options.command is not None:
    +219                if len(options.session_id) != 0:
    +220                    for session_id in session_id:
    +221                        if session_id in self.sessions.keys():
    +222                            self.logger.info("Executing '%s to session #%d" % (options.command, options.session_id))
    +223                        else:
    +224                            self.logger.error("No session with id #%d" % options.session_id)
    +225                elif options.all == True:
    +226                    all_session_ids = list(self.sessions.keys())
    +227                    for session_id in all_session_ids:
    +228                        pass
    +229        
    +230        # 
    +231        elif options.action == "list":
    +232            for sessionId in sorted(self.sessions.keys()):
    +233                session = self.sessions[sessionId]["smbSession"]
    +234                created_at_str = str(datetime.datetime.fromtimestamp(self.sessions[sessionId]["created_at"]))
    +235                if sessionId == self.current_session_id:
    +236                    if self.config.no_colors:
    +237                        print(f"=> [#{sessionId:<2} - '{session.credentials.domain}\\{session.credentials.username}' @ {session.host}:{session.port}] created at [{created_at_str}] [current session]")
    +238                    else:
    +239                        print(f"\x1b[48;2;50;50;50m=> #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\x1b[48;2;50;50;50m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[48;2;50;50;50m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m\x1b[48;2;50;50;50m [\x1b[93mcurrent session\x1b[0m\x1b[48;2;50;50;50m]\x1b[0m")
    +240                else:
    +241                    print(f"── #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m")
    +242                
    +
    + + +
    +
    + +
    + + class + SessionsManager: + + + +
    + +
     15class SessionsManager(object):
    + 16    """
    + 17    A class to manage SMB sessions.
    + 18
    + 19    This class is responsible for creating, managing, and switching between multiple SMB sessions. It allows for the creation of new sessions with specified credentials and hosts, and provides methods to switch between existing sessions. It also keeps track of the current session and its ID.
    + 20
    + 21    Attributes:
    + 22        next_session_id (int): The next available session ID.
    + 23        current_session (SMBSession): The currently active SMB session.
    + 24        current_session_id (int): The ID of the currently active session.
    + 25        sessions (dict): A dictionary of all active sessions, keyed by their session ID.
    + 26    """
    + 27
    + 28    next_session_id = 1
    + 29    current_session = None
    + 30    current_session_id = None
    + 31    sessions = {}
    + 32
    + 33    def __init__(self, config, logger):
    + 34        self.sessions = {}
    + 35        self.next_session_id = 1
    + 36        self.current_session = None
    + 37        self.current_session_id = None
    + 38
    + 39        self.config = config
    + 40        self.logger = logger
    + 41
    + 42    def create_new_session(self, credentials, host, port=445):
    + 43        """
    + 44        Creates a new session with the given session information.
    + 45
    + 46        Args:
    + 47            session_info (dict): Information necessary to start a new session.
    + 48
    + 49        Returns:
    + 50            None
    + 51        """
    + 52        
    + 53        smbSession = SMBSession(
    + 54            host=host,
    + 55            port=port,
    + 56            credentials=credentials,
    + 57            config=self.config,
    + 58            logger=self.logger
    + 59        )
    + 60        smbSession.init_smb_session()
    + 61        
    + 62        self.sessions[self.next_session_id] = {
    + 63            "id": self.next_session_id,
    + 64            "smbSession": smbSession,
    + 65            "created_at": int(time.time()),
    + 66        }
    + 67        self.switch_session(self.next_session_id)
    + 68        self.next_session_id += 1
    + 69
    + 70    def switch_session(self, session_id):
    + 71        """
    + 72        Switches the current session to the session with the specified ID.
    + 73
    + 74        Args:
    + 75            session_id (int): The ID of the session to switch to.
    + 76
    + 77        Returns:
    + 78            bool: True if the session was successfully switched, False otherwise.
    + 79        """
    + 80
    + 81        if session_id in self.sessions.keys():
    + 82            self.current_session = self.sessions[session_id]["smbSession"]
    + 83            self.current_session_id = session_id
    + 84            return True
    + 85        else:
    + 86            return False
    + 87
    + 88    def delete_session(self, session_id):
    + 89        """
    + 90        Deletes a session with the given session ID.
    + 91
    + 92        Args:
    + 93            session_id (int): The ID of the session to delete.
    + 94
    + 95        Returns:
    + 96            bool: True if the session was successfully deleted, False otherwise.
    + 97        """
    + 98
    + 99        if session_id in self.sessions.keys():
    +100            self.sessions[session_id]["smbSession"].close_smb_session()
    +101            del self.sessions[session_id]
    +102            if self.current_session_id == session_id:
    +103                self.current_session = None
    +104                self.current_session_id = None
    +105            return True
    +106        return False
    +107
    +108    def process_command_line(self, arguments):
    +109        """
    +110        Processes command line arguments to manage SMB sessions.
    +111
    +112        This function parses the command line arguments provided to the application and determines the appropriate action to take,
    +113        such as creating, interacting, deleting, or listing SMB sessions, or executing a command in one or more sessions.
    +114
    +115        Args:
    +116            arguments (list of str): The command line arguments.
    +117
    +118        Returns:
    +119            None
    +120        """
    +121
    +122        parser = ModuleArgumentParser(add_help=True, prog="sessions", description="")
    +123
    +124        # interact
    +125        mode_interact = ModuleArgumentParser(add_help=False, description="Switch to the specified session.")
    +126        mode_interact.add_argument("-i", "--session-id", type=int, default=None, required=True, help="Session ID to interact with.")
    +127
    +128        # Create
    +129        mode_create = ModuleArgumentParser(add_help=False, description="Create a new session.")
    +130        group_target = mode_create.add_argument_group("Target")
    +131        group_target.add_argument("--host", action="store", metavar="HOST", required=True, type=str, help="IP address or hostname of the SMB Server to connect to.")  
    +132        group_target.add_argument("--port", action="store", metavar="PORT", type=int, default=445, help="Port of the SMB Server to connect to. (default: 445)")
    +133        authconn = mode_create.add_argument_group("Authentication & connection")
    +134        authconn.add_argument("--kdcHost", dest="kdcHost", action="store", metavar="FQDN KDC", help="FQDN of KDC for Kerberos.")
    +135        authconn.add_argument("-d", "--domain", dest="auth_domain", metavar="DOMAIN", action="store", default='.', help="(FQDN) domain to authenticate to.")
    +136        authconn.add_argument("-u", "--user", dest="auth_username", metavar="USER", action="store", help="User to authenticate with.")
    +137        secret = mode_create.add_argument_group()
    +138        cred = secret.add_mutually_exclusive_group()
    +139        cred.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k).")
    +140        cred.add_argument("-p", "--password", dest="auth_password", metavar="PASSWORD", action="store", nargs="?", help="Password to authenticate with.")
    +141        cred.add_argument("-H", "--hashes", dest="auth_hashes", action="store", metavar="[LMHASH:]NTHASH", help="NT/LM hashes, format is LMhash:NThash.")
    +142        cred.add_argument("--aes-key", dest="aesKey", action="store", metavar="hex key", help="AES key to use for Kerberos Authentication (128 or 256 bits).")
    +143        secret.add_argument("-k", "--kerberos", dest="use_kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line.")
    +144
    +145        # Delete
    +146        mode_delete = ModuleArgumentParser(add_help=False, description="Delete the specified session.")
    +147        group_sessions = mode_delete.add_mutually_exclusive_group(required=True)
    +148        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +149        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Delete all sessions.")
    +150
    +151        # Execute
    +152        mode_execute = ModuleArgumentParser(add_help=False, description="Send a smbclient-ng command line in one or more sessions.")
    +153        group_sessions = mode_execute.add_mutually_exclusive_group(required=True)
    +154        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +155        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Execute command in all sessions.")
    +156        mode_execute.add_argument("-c", "--command", type=str, required=True, help="Command to execute in the target sessions.")
    +157
    +158        # List
    +159        mode_list = ModuleArgumentParser(add_help=False, description="List the registered sessions.")
    +160
    +161        # Register subparsers
    +162        subparsers = parser.add_subparsers(help="Action", dest="action", required=True)
    +163        subparsers.add_parser("interact", parents=[mode_interact], help=mode_interact.description)
    +164        subparsers.add_parser("create", parents=[mode_create], help=mode_create.description)
    +165        subparsers.add_parser("delete", parents=[mode_delete], help=mode_delete.description)
    +166        subparsers.add_parser("execute", parents=[mode_execute], help=mode_execute.description)
    +167        subparsers.add_parser("list", parents=[mode_list], help=mode_list.description)
    +168
    +169        try:
    +170            options = parser.parse_args(arguments)
    +171        except SystemExit as e:
    +172            pass
    +173        
    +174        # Process actions
    +175
    +176        # 
    +177        if options.action == "interact":
    +178            if options.session_id is not None:
    +179                if options.session_id in self.sessions.keys():
    +180                    self.logger.info("Switching to session #%d" % options.session_id)
    +181                    self.switch_session(session_id=options.session_id)
    +182                else:
    +183                    self.logger.error("No session with id #%d" % options.session_id)
    +184
    +185        # 
    +186        elif options.action == "create":
    +187            credentials = Credentials(
    +188                domain=options.auth_domain,
    +189                username=options.auth_username,
    +190                password=options.auth_password,
    +191                hashes=options.auth_hashes,
    +192                use_kerberos=options.use_kerberos,
    +193                aesKey=options.aesKey,
    +194                kdcHost=options.kdcHost
    +195            )
    +196            self.create_new_session(
    +197                credentials=credentials,
    +198                host=options.host,
    +199                port=options.port
    +200            )
    +201        
    +202        # 
    +203        elif options.action == "delete":
    +204            if len(options.session_id) != 0:
    +205                for session_id in options.session_id:
    +206                    if session_id in self.sessions.keys():
    +207                        self.logger.info("Closing and deleting session #%d" % session_id)
    +208                        self.delete_session(session_id=session_id)
    +209                    else:
    +210                        self.logger.error("No session with id #%d" % session_id)
    +211            elif options.all == True:
    +212                all_session_ids = list(self.sessions.keys())
    +213                for session_id in all_session_ids:
    +214                    print("[+] Closing and deleting session #%d" % session_id)
    +215                    self.delete_session(session_id=session_id)
    +216
    +217        # 
    +218        elif options.action == "execute":
    +219            if options.command is not None:
    +220                if len(options.session_id) != 0:
    +221                    for session_id in session_id:
    +222                        if session_id in self.sessions.keys():
    +223                            self.logger.info("Executing '%s to session #%d" % (options.command, options.session_id))
    +224                        else:
    +225                            self.logger.error("No session with id #%d" % options.session_id)
    +226                elif options.all == True:
    +227                    all_session_ids = list(self.sessions.keys())
    +228                    for session_id in all_session_ids:
    +229                        pass
    +230        
    +231        # 
    +232        elif options.action == "list":
    +233            for sessionId in sorted(self.sessions.keys()):
    +234                session = self.sessions[sessionId]["smbSession"]
    +235                created_at_str = str(datetime.datetime.fromtimestamp(self.sessions[sessionId]["created_at"]))
    +236                if sessionId == self.current_session_id:
    +237                    if self.config.no_colors:
    +238                        print(f"=> [#{sessionId:<2} - '{session.credentials.domain}\\{session.credentials.username}' @ {session.host}:{session.port}] created at [{created_at_str}] [current session]")
    +239                    else:
    +240                        print(f"\x1b[48;2;50;50;50m=> #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\x1b[48;2;50;50;50m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[48;2;50;50;50m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m\x1b[48;2;50;50;50m [\x1b[93mcurrent session\x1b[0m\x1b[48;2;50;50;50m]\x1b[0m")
    +241                else:
    +242                    print(f"── #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m")
    +
    + + +

    A class to manage SMB sessions.

    + +

    This class is responsible for creating, managing, and switching between multiple SMB sessions. It allows for the creation of new sessions with specified credentials and hosts, and provides methods to switch between existing sessions. It also keeps track of the current session and its ID.

    + +

    Attributes: + next_session_id (int): The next available session ID. + current_session (SMBSession): The currently active SMB session. + current_session_id (int): The ID of the currently active session. + sessions (dict): A dictionary of all active sessions, keyed by their session ID.

    +
    + + +
    + +
    + + SessionsManager(config, logger) + + + +
    + +
    33    def __init__(self, config, logger):
    +34        self.sessions = {}
    +35        self.next_session_id = 1
    +36        self.current_session = None
    +37        self.current_session_id = None
    +38
    +39        self.config = config
    +40        self.logger = logger
    +
    + + + + +
    +
    +
    + next_session_id = +1 + + +
    + + + + +
    +
    +
    + current_session = +None + + +
    + + + + +
    +
    +
    + current_session_id = +None + + +
    + + + + +
    +
    +
    + sessions = +{} + + +
    + + + + +
    +
    +
    + config + + +
    + + + + +
    +
    +
    + logger + + +
    + + + + +
    +
    + +
    + + def + create_new_session(self, credentials, host, port=445): + + + +
    + +
    42    def create_new_session(self, credentials, host, port=445):
    +43        """
    +44        Creates a new session with the given session information.
    +45
    +46        Args:
    +47            session_info (dict): Information necessary to start a new session.
    +48
    +49        Returns:
    +50            None
    +51        """
    +52        
    +53        smbSession = SMBSession(
    +54            host=host,
    +55            port=port,
    +56            credentials=credentials,
    +57            config=self.config,
    +58            logger=self.logger
    +59        )
    +60        smbSession.init_smb_session()
    +61        
    +62        self.sessions[self.next_session_id] = {
    +63            "id": self.next_session_id,
    +64            "smbSession": smbSession,
    +65            "created_at": int(time.time()),
    +66        }
    +67        self.switch_session(self.next_session_id)
    +68        self.next_session_id += 1
    +
    + + +

    Creates a new session with the given session information.

    + +

    Args: + session_info (dict): Information necessary to start a new session.

    + +

    Returns: + None

    +
    + + +
    +
    + +
    + + def + switch_session(self, session_id): + + + +
    + +
    70    def switch_session(self, session_id):
    +71        """
    +72        Switches the current session to the session with the specified ID.
    +73
    +74        Args:
    +75            session_id (int): The ID of the session to switch to.
    +76
    +77        Returns:
    +78            bool: True if the session was successfully switched, False otherwise.
    +79        """
    +80
    +81        if session_id in self.sessions.keys():
    +82            self.current_session = self.sessions[session_id]["smbSession"]
    +83            self.current_session_id = session_id
    +84            return True
    +85        else:
    +86            return False
    +
    + + +

    Switches the current session to the session with the specified ID.

    + +

    Args: + session_id (int): The ID of the session to switch to.

    + +

    Returns: + bool: True if the session was successfully switched, False otherwise.

    +
    + + +
    +
    + +
    + + def + delete_session(self, session_id): + + + +
    + +
     88    def delete_session(self, session_id):
    + 89        """
    + 90        Deletes a session with the given session ID.
    + 91
    + 92        Args:
    + 93            session_id (int): The ID of the session to delete.
    + 94
    + 95        Returns:
    + 96            bool: True if the session was successfully deleted, False otherwise.
    + 97        """
    + 98
    + 99        if session_id in self.sessions.keys():
    +100            self.sessions[session_id]["smbSession"].close_smb_session()
    +101            del self.sessions[session_id]
    +102            if self.current_session_id == session_id:
    +103                self.current_session = None
    +104                self.current_session_id = None
    +105            return True
    +106        return False
    +
    + + +

    Deletes a session with the given session ID.

    + +

    Args: + session_id (int): The ID of the session to delete.

    + +

    Returns: + bool: True if the session was successfully deleted, False otherwise.

    +
    + + +
    +
    + +
    + + def + process_command_line(self, arguments): + + + +
    + +
    108    def process_command_line(self, arguments):
    +109        """
    +110        Processes command line arguments to manage SMB sessions.
    +111
    +112        This function parses the command line arguments provided to the application and determines the appropriate action to take,
    +113        such as creating, interacting, deleting, or listing SMB sessions, or executing a command in one or more sessions.
    +114
    +115        Args:
    +116            arguments (list of str): The command line arguments.
    +117
    +118        Returns:
    +119            None
    +120        """
    +121
    +122        parser = ModuleArgumentParser(add_help=True, prog="sessions", description="")
    +123
    +124        # interact
    +125        mode_interact = ModuleArgumentParser(add_help=False, description="Switch to the specified session.")
    +126        mode_interact.add_argument("-i", "--session-id", type=int, default=None, required=True, help="Session ID to interact with.")
    +127
    +128        # Create
    +129        mode_create = ModuleArgumentParser(add_help=False, description="Create a new session.")
    +130        group_target = mode_create.add_argument_group("Target")
    +131        group_target.add_argument("--host", action="store", metavar="HOST", required=True, type=str, help="IP address or hostname of the SMB Server to connect to.")  
    +132        group_target.add_argument("--port", action="store", metavar="PORT", type=int, default=445, help="Port of the SMB Server to connect to. (default: 445)")
    +133        authconn = mode_create.add_argument_group("Authentication & connection")
    +134        authconn.add_argument("--kdcHost", dest="kdcHost", action="store", metavar="FQDN KDC", help="FQDN of KDC for Kerberos.")
    +135        authconn.add_argument("-d", "--domain", dest="auth_domain", metavar="DOMAIN", action="store", default='.', help="(FQDN) domain to authenticate to.")
    +136        authconn.add_argument("-u", "--user", dest="auth_username", metavar="USER", action="store", help="User to authenticate with.")
    +137        secret = mode_create.add_argument_group()
    +138        cred = secret.add_mutually_exclusive_group()
    +139        cred.add_argument("--no-pass", action="store_true", help="Don't ask for password (useful for -k).")
    +140        cred.add_argument("-p", "--password", dest="auth_password", metavar="PASSWORD", action="store", nargs="?", help="Password to authenticate with.")
    +141        cred.add_argument("-H", "--hashes", dest="auth_hashes", action="store", metavar="[LMHASH:]NTHASH", help="NT/LM hashes, format is LMhash:NThash.")
    +142        cred.add_argument("--aes-key", dest="aesKey", action="store", metavar="hex key", help="AES key to use for Kerberos Authentication (128 or 256 bits).")
    +143        secret.add_argument("-k", "--kerberos", dest="use_kerberos", action="store_true", help="Use Kerberos authentication. Grabs credentials from .ccache file (KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ones specified in the command line.")
    +144
    +145        # Delete
    +146        mode_delete = ModuleArgumentParser(add_help=False, description="Delete the specified session.")
    +147        group_sessions = mode_delete.add_mutually_exclusive_group(required=True)
    +148        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +149        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Delete all sessions.")
    +150
    +151        # Execute
    +152        mode_execute = ModuleArgumentParser(add_help=False, description="Send a smbclient-ng command line in one or more sessions.")
    +153        group_sessions = mode_execute.add_mutually_exclusive_group(required=True)
    +154        group_sessions.add_argument("-i", "--session-id", type=int, default=[], action="append", help="One or more ID of sessions to target.")
    +155        group_sessions.add_argument("-a", "--all", default=False, action="store_true", help="Execute command in all sessions.")
    +156        mode_execute.add_argument("-c", "--command", type=str, required=True, help="Command to execute in the target sessions.")
    +157
    +158        # List
    +159        mode_list = ModuleArgumentParser(add_help=False, description="List the registered sessions.")
    +160
    +161        # Register subparsers
    +162        subparsers = parser.add_subparsers(help="Action", dest="action", required=True)
    +163        subparsers.add_parser("interact", parents=[mode_interact], help=mode_interact.description)
    +164        subparsers.add_parser("create", parents=[mode_create], help=mode_create.description)
    +165        subparsers.add_parser("delete", parents=[mode_delete], help=mode_delete.description)
    +166        subparsers.add_parser("execute", parents=[mode_execute], help=mode_execute.description)
    +167        subparsers.add_parser("list", parents=[mode_list], help=mode_list.description)
    +168
    +169        try:
    +170            options = parser.parse_args(arguments)
    +171        except SystemExit as e:
    +172            pass
    +173        
    +174        # Process actions
    +175
    +176        # 
    +177        if options.action == "interact":
    +178            if options.session_id is not None:
    +179                if options.session_id in self.sessions.keys():
    +180                    self.logger.info("Switching to session #%d" % options.session_id)
    +181                    self.switch_session(session_id=options.session_id)
    +182                else:
    +183                    self.logger.error("No session with id #%d" % options.session_id)
    +184
    +185        # 
    +186        elif options.action == "create":
    +187            credentials = Credentials(
    +188                domain=options.auth_domain,
    +189                username=options.auth_username,
    +190                password=options.auth_password,
    +191                hashes=options.auth_hashes,
    +192                use_kerberos=options.use_kerberos,
    +193                aesKey=options.aesKey,
    +194                kdcHost=options.kdcHost
    +195            )
    +196            self.create_new_session(
    +197                credentials=credentials,
    +198                host=options.host,
    +199                port=options.port
    +200            )
    +201        
    +202        # 
    +203        elif options.action == "delete":
    +204            if len(options.session_id) != 0:
    +205                for session_id in options.session_id:
    +206                    if session_id in self.sessions.keys():
    +207                        self.logger.info("Closing and deleting session #%d" % session_id)
    +208                        self.delete_session(session_id=session_id)
    +209                    else:
    +210                        self.logger.error("No session with id #%d" % session_id)
    +211            elif options.all == True:
    +212                all_session_ids = list(self.sessions.keys())
    +213                for session_id in all_session_ids:
    +214                    print("[+] Closing and deleting session #%d" % session_id)
    +215                    self.delete_session(session_id=session_id)
    +216
    +217        # 
    +218        elif options.action == "execute":
    +219            if options.command is not None:
    +220                if len(options.session_id) != 0:
    +221                    for session_id in session_id:
    +222                        if session_id in self.sessions.keys():
    +223                            self.logger.info("Executing '%s to session #%d" % (options.command, options.session_id))
    +224                        else:
    +225                            self.logger.error("No session with id #%d" % options.session_id)
    +226                elif options.all == True:
    +227                    all_session_ids = list(self.sessions.keys())
    +228                    for session_id in all_session_ids:
    +229                        pass
    +230        
    +231        # 
    +232        elif options.action == "list":
    +233            for sessionId in sorted(self.sessions.keys()):
    +234                session = self.sessions[sessionId]["smbSession"]
    +235                created_at_str = str(datetime.datetime.fromtimestamp(self.sessions[sessionId]["created_at"]))
    +236                if sessionId == self.current_session_id:
    +237                    if self.config.no_colors:
    +238                        print(f"=> [#{sessionId:<2} - '{session.credentials.domain}\\{session.credentials.username}' @ {session.host}:{session.port}] created at [{created_at_str}] [current session]")
    +239                    else:
    +240                        print(f"\x1b[48;2;50;50;50m=> #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\x1b[48;2;50;50;50m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[48;2;50;50;50m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m\x1b[48;2;50;50;50m [\x1b[93mcurrent session\x1b[0m\x1b[48;2;50;50;50m]\x1b[0m")
    +241                else:
    +242                    print(f"── #{sessionId:<2} - '\x1b[1;96m{session.credentials.domain}\x1b[0m\\\x1b[1;96m{session.credentials.username}\x1b[0m\x1b[1m' @ {session.host}:{session.port} created at [{created_at_str}]\x1b[0m")
    +
    + + +

    Processes command line arguments to manage SMB sessions.

    + +

    This function parses the command line arguments provided to the application and determines the appropriate action to take, +such as creating, interacting, deleting, or listing SMB sessions, or executing a command in one or more sessions.

    + +

    Args: + arguments (list of str): The command line arguments.

    + +

    Returns: + None

    +
    + + +
    +
    +
    + + \ No newline at end of file diff --git a/documentation/smbclientng/core/utils.html b/documentation/smbclientng/core/utils.html index 068c3e3..66bafb5 100644 --- a/documentation/smbclientng/core/utils.html +++ b/documentation/smbclientng/core/utils.html @@ -48,6 +48,15 @@

    API Documentation

  • local_tree
  • +
  • + resolve_local_files +
  • +
  • + resolve_remote_files +
  • +
  • + is_port_open +
  • @@ -77,321 +86,425 @@

    6 7 8import datetime - 9import os + 9import fnmatch 10import ntpath - 11import re - 12import stat - 13 - 14 - 15def parse_lm_nt_hashes(lm_nt_hashes_string): - 16 """ - 17 Parse the input string containing LM and NT hash values and return them separately. - 18 - 19 This function takes a string containing LM and NT hash values, typically separated by a colon (:). - 20 It returns the LM and NT hash values as separate strings. If only one hash value is provided, it is - 21 assumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are - 22 found, both return values are empty strings. - 23 - 24 Args: - 25 lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon. - 26 - 27 Returns: - 28 tuple: A tuple containing two strings (lm_hash_value, nt_hash_value). - 29 - lm_hash_value: The LM hash value or its default if not provided. - 30 - nt_hash_value: The NT hash value or its default if not provided. - 31 - 32 Extracted from p0dalirius/sectools library - 33 Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24 - 34 """ - 35 - 36 lm_hash_value, nt_hash_value = "", "" - 37 if lm_nt_hashes_string is not None: - 38 matched = re.match("([0-9a-f]{32})?(:)?([0-9a-f]{32})?", lm_nt_hashes_string.strip().lower()) - 39 m_lm_hash, m_sep, m_nt_hash = matched.groups() - 40 if m_lm_hash is None and m_sep is None and m_nt_hash is None: - 41 lm_hash_value, nt_hash_value = "", "" - 42 elif m_lm_hash is None and m_nt_hash is not None: - 43 lm_hash_value = "aad3b435b51404eeaad3b435b51404ee" - 44 nt_hash_value = m_nt_hash - 45 elif m_lm_hash is not None and m_nt_hash is None: - 46 lm_hash_value = m_lm_hash - 47 nt_hash_value = "31d6cfe0d16ae931b73c59d7e0c089c0" - 48 return lm_hash_value, nt_hash_value - 49 - 50 - 51def b_filesize(l): - 52 """ - 53 Convert a file size from bytes to a more readable format using the largest appropriate unit. - 54 - 55 This function takes an integer representing a file size in bytes and converts it to a human-readable - 56 string using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to - 57 two decimal places. - 58 - 59 Args: - 60 l (int): The file size in bytes. - 61 - 62 Returns: - 63 str: A string representing the file size in a more readable format, including the appropriate unit. - 64 """ - 65 - 66 units = ['B','kB','MB','GB','TB','PB'] - 67 for k in range(len(units)): - 68 if l < (1024**(k+1)): - 69 break - 70 return "%4.2f %s" % (round(l/(1024**(k)),2), units[k]) - 71 - 72 - 73def unix_permissions(entryname): - 74 """ - 75 Generate a string representing the Unix-style permissions for a given file or directory. - 76 - 77 This function uses the os.lstat() method to retrieve the status of the specified file or directory, - 78 then constructs a string that represents the Unix-style permissions based on the mode of the file. - 79 - 80 Args: - 81 entryname (str): The path to the file or directory for which permissions are being determined. - 82 - 83 Returns: - 84 str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--'). - 85 The first character is either 'd' (directory), '-' (not a directory), followed by - 86 three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group, - 87 and others respectively. - 88 """ - 89 - 90 mode = os.lstat(entryname).st_mode - 91 permissions = [] - 92 - 93 permissions.append('d' if stat.S_ISDIR(mode) else '-') + 11import os + 12import re + 13import socket + 14import stat + 15 + 16 + 17def parse_lm_nt_hashes(lm_nt_hashes_string): + 18 """ + 19 Parse the input string containing LM and NT hash values and return them separately. + 20 + 21 This function takes a string containing LM and NT hash values, typically separated by a colon (:). + 22 It returns the LM and NT hash values as separate strings. If only one hash value is provided, it is + 23 assumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are + 24 found, both return values are empty strings. + 25 + 26 Args: + 27 lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon. + 28 + 29 Returns: + 30 tuple: A tuple containing two strings (lm_hash_value, nt_hash_value). + 31 - lm_hash_value: The LM hash value or its default if not provided. + 32 - nt_hash_value: The NT hash value or its default if not provided. + 33 + 34 Extracted from p0dalirius/sectools library + 35 Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24 + 36 """ + 37 + 38 lm_hash_value, nt_hash_value = "", "" + 39 if lm_nt_hashes_string is not None: + 40 matched = re.match("([0-9a-f]{32})?(:)?([0-9a-f]{32})?", lm_nt_hashes_string.strip().lower()) + 41 m_lm_hash, m_sep, m_nt_hash = matched.groups() + 42 if m_lm_hash is None and m_sep is None and m_nt_hash is None: + 43 lm_hash_value, nt_hash_value = "", "" + 44 elif m_lm_hash is None and m_nt_hash is not None: + 45 lm_hash_value = "aad3b435b51404eeaad3b435b51404ee" + 46 nt_hash_value = m_nt_hash + 47 elif m_lm_hash is not None and m_nt_hash is None: + 48 lm_hash_value = m_lm_hash + 49 nt_hash_value = "31d6cfe0d16ae931b73c59d7e0c089c0" + 50 return lm_hash_value, nt_hash_value + 51 + 52 + 53def b_filesize(l): + 54 """ + 55 Convert a file size from bytes to a more readable format using the largest appropriate unit. + 56 + 57 This function takes an integer representing a file size in bytes and converts it to a human-readable + 58 string using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to + 59 two decimal places. + 60 + 61 Args: + 62 l (int): The file size in bytes. + 63 + 64 Returns: + 65 str: A string representing the file size in a more readable format, including the appropriate unit. + 66 """ + 67 + 68 units = ['B','kB','MB','GB','TB','PB'] + 69 for k in range(len(units)): + 70 if l < (1024**(k+1)): + 71 break + 72 return "%4.2f %s" % (round(l/(1024**(k)),2), units[k]) + 73 + 74 + 75def unix_permissions(entryname): + 76 """ + 77 Generate a string representing the Unix-style permissions for a given file or directory. + 78 + 79 This function uses the os.lstat() method to retrieve the status of the specified file or directory, + 80 then constructs a string that represents the Unix-style permissions based on the mode of the file. + 81 + 82 Args: + 83 entryname (str): The path to the file or directory for which permissions are being determined. + 84 + 85 Returns: + 86 str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--'). + 87 The first character is either 'd' (directory), '-' (not a directory), followed by + 88 three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group, + 89 and others respectively. + 90 """ + 91 + 92 mode = os.lstat(entryname).st_mode + 93 permissions = [] 94 - 95 permissions.append('r' if mode & stat.S_IRUSR else '-') - 96 permissions.append('w' if mode & stat.S_IWUSR else '-') - 97 permissions.append('x' if mode & stat.S_IXUSR else '-') - 98 - 99 permissions.append('r' if mode & stat.S_IRGRP else '-') -100 permissions.append('w' if mode & stat.S_IWGRP else '-') -101 permissions.append('x' if mode & stat.S_IXGRP else '-') -102 -103 permissions.append('r' if mode & stat.S_IROTH else '-') -104 permissions.append('w' if mode & stat.S_IWOTH else '-') -105 permissions.append('x' if mode & stat.S_IXOTH else '-') -106 -107 return ''.join(permissions) + 95 permissions.append('d' if stat.S_ISDIR(mode) else '-') + 96 + 97 permissions.append('r' if mode & stat.S_IRUSR else '-') + 98 permissions.append('w' if mode & stat.S_IWUSR else '-') + 99 permissions.append('x' if mode & stat.S_IXUSR else '-') +100 +101 permissions.append('r' if mode & stat.S_IRGRP else '-') +102 permissions.append('w' if mode & stat.S_IWGRP else '-') +103 permissions.append('x' if mode & stat.S_IXGRP else '-') +104 +105 permissions.append('r' if mode & stat.S_IROTH else '-') +106 permissions.append('w' if mode & stat.S_IWOTH else '-') +107 permissions.append('x' if mode & stat.S_IXOTH else '-') 108 -109 -110def STYPE_MASK(stype_value): -111 """ -112 Extracts the share type flags from a given share type value. -113 -114 This function uses bitwise operations to determine which share type flags are set in the provided `stype_value`. -115 It checks against known share type flags and returns a list of the flags that are set. -116 -117 Parameters: -118 stype_value (int): The share type value to analyze, typically obtained from SMB share properties. -119 -120 Returns: -121 list: A list of strings, where each string represents a share type flag that is set in the input value. -122 """ -123 -124 known_flags = { -125 ## One of the following values may be specified. You can isolate these values by using the STYPE_MASK value. -126 # Disk drive. -127 "STYPE_DISKTREE": 0x0, -128 -129 # Print queue. -130 "STYPE_PRINTQ": 0x1, -131 -132 # Communication device. -133 "STYPE_DEVICE": 0x2, -134 -135 # Interprocess communication (IPC). -136 "STYPE_IPC": 0x3, -137 -138 ## In addition, one or both of the following values may be specified. -139 # Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$). -140 # Can also refer to administrative shares such as C$, D$, E$, and so forth. For more information, see Network Share Functions. -141 "STYPE_SPECIAL": 0x80000000, -142 -143 # A temporary share. -144 "STYPE_TEMPORARY": 0x40000000 -145 } -146 flags = [] -147 if (stype_value & 0b11) == known_flags["STYPE_DISKTREE"]: -148 flags.append("STYPE_DISKTREE") -149 elif (stype_value & 0b11) == known_flags["STYPE_PRINTQ"]: -150 flags.append("STYPE_PRINTQ") -151 elif (stype_value & 0b11) == known_flags["STYPE_DEVICE"]: -152 flags.append("STYPE_DEVICE") -153 elif (stype_value & 0b11) == known_flags["STYPE_IPC"]: -154 flags.append("STYPE_IPC") -155 if (stype_value & known_flags["STYPE_SPECIAL"]) == known_flags["STYPE_SPECIAL"]: -156 flags.append("STYPE_SPECIAL") -157 if (stype_value & known_flags["STYPE_TEMPORARY"]) == known_flags["STYPE_TEMPORARY"]: -158 flags.append("STYPE_TEMPORARY") -159 return flags -160 -161 -162def windows_ls_entry(entry, config, pathToPrint=None): -163 """ -164 This function generates a metadata string based on the attributes of the provided entry object. -165 -166 Parameters: -167 entry (object): An object representing a file or directory entry. -168 -169 Returns: -170 str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary. -171 """ -172 -173 if pathToPrint is not None: -174 pathToPrint = pathToPrint + ntpath.sep + entry.get_longname() -175 else: -176 pathToPrint = entry.get_longname() -177 -178 meta_string = "" -179 meta_string += ("d" if entry.is_directory() else "-") -180 meta_string += ("a" if entry.is_archive() else "-") -181 meta_string += ("c" if entry.is_compressed() else "-") -182 meta_string += ("h" if entry.is_hidden() else "-") -183 meta_string += ("n" if entry.is_normal() else "-") -184 meta_string += ("r" if entry.is_readonly() else "-") -185 meta_string += ("s" if entry.is_system() else "-") -186 meta_string += ("t" if entry.is_temporary() else "-") -187 -188 size_str = b_filesize(entry.get_filesize()) +109 return ''.join(permissions) +110 +111 +112def STYPE_MASK(stype_value): +113 """ +114 Extracts the share type flags from a given share type value. +115 +116 This function uses bitwise operations to determine which share type flags are set in the provided `stype_value`. +117 It checks against known share type flags and returns a list of the flags that are set. +118 +119 Parameters: +120 stype_value (int): The share type value to analyze, typically obtained from SMB share properties. +121 +122 Returns: +123 list: A list of strings, where each string represents a share type flag that is set in the input value. +124 """ +125 +126 known_flags = { +127 ## One of the following values may be specified. You can isolate these values by using the STYPE_MASK value. +128 # Disk drive. +129 "STYPE_DISKTREE": 0x0, +130 +131 # Print queue. +132 "STYPE_PRINTQ": 0x1, +133 +134 # Communication device. +135 "STYPE_DEVICE": 0x2, +136 +137 # Interprocess communication (IPC). +138 "STYPE_IPC": 0x3, +139 +140 ## In addition, one or both of the following values may be specified. +141 # Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$). +142 # Can also refer to administrative shares such as C$, D$, E$, and so forth. For more information, see Network Share Functions. +143 "STYPE_SPECIAL": 0x80000000, +144 +145 # A temporary share. +146 "STYPE_TEMPORARY": 0x40000000 +147 } +148 flags = [] +149 if (stype_value & 0b11) == known_flags["STYPE_DISKTREE"]: +150 flags.append("STYPE_DISKTREE") +151 elif (stype_value & 0b11) == known_flags["STYPE_PRINTQ"]: +152 flags.append("STYPE_PRINTQ") +153 elif (stype_value & 0b11) == known_flags["STYPE_DEVICE"]: +154 flags.append("STYPE_DEVICE") +155 elif (stype_value & 0b11) == known_flags["STYPE_IPC"]: +156 flags.append("STYPE_IPC") +157 if (stype_value & known_flags["STYPE_SPECIAL"]) == known_flags["STYPE_SPECIAL"]: +158 flags.append("STYPE_SPECIAL") +159 if (stype_value & known_flags["STYPE_TEMPORARY"]) == known_flags["STYPE_TEMPORARY"]: +160 flags.append("STYPE_TEMPORARY") +161 return flags +162 +163 +164def windows_ls_entry(entry, config, pathToPrint=None): +165 """ +166 This function generates a metadata string based on the attributes of the provided entry object. +167 +168 Parameters: +169 entry (object): An object representing a file or directory entry. +170 +171 Returns: +172 str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary. +173 """ +174 +175 if pathToPrint is not None: +176 pathToPrint = pathToPrint + ntpath.sep + entry.get_longname() +177 else: +178 pathToPrint = entry.get_longname() +179 +180 meta_string = "" +181 meta_string += ("d" if entry.is_directory() else "-") +182 meta_string += ("a" if entry.is_archive() else "-") +183 meta_string += ("c" if entry.is_compressed() else "-") +184 meta_string += ("h" if entry.is_hidden() else "-") +185 meta_string += ("n" if entry.is_normal() else "-") +186 meta_string += ("r" if entry.is_readonly() else "-") +187 meta_string += ("s" if entry.is_system() else "-") +188 meta_string += ("t" if entry.is_temporary() else "-") 189 -190 date_str = datetime.datetime.fromtimestamp(entry.get_atime_epoch()).strftime("%Y-%m-%d %H:%M") -191 -192 if entry.is_directory(): -193 if config.no_colors: -194 print("%s %10s %s %s\\" % (meta_string, size_str, date_str, pathToPrint)) -195 else: -196 print("%s %10s %s \x1b[1;96m%s\x1b[0m\\" % (meta_string, size_str, date_str, pathToPrint)) -197 else: -198 if config.no_colors: -199 print("%s %10s %s %s" % (meta_string, size_str, date_str, pathToPrint)) -200 else: -201 print("%s %10s %s \x1b[1m%s\x1b[0m" % (meta_string, size_str, date_str, pathToPrint)) -202 -203 -204def local_tree(path, config): -205 """ -206 This function recursively lists the contents of a directory in a tree-like format. -207 -208 Parameters: -209 path (str): The path to the directory to list. -210 config (object): Configuration settings which may affect the output, such as whether to use colors. -211 -212 Returns: -213 None: This function does not return anything but prints the directory tree to the console. -214 """ -215 -216 def recurse_action(base_dir="", path=[], prompt=[]): -217 bars = ["│ ", "├── ", "└── "] -218 -219 local_path = os.path.normpath(base_dir + os.path.sep + os.path.sep.join(path) + os.path.sep) +190 size_str = b_filesize(entry.get_filesize()) +191 +192 date_str = datetime.datetime.fromtimestamp(entry.get_atime_epoch()).strftime("%Y-%m-%d %H:%M") +193 +194 if entry.is_directory(): +195 if config.no_colors: +196 print("%s %10s %s %s\\" % (meta_string, size_str, date_str, pathToPrint)) +197 else: +198 print("%s %10s %s \x1b[1;96m%s\x1b[0m\\" % (meta_string, size_str, date_str, pathToPrint)) +199 else: +200 if config.no_colors: +201 print("%s %10s %s %s" % (meta_string, size_str, date_str, pathToPrint)) +202 else: +203 print("%s %10s %s \x1b[1m%s\x1b[0m" % (meta_string, size_str, date_str, pathToPrint)) +204 +205 +206def local_tree(path, config): +207 """ +208 This function recursively lists the contents of a directory in a tree-like format. +209 +210 Parameters: +211 path (str): The path to the directory to list. +212 config (object): Configuration settings which may affect the output, such as whether to use colors. +213 +214 Returns: +215 None: This function does not return anything but prints the directory tree to the console. +216 """ +217 +218 def recurse_action(base_dir="", path=[], prompt=[]): +219 bars = ["│ ", "├── ", "└── "] 220 -221 entries = [] -222 try: -223 entries = os.listdir(local_path) -224 except Exception as err: -225 if config.no_colors: -226 print("%s%s" % (''.join(prompt+[bars[2]]), err)) -227 else: -228 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), err)) -229 return -230 -231 entries = sorted(entries) +221 local_path = os.path.normpath(base_dir + os.path.sep + os.path.sep.join(path) + os.path.sep) +222 +223 entries = [] +224 try: +225 entries = os.listdir(local_path) +226 except Exception as err: +227 if config.no_colors: +228 print("%s%s" % (''.join(prompt+[bars[2]]), err)) +229 else: +230 print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), err)) +231 return 232 -233 # -234 if len(entries) > 1: -235 index = 0 -236 for entry in entries: -237 index += 1 -238 # This is the first entry -239 if index == 0: -240 if os.path.isdir(local_path + os.path.sep + entry): -241 if config.no_colors: -242 print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) -243 else: -244 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) -245 recurse_action( -246 base_dir=base_dir, -247 path=path+[entry], -248 prompt=prompt+["│ "] -249 ) -250 else: -251 if config.no_colors: -252 print("%s%s" % (''.join(prompt+[bars[1]]), entry)) -253 else: -254 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry)) -255 -256 # This is the last entry -257 elif index == len(entries): -258 if os.path.isdir(local_path + os.path.sep + entry): -259 if config.no_colors: -260 print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) -261 else: -262 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) -263 recurse_action( -264 base_dir=base_dir, -265 path=path+[entry], -266 prompt=prompt+[" "] -267 ) -268 else: -269 if config.no_colors: -270 print("%s%s" % (''.join(prompt+[bars[2]]), entry)) -271 else: -272 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry)) -273 -274 # These are entries in the middle -275 else: -276 if os.path.isdir(local_path + os.path.sep + entry): -277 if config.no_colors: -278 print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) -279 else: -280 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) -281 recurse_action( -282 base_dir=base_dir, -283 path=path+[entry], -284 prompt=prompt+["│ "] -285 ) -286 else: -287 if config.no_colors: -288 print("%s%s" % (''.join(prompt+[bars[1]]), entry)) -289 else: -290 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry)) -291 -292 # -293 elif len(entries) == 1: -294 entry = entries[0] -295 if os.path.isdir(local_path + os.path.sep + entry): -296 if config.no_colors: -297 print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) -298 else: -299 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) -300 recurse_action( -301 base_dir=base_dir, -302 path=path+[entry], -303 prompt=prompt+[" "] -304 ) -305 else: -306 if config.no_colors: -307 print("%s%s" % (''.join(prompt+[bars[2]]), entry)) -308 else: -309 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry)) -310 -311 # Entrypoint -312 try: -313 if config.no_colors: -314 print("%s%s" % (path, os.path.sep)) -315 else: -316 print("\x1b[1;96m%s\x1b[0m%s" % (path, os.path.sep)) -317 recurse_action( -318 base_dir=os.getcwd(), -319 path=[path], -320 prompt=[""] -321 ) -322 except (BrokenPipeError, KeyboardInterrupt) as e: -323 print("[!] Interrupted.") +233 entries = sorted(entries) +234 +235 # +236 if len(entries) > 1: +237 index = 0 +238 for entry in entries: +239 index += 1 +240 # This is the first entry +241 if index == 0: +242 if os.path.isdir(local_path + os.path.sep + entry): +243 if config.no_colors: +244 print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) +245 else: +246 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) +247 recurse_action( +248 base_dir=base_dir, +249 path=path+[entry], +250 prompt=prompt+["│ "] +251 ) +252 else: +253 if config.no_colors: +254 print("%s%s" % (''.join(prompt+[bars[1]]), entry)) +255 else: +256 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry)) +257 +258 # This is the last entry +259 elif index == len(entries): +260 if os.path.isdir(local_path + os.path.sep + entry): +261 if config.no_colors: +262 print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) +263 else: +264 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) +265 recurse_action( +266 base_dir=base_dir, +267 path=path+[entry], +268 prompt=prompt+[" "] +269 ) +270 else: +271 if config.no_colors: +272 print("%s%s" % (''.join(prompt+[bars[2]]), entry)) +273 else: +274 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry)) +275 +276 # These are entries in the middle +277 else: +278 if os.path.isdir(local_path + os.path.sep + entry): +279 if config.no_colors: +280 print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) +281 else: +282 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep)) +283 recurse_action( +284 base_dir=base_dir, +285 path=path+[entry], +286 prompt=prompt+["│ "] +287 ) +288 else: +289 if config.no_colors: +290 print("%s%s" % (''.join(prompt+[bars[1]]), entry)) +291 else: +292 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry)) +293 +294 # +295 elif len(entries) == 1: +296 entry = entries[0] +297 if os.path.isdir(local_path + os.path.sep + entry): +298 if config.no_colors: +299 print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) +300 else: +301 print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep)) +302 recurse_action( +303 base_dir=base_dir, +304 path=path+[entry], +305 prompt=prompt+[" "] +306 ) +307 else: +308 if config.no_colors: +309 print("%s%s" % (''.join(prompt+[bars[2]]), entry)) +310 else: +311 print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry)) +312 +313 # Entrypoint +314 try: +315 if config.no_colors: +316 print("%s%s" % (path, os.path.sep)) +317 else: +318 print("\x1b[1;96m%s\x1b[0m%s" % (path, os.path.sep)) +319 recurse_action( +320 base_dir=os.getcwd(), +321 path=[path], +322 prompt=[""] +323 ) +324 except (BrokenPipeError, KeyboardInterrupt) as e: +325 print("[!] Interrupted.") +326 +327 +328def resolve_local_files(arguments): +329 """ +330 Resolves local file paths based on the provided arguments. +331 +332 This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual file paths. +333 If an argument contains a wildcard ('*'), it attempts to match files in the specified directory against the pattern. +334 If the argument does not contain a wildcard, it is added to the list of resolved files as is. +335 +336 Args: +337 arguments (list): A list of file path arguments, which may include wildcard patterns. +338 +339 Returns: +340 list: A list of resolved file paths that match the provided arguments. +341 """ +342 +343 resolved_files = [] +344 for arg in arguments: +345 if '*' in arg: +346 try: +347 path = os.path.dirname(arg) or '.' +348 pattern = os.path.basename(arg) +349 for entry in os.listdir(path): +350 if fnmatch.fnmatch(entry, pattern): +351 resolved_files.append(os.path.join(path, entry)) +352 except FileNotFoundError as err: +353 pass +354 else: +355 resolved_files.append(arg) +356 resolved_files = sorted(list(set(resolved_files))) +357 return resolved_files +358 +359 +360def resolve_remote_files(smbSession, arguments): +361 """ +362 Resolves remote file paths based on the provided arguments using an SMB session. +363 +364 This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual remote file paths. +365 If an argument contains a wildcard ('*'), it attempts to match files in the specified remote directory against the pattern. +366 If the argument does not contain a wildcard, it is added to the list of resolved files as is. +367 +368 Args: +369 smbsession (SMBSession): The SMB session through which to access the files. +370 arguments (list): A list of file path arguments, which may include wildcard patterns. +371 +372 Returns: +373 list: A list of resolved remote file paths that match the provided arguments. +374 """ +375 +376 resolved_files = [] +377 for arg in arguments: +378 if '*' in arg: +379 if arg == '*': +380 path = smbSession.smb_cwd +381 elif arg.startswith(ntpath.sep): +382 path = ntpath.dirname(arg) +383 else: +384 path = ntpath.normpath(smbSession.smb_cwd + ntpath.sep + ntpath.dirname(arg)) +385 +386 try: +387 contents = smbSession.smbClient.listPath( +388 shareName=smbSession.smb_share, +389 path=path + ntpath.sep + '*' +390 ) +391 contents = [e for e in contents if e.get_longname() not in ['.', '..']] +392 +393 for entry in contents: +394 if fnmatch.fnmatch(entry.get_longname(), ntpath.basename(arg)): +395 resolved_files.append(ntpath.join(path, entry.get_longname())) +396 +397 except Exception as err: +398 pass +399 else: +400 resolved_files.append(arg) +401 resolved_files = sorted(list(set(resolved_files))) +402 return resolved_files +403 +404 +405def is_port_open(target, port) -> bool: +406 """ +407 Check if a specific port on a target host is open. +408 +409 This function attempts to establish a TCP connection to the specified port on the target host. +410 If the connection is successful, it indicates that the port is open. If the connection fails, +411 it indicates that the port is closed or the host is unreachable. +412 +413 Args: +414 target (str): The hostname or IP address of the target host. +415 port (int): The port number to check. +416 +417 Returns: +418 bool: True if the port is open, False otherwise. +419 """ +420 +421 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: +422 s.settimeout(0.1) +423 # Non-existant domains cause a lot of errors, added error handling +424 try: +425 return s.connect_ex((target, port)) == 0 +426 except Exception as e: +427 return False

    @@ -407,40 +520,40 @@

    -
    16def parse_lm_nt_hashes(lm_nt_hashes_string):
    -17    """
    -18    Parse the input string containing LM and NT hash values and return them separately.
    -19
    -20    This function takes a string containing LM and NT hash values, typically separated by a colon (:).
    -21    It returns the LM and NT hash values as separate strings. If only one hash value is provided, it is
    -22    assumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are
    -23    found, both return values are empty strings.
    -24
    -25    Args:
    -26        lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon.
    -27
    -28    Returns:
    -29        tuple: A tuple containing two strings (lm_hash_value, nt_hash_value).
    -30               - lm_hash_value: The LM hash value or its default if not provided.
    -31               - nt_hash_value: The NT hash value or its default if not provided.
    -32    
    -33    Extracted from p0dalirius/sectools library
    -34    Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24
    -35    """
    -36
    -37    lm_hash_value, nt_hash_value = "", ""
    -38    if lm_nt_hashes_string is not None:
    -39        matched = re.match("([0-9a-f]{32})?(:)?([0-9a-f]{32})?", lm_nt_hashes_string.strip().lower())
    -40        m_lm_hash, m_sep, m_nt_hash = matched.groups()
    -41        if m_lm_hash is None and m_sep is None and m_nt_hash is None:
    -42            lm_hash_value, nt_hash_value = "", ""
    -43        elif m_lm_hash is None and m_nt_hash is not None:
    -44            lm_hash_value = "aad3b435b51404eeaad3b435b51404ee"
    -45            nt_hash_value = m_nt_hash
    -46        elif m_lm_hash is not None and m_nt_hash is None:
    -47            lm_hash_value = m_lm_hash
    -48            nt_hash_value = "31d6cfe0d16ae931b73c59d7e0c089c0"
    -49    return lm_hash_value, nt_hash_value
    +            
    18def parse_lm_nt_hashes(lm_nt_hashes_string):
    +19    """
    +20    Parse the input string containing LM and NT hash values and return them separately.
    +21
    +22    This function takes a string containing LM and NT hash values, typically separated by a colon (:).
    +23    It returns the LM and NT hash values as separate strings. If only one hash value is provided, it is
    +24    assumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are
    +25    found, both return values are empty strings.
    +26
    +27    Args:
    +28        lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon.
    +29
    +30    Returns:
    +31        tuple: A tuple containing two strings (lm_hash_value, nt_hash_value).
    +32               - lm_hash_value: The LM hash value or its default if not provided.
    +33               - nt_hash_value: The NT hash value or its default if not provided.
    +34    
    +35    Extracted from p0dalirius/sectools library
    +36    Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24
    +37    """
    +38
    +39    lm_hash_value, nt_hash_value = "", ""
    +40    if lm_nt_hashes_string is not None:
    +41        matched = re.match("([0-9a-f]{32})?(:)?([0-9a-f]{32})?", lm_nt_hashes_string.strip().lower())
    +42        m_lm_hash, m_sep, m_nt_hash = matched.groups()
    +43        if m_lm_hash is None and m_sep is None and m_nt_hash is None:
    +44            lm_hash_value, nt_hash_value = "", ""
    +45        elif m_lm_hash is None and m_nt_hash is not None:
    +46            lm_hash_value = "aad3b435b51404eeaad3b435b51404ee"
    +47            nt_hash_value = m_nt_hash
    +48        elif m_lm_hash is not None and m_nt_hash is None:
    +49            lm_hash_value = m_lm_hash
    +50            nt_hash_value = "31d6cfe0d16ae931b73c59d7e0c089c0"
    +51    return lm_hash_value, nt_hash_value
     
    @@ -476,26 +589,26 @@

    -
    52def b_filesize(l):
    -53    """
    -54    Convert a file size from bytes to a more readable format using the largest appropriate unit.
    -55
    -56    This function takes an integer representing a file size in bytes and converts it to a human-readable
    -57    string using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to
    -58    two decimal places.
    -59
    -60    Args:
    -61        l (int): The file size in bytes.
    -62
    -63    Returns:
    -64        str: A string representing the file size in a more readable format, including the appropriate unit.
    -65    """
    -66
    -67    units = ['B','kB','MB','GB','TB','PB']
    -68    for k in range(len(units)):
    -69        if l < (1024**(k+1)):
    -70            break
    -71    return "%4.2f %s" % (round(l/(1024**(k)),2), units[k])
    +            
    54def b_filesize(l):
    +55    """
    +56    Convert a file size from bytes to a more readable format using the largest appropriate unit.
    +57
    +58    This function takes an integer representing a file size in bytes and converts it to a human-readable
    +59    string using the largest appropriate unit from bytes (B) to petabytes (PB). The result is rounded to
    +60    two decimal places.
    +61
    +62    Args:
    +63        l (int): The file size in bytes.
    +64
    +65    Returns:
    +66        str: A string representing the file size in a more readable format, including the appropriate unit.
    +67    """
    +68
    +69    units = ['B','kB','MB','GB','TB','PB']
    +70    for k in range(len(units)):
    +71        if l < (1024**(k+1)):
    +72            break
    +73    return "%4.2f %s" % (round(l/(1024**(k)),2), units[k])
     
    @@ -525,41 +638,41 @@

    -
     74def unix_permissions(entryname):
    - 75    """
    - 76    Generate a string representing the Unix-style permissions for a given file or directory.
    - 77
    - 78    This function uses the os.lstat() method to retrieve the status of the specified file or directory,
    - 79    then constructs a string that represents the Unix-style permissions based on the mode of the file.
    - 80
    - 81    Args:
    - 82        entryname (str): The path to the file or directory for which permissions are being determined.
    - 83
    - 84    Returns:
    - 85        str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--').
    - 86             The first character is either 'd' (directory), '-' (not a directory), followed by
    - 87             three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group,
    - 88             and others respectively.
    - 89    """
    - 90
    - 91    mode = os.lstat(entryname).st_mode
    - 92    permissions = []
    - 93
    - 94    permissions.append('d' if stat.S_ISDIR(mode) else '-')
    +            
     76def unix_permissions(entryname):
    + 77    """
    + 78    Generate a string representing the Unix-style permissions for a given file or directory.
    + 79
    + 80    This function uses the os.lstat() method to retrieve the status of the specified file or directory,
    + 81    then constructs a string that represents the Unix-style permissions based on the mode of the file.
    + 82
    + 83    Args:
    + 84        entryname (str): The path to the file or directory for which permissions are being determined.
    + 85
    + 86    Returns:
    + 87        str: A string of length 10 representing the Unix-style permissions (e.g., '-rwxr-xr--').
    + 88             The first character is either 'd' (directory), '-' (not a directory), followed by
    + 89             three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group,
    + 90             and others respectively.
    + 91    """
    + 92
    + 93    mode = os.lstat(entryname).st_mode
    + 94    permissions = []
      95
    - 96    permissions.append('r' if mode & stat.S_IRUSR else '-')
    - 97    permissions.append('w' if mode & stat.S_IWUSR else '-')
    - 98    permissions.append('x' if mode & stat.S_IXUSR else '-')
    - 99
    -100    permissions.append('r' if mode & stat.S_IRGRP else '-')
    -101    permissions.append('w' if mode & stat.S_IWGRP else '-')
    -102    permissions.append('x' if mode & stat.S_IXGRP else '-')
    -103
    -104    permissions.append('r' if mode & stat.S_IROTH else '-')
    -105    permissions.append('w' if mode & stat.S_IWOTH else '-')
    -106    permissions.append('x' if mode & stat.S_IXOTH else '-')
    -107
    -108    return ''.join(permissions)
    + 96    permissions.append('d' if stat.S_ISDIR(mode) else '-')
    + 97
    + 98    permissions.append('r' if mode & stat.S_IRUSR else '-')
    + 99    permissions.append('w' if mode & stat.S_IWUSR else '-')
    +100    permissions.append('x' if mode & stat.S_IXUSR else '-')
    +101
    +102    permissions.append('r' if mode & stat.S_IRGRP else '-')
    +103    permissions.append('w' if mode & stat.S_IWGRP else '-')
    +104    permissions.append('x' if mode & stat.S_IXGRP else '-')
    +105
    +106    permissions.append('r' if mode & stat.S_IROTH else '-')
    +107    permissions.append('w' if mode & stat.S_IWOTH else '-')
    +108    permissions.append('x' if mode & stat.S_IXOTH else '-')
    +109
    +110    return ''.join(permissions)
     
    @@ -591,56 +704,56 @@

    -
    111def STYPE_MASK(stype_value):
    -112    """
    -113    Extracts the share type flags from a given share type value.
    -114
    -115    This function uses bitwise operations to determine which share type flags are set in the provided `stype_value`.
    -116    It checks against known share type flags and returns a list of the flags that are set.
    -117
    -118    Parameters:
    -119        stype_value (int): The share type value to analyze, typically obtained from SMB share properties.
    -120
    -121    Returns:
    -122        list: A list of strings, where each string represents a share type flag that is set in the input value.
    -123    """
    -124
    -125    known_flags = {
    -126        ## One of the following values may be specified. You can isolate these values by using the STYPE_MASK value.
    -127        # Disk drive.
    -128        "STYPE_DISKTREE": 0x0,
    -129
    -130        # Print queue.
    -131        "STYPE_PRINTQ": 0x1,
    -132
    -133        # Communication device.
    -134        "STYPE_DEVICE": 0x2,
    -135
    -136        # Interprocess communication (IPC).
    -137        "STYPE_IPC": 0x3,
    -138
    -139        ## In addition, one or both of the following values may be specified.
    -140        # Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$).
    -141        # Can also refer to administrative shares such as C$, D$, E$, and so forth. For more information, see Network Share Functions.
    -142        "STYPE_SPECIAL": 0x80000000,
    -143
    -144        # A temporary share.
    -145        "STYPE_TEMPORARY": 0x40000000
    -146    }
    -147    flags = []
    -148    if (stype_value & 0b11) == known_flags["STYPE_DISKTREE"]:
    -149        flags.append("STYPE_DISKTREE")
    -150    elif (stype_value & 0b11) == known_flags["STYPE_PRINTQ"]:
    -151        flags.append("STYPE_PRINTQ")
    -152    elif (stype_value & 0b11) == known_flags["STYPE_DEVICE"]:
    -153        flags.append("STYPE_DEVICE")
    -154    elif (stype_value & 0b11) == known_flags["STYPE_IPC"]:
    -155        flags.append("STYPE_IPC")
    -156    if (stype_value & known_flags["STYPE_SPECIAL"]) == known_flags["STYPE_SPECIAL"]:
    -157        flags.append("STYPE_SPECIAL")
    -158    if (stype_value & known_flags["STYPE_TEMPORARY"]) == known_flags["STYPE_TEMPORARY"]:
    -159        flags.append("STYPE_TEMPORARY")
    -160    return flags
    +            
    113def STYPE_MASK(stype_value):
    +114    """
    +115    Extracts the share type flags from a given share type value.
    +116
    +117    This function uses bitwise operations to determine which share type flags are set in the provided `stype_value`.
    +118    It checks against known share type flags and returns a list of the flags that are set.
    +119
    +120    Parameters:
    +121        stype_value (int): The share type value to analyze, typically obtained from SMB share properties.
    +122
    +123    Returns:
    +124        list: A list of strings, where each string represents a share type flag that is set in the input value.
    +125    """
    +126
    +127    known_flags = {
    +128        ## One of the following values may be specified. You can isolate these values by using the STYPE_MASK value.
    +129        # Disk drive.
    +130        "STYPE_DISKTREE": 0x0,
    +131
    +132        # Print queue.
    +133        "STYPE_PRINTQ": 0x1,
    +134
    +135        # Communication device.
    +136        "STYPE_DEVICE": 0x2,
    +137
    +138        # Interprocess communication (IPC).
    +139        "STYPE_IPC": 0x3,
    +140
    +141        ## In addition, one or both of the following values may be specified.
    +142        # Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$).
    +143        # Can also refer to administrative shares such as C$, D$, E$, and so forth. For more information, see Network Share Functions.
    +144        "STYPE_SPECIAL": 0x80000000,
    +145
    +146        # A temporary share.
    +147        "STYPE_TEMPORARY": 0x40000000
    +148    }
    +149    flags = []
    +150    if (stype_value & 0b11) == known_flags["STYPE_DISKTREE"]:
    +151        flags.append("STYPE_DISKTREE")
    +152    elif (stype_value & 0b11) == known_flags["STYPE_PRINTQ"]:
    +153        flags.append("STYPE_PRINTQ")
    +154    elif (stype_value & 0b11) == known_flags["STYPE_DEVICE"]:
    +155        flags.append("STYPE_DEVICE")
    +156    elif (stype_value & 0b11) == known_flags["STYPE_IPC"]:
    +157        flags.append("STYPE_IPC")
    +158    if (stype_value & known_flags["STYPE_SPECIAL"]) == known_flags["STYPE_SPECIAL"]:
    +159        flags.append("STYPE_SPECIAL")
    +160    if (stype_value & known_flags["STYPE_TEMPORARY"]) == known_flags["STYPE_TEMPORARY"]:
    +161        flags.append("STYPE_TEMPORARY")
    +162    return flags
     
    @@ -669,46 +782,46 @@

    -
    163def windows_ls_entry(entry, config, pathToPrint=None):
    -164    """
    -165    This function generates a metadata string based on the attributes of the provided entry object.
    -166    
    -167    Parameters:
    -168        entry (object): An object representing a file or directory entry.
    -169
    -170    Returns:
    -171        str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary.
    -172    """
    -173    
    -174    if pathToPrint is not None:
    -175        pathToPrint = pathToPrint + ntpath.sep + entry.get_longname()
    -176    else:
    -177        pathToPrint = entry.get_longname()
    -178
    -179    meta_string = ""
    -180    meta_string += ("d" if entry.is_directory() else "-")
    -181    meta_string += ("a" if entry.is_archive() else "-")
    -182    meta_string += ("c" if entry.is_compressed() else "-")
    -183    meta_string += ("h" if entry.is_hidden() else "-")
    -184    meta_string += ("n" if entry.is_normal() else "-")
    -185    meta_string += ("r" if entry.is_readonly() else "-")
    -186    meta_string += ("s" if entry.is_system() else "-")
    -187    meta_string += ("t" if entry.is_temporary() else "-")
    -188
    -189    size_str = b_filesize(entry.get_filesize())
    +            
    165def windows_ls_entry(entry, config, pathToPrint=None):
    +166    """
    +167    This function generates a metadata string based on the attributes of the provided entry object.
    +168    
    +169    Parameters:
    +170        entry (object): An object representing a file or directory entry.
    +171
    +172    Returns:
    +173        str: A string representing the metadata of the entry, including attributes like directory, archive, compressed, hidden, normal, readonly, system, and temporary.
    +174    """
    +175    
    +176    if pathToPrint is not None:
    +177        pathToPrint = pathToPrint + ntpath.sep + entry.get_longname()
    +178    else:
    +179        pathToPrint = entry.get_longname()
    +180
    +181    meta_string = ""
    +182    meta_string += ("d" if entry.is_directory() else "-")
    +183    meta_string += ("a" if entry.is_archive() else "-")
    +184    meta_string += ("c" if entry.is_compressed() else "-")
    +185    meta_string += ("h" if entry.is_hidden() else "-")
    +186    meta_string += ("n" if entry.is_normal() else "-")
    +187    meta_string += ("r" if entry.is_readonly() else "-")
    +188    meta_string += ("s" if entry.is_system() else "-")
    +189    meta_string += ("t" if entry.is_temporary() else "-")
     190
    -191    date_str = datetime.datetime.fromtimestamp(entry.get_atime_epoch()).strftime("%Y-%m-%d %H:%M")
    -192    
    -193    if entry.is_directory():
    -194        if config.no_colors:
    -195            print("%s %10s  %s  %s\\" % (meta_string, size_str, date_str, pathToPrint))
    -196        else:
    -197            print("%s %10s  %s  \x1b[1;96m%s\x1b[0m\\" % (meta_string, size_str, date_str, pathToPrint))
    -198    else:
    -199        if config.no_colors:
    -200            print("%s %10s  %s  %s" % (meta_string, size_str, date_str, pathToPrint))
    -201        else:
    -202            print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (meta_string, size_str, date_str, pathToPrint))
    +191    size_str = b_filesize(entry.get_filesize())
    +192
    +193    date_str = datetime.datetime.fromtimestamp(entry.get_atime_epoch()).strftime("%Y-%m-%d %H:%M")
    +194    
    +195    if entry.is_directory():
    +196        if config.no_colors:
    +197            print("%s %10s  %s  %s\\" % (meta_string, size_str, date_str, pathToPrint))
    +198        else:
    +199            print("%s %10s  %s  \x1b[1;96m%s\x1b[0m\\" % (meta_string, size_str, date_str, pathToPrint))
    +200    else:
    +201        if config.no_colors:
    +202            print("%s %10s  %s  %s" % (meta_string, size_str, date_str, pathToPrint))
    +203        else:
    +204            print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (meta_string, size_str, date_str, pathToPrint))
     
    @@ -734,126 +847,126 @@

    -
    205def local_tree(path, config):
    -206    """
    -207    This function recursively lists the contents of a directory in a tree-like format.
    -208
    -209    Parameters:
    -210        path (str): The path to the directory to list.
    -211        config (object): Configuration settings which may affect the output, such as whether to use colors.
    -212
    -213    Returns:
    -214        None: This function does not return anything but prints the directory tree to the console.
    -215    """
    -216
    -217    def recurse_action(base_dir="", path=[], prompt=[]):
    -218        bars = ["│   ", "├── ", "└── "]
    -219
    -220        local_path = os.path.normpath(base_dir + os.path.sep + os.path.sep.join(path) + os.path.sep)
    +            
    207def local_tree(path, config):
    +208    """
    +209    This function recursively lists the contents of a directory in a tree-like format.
    +210
    +211    Parameters:
    +212        path (str): The path to the directory to list.
    +213        config (object): Configuration settings which may affect the output, such as whether to use colors.
    +214
    +215    Returns:
    +216        None: This function does not return anything but prints the directory tree to the console.
    +217    """
    +218
    +219    def recurse_action(base_dir="", path=[], prompt=[]):
    +220        bars = ["│   ", "├── ", "└── "]
     221
    -222        entries = []
    -223        try:
    -224            entries = os.listdir(local_path)
    -225        except Exception as err:
    -226            if config.no_colors:
    -227                print("%s%s" % (''.join(prompt+[bars[2]]), err))
    -228            else:
    -229                print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), err))
    -230            return 
    -231
    -232        entries = sorted(entries)
    +222        local_path = os.path.normpath(base_dir + os.path.sep + os.path.sep.join(path) + os.path.sep)
    +223
    +224        entries = []
    +225        try:
    +226            entries = os.listdir(local_path)
    +227        except Exception as err:
    +228            if config.no_colors:
    +229                print("%s%s" % (''.join(prompt+[bars[2]]), err))
    +230            else:
    +231                print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), err))
    +232            return 
     233
    -234        # 
    -235        if len(entries) > 1:
    -236            index = 0
    -237            for entry in entries:
    -238                index += 1
    -239                # This is the first entry 
    -240                if index == 0:
    -241                    if os.path.isdir(local_path + os.path.sep + entry):
    -242                        if config.no_colors:
    -243                            print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    -244                        else:
    -245                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    -246                        recurse_action(
    -247                            base_dir=base_dir, 
    -248                            path=path+[entry],
    -249                            prompt=prompt+["│   "]
    -250                        )
    -251                    else:
    -252                        if config.no_colors:
    -253                            print("%s%s" % (''.join(prompt+[bars[1]]), entry))
    -254                        else:
    -255                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry))
    -256
    -257                # This is the last entry
    -258                elif index == len(entries):
    -259                    if os.path.isdir(local_path + os.path.sep + entry):
    -260                        if config.no_colors:
    -261                            print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    -262                        else:
    -263                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    -264                        recurse_action(
    -265                            base_dir=base_dir, 
    -266                            path=path+[entry],
    -267                            prompt=prompt+["    "]
    -268                        )
    -269                    else:
    -270                        if config.no_colors:
    -271                            print("%s%s" % (''.join(prompt+[bars[2]]), entry))
    -272                        else:
    -273                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry))
    -274                    
    -275                # These are entries in the middle
    -276                else:
    -277                    if os.path.isdir(local_path + os.path.sep + entry):
    -278                        if config.no_colors:
    -279                            print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    -280                        else:
    -281                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    -282                        recurse_action(
    -283                            base_dir=base_dir, 
    -284                            path=path+[entry],
    -285                            prompt=prompt+["│   "]
    -286                        )
    -287                    else:
    -288                        if config.no_colors:
    -289                            print("%s%s" % (''.join(prompt+[bars[1]]), entry))
    -290                        else:
    -291                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry))
    -292
    -293        # 
    -294        elif len(entries) == 1:
    -295            entry = entries[0]
    -296            if os.path.isdir(local_path + os.path.sep + entry):
    -297                if config.no_colors:
    -298                    print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    -299                else:
    -300                    print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    -301                recurse_action(
    -302                    base_dir=base_dir, 
    -303                    path=path+[entry],
    -304                    prompt=prompt+["    "]
    -305                )
    -306            else:
    -307                if config.no_colors:
    -308                    print("%s%s" % (''.join(prompt+[bars[2]]), entry))
    -309                else:
    -310                    print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry))
    -311
    -312    # Entrypoint
    -313    try:
    -314        if config.no_colors:
    -315            print("%s%s" % (path, os.path.sep))
    -316        else:
    -317            print("\x1b[1;96m%s\x1b[0m%s" % (path, os.path.sep))
    -318        recurse_action(
    -319            base_dir=os.getcwd(),
    -320            path=[path],
    -321            prompt=[""]
    -322        )
    -323    except (BrokenPipeError, KeyboardInterrupt) as e:
    -324        print("[!] Interrupted.")
    +234        entries = sorted(entries)
    +235
    +236        # 
    +237        if len(entries) > 1:
    +238            index = 0
    +239            for entry in entries:
    +240                index += 1
    +241                # This is the first entry 
    +242                if index == 0:
    +243                    if os.path.isdir(local_path + os.path.sep + entry):
    +244                        if config.no_colors:
    +245                            print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    +246                        else:
    +247                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    +248                        recurse_action(
    +249                            base_dir=base_dir, 
    +250                            path=path+[entry],
    +251                            prompt=prompt+["│   "]
    +252                        )
    +253                    else:
    +254                        if config.no_colors:
    +255                            print("%s%s" % (''.join(prompt+[bars[1]]), entry))
    +256                        else:
    +257                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry))
    +258
    +259                # This is the last entry
    +260                elif index == len(entries):
    +261                    if os.path.isdir(local_path + os.path.sep + entry):
    +262                        if config.no_colors:
    +263                            print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    +264                        else:
    +265                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    +266                        recurse_action(
    +267                            base_dir=base_dir, 
    +268                            path=path+[entry],
    +269                            prompt=prompt+["    "]
    +270                        )
    +271                    else:
    +272                        if config.no_colors:
    +273                            print("%s%s" % (''.join(prompt+[bars[2]]), entry))
    +274                        else:
    +275                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry))
    +276                    
    +277                # These are entries in the middle
    +278                else:
    +279                    if os.path.isdir(local_path + os.path.sep + entry):
    +280                        if config.no_colors:
    +281                            print("%s%s%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    +282                        else:
    +283                            print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[1]]), entry, os.path.sep))
    +284                        recurse_action(
    +285                            base_dir=base_dir, 
    +286                            path=path+[entry],
    +287                            prompt=prompt+["│   "]
    +288                        )
    +289                    else:
    +290                        if config.no_colors:
    +291                            print("%s%s" % (''.join(prompt+[bars[1]]), entry))
    +292                        else:
    +293                            print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry))
    +294
    +295        # 
    +296        elif len(entries) == 1:
    +297            entry = entries[0]
    +298            if os.path.isdir(local_path + os.path.sep + entry):
    +299                if config.no_colors:
    +300                    print("%s%s%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    +301                else:
    +302                    print("%s\x1b[1;96m%s\x1b[0m%s" % (''.join(prompt+[bars[2]]), entry, os.path.sep))
    +303                recurse_action(
    +304                    base_dir=base_dir, 
    +305                    path=path+[entry],
    +306                    prompt=prompt+["    "]
    +307                )
    +308            else:
    +309                if config.no_colors:
    +310                    print("%s%s" % (''.join(prompt+[bars[2]]), entry))
    +311                else:
    +312                    print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry))
    +313
    +314    # Entrypoint
    +315    try:
    +316        if config.no_colors:
    +317            print("%s%s" % (path, os.path.sep))
    +318        else:
    +319            print("\x1b[1;96m%s\x1b[0m%s" % (path, os.path.sep))
    +320        recurse_action(
    +321            base_dir=os.getcwd(),
    +322            path=[path],
    +323            prompt=[""]
    +324        )
    +325    except (BrokenPipeError, KeyboardInterrupt) as e:
    +326        print("[!] Interrupted.")
     
    @@ -868,6 +981,191 @@

    + +
    + +
    + + def + resolve_local_files(arguments): + + + +
    + +
    329def resolve_local_files(arguments):
    +330    """
    +331    Resolves local file paths based on the provided arguments.
    +332
    +333    This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual file paths.
    +334    If an argument contains a wildcard ('*'), it attempts to match files in the specified directory against the pattern.
    +335    If the argument does not contain a wildcard, it is added to the list of resolved files as is.
    +336
    +337    Args:
    +338        arguments (list): A list of file path arguments, which may include wildcard patterns.
    +339
    +340    Returns:
    +341        list: A list of resolved file paths that match the provided arguments.
    +342    """
    +343
    +344    resolved_files = []
    +345    for arg in arguments:
    +346        if '*' in arg:
    +347            try:
    +348                path = os.path.dirname(arg) or '.'
    +349                pattern = os.path.basename(arg)
    +350                for entry in os.listdir(path):
    +351                    if fnmatch.fnmatch(entry, pattern):
    +352                        resolved_files.append(os.path.join(path, entry))
    +353            except FileNotFoundError as err:
    +354                pass
    +355        else:
    +356            resolved_files.append(arg)
    +357    resolved_files = sorted(list(set(resolved_files)))
    +358    return resolved_files
    +
    + + +

    Resolves local file paths based on the provided arguments.

    + +

    This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual file paths. +If an argument contains a wildcard ('*'), it attempts to match files in the specified directory against the pattern. +If the argument does not contain a wildcard, it is added to the list of resolved files as is.

    + +

    Args: + arguments (list): A list of file path arguments, which may include wildcard patterns.

    + +

    Returns: + list: A list of resolved file paths that match the provided arguments.

    +
    + + +
    +
    + +
    + + def + resolve_remote_files(smbSession, arguments): + + + +
    + +
    361def resolve_remote_files(smbSession, arguments):
    +362    """
    +363    Resolves remote file paths based on the provided arguments using an SMB session.
    +364
    +365    This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual remote file paths.
    +366    If an argument contains a wildcard ('*'), it attempts to match files in the specified remote directory against the pattern.
    +367    If the argument does not contain a wildcard, it is added to the list of resolved files as is.
    +368
    +369    Args:
    +370        smbsession (SMBSession): The SMB session through which to access the files.
    +371        arguments (list): A list of file path arguments, which may include wildcard patterns.
    +372
    +373    Returns:
    +374        list: A list of resolved remote file paths that match the provided arguments.
    +375    """
    +376
    +377    resolved_files = []
    +378    for arg in arguments:
    +379        if '*' in arg:
    +380            if arg == '*':
    +381                path = smbSession.smb_cwd
    +382            elif arg.startswith(ntpath.sep):
    +383                path = ntpath.dirname(arg)
    +384            else:
    +385                path = ntpath.normpath(smbSession.smb_cwd + ntpath.sep + ntpath.dirname(arg))
    +386
    +387            try:
    +388                contents = smbSession.smbClient.listPath(
    +389                    shareName=smbSession.smb_share,
    +390                    path=path + ntpath.sep + '*'
    +391                )
    +392                contents = [e for e in contents if e.get_longname() not in ['.', '..']]
    +393
    +394                for entry in contents:
    +395                    if fnmatch.fnmatch(entry.get_longname(), ntpath.basename(arg)):
    +396                        resolved_files.append(ntpath.join(path, entry.get_longname()))
    +397
    +398            except Exception as err:
    +399                pass
    +400        else:
    +401            resolved_files.append(arg)
    +402    resolved_files = sorted(list(set(resolved_files)))
    +403    return resolved_files
    +
    + + +

    Resolves remote file paths based on the provided arguments using an SMB session.

    + +

    This function takes a list of arguments, which can include wildcard patterns, and resolves them to actual remote file paths. +If an argument contains a wildcard ('*'), it attempts to match files in the specified remote directory against the pattern. +If the argument does not contain a wildcard, it is added to the list of resolved files as is.

    + +

    Args: + smbsession (SMBSession): The SMB session through which to access the files. + arguments (list): A list of file path arguments, which may include wildcard patterns.

    + +

    Returns: + list: A list of resolved remote file paths that match the provided arguments.

    +
    + + +
    +
    + +
    + + def + is_port_open(target, port) -> bool: + + + +
    + +
    406def is_port_open(target, port) -> bool:
    +407    """
    +408    Check if a specific port on a target host is open.
    +409
    +410    This function attempts to establish a TCP connection to the specified port on the target host.
    +411    If the connection is successful, it indicates that the port is open. If the connection fails,
    +412    it indicates that the port is closed or the host is unreachable.
    +413
    +414    Args:
    +415        target (str): The hostname or IP address of the target host.
    +416        port (int): The port number to check.
    +417
    +418    Returns:
    +419        bool: True if the port is open, False otherwise.
    +420    """
    +421
    +422    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    +423        s.settimeout(0.1)
    +424        # Non-existant domains cause a lot of errors, added error handling
    +425        try:
    +426            return s.connect_ex((target, port)) == 0
    +427        except Exception as e:
    +428            return False
    +
    + + +

    Check if a specific port on a target host is open.

    + +

    This function attempts to establish a TCP connection to the specified port on the target host. +If the connection is successful, it indicates that the port is open. If the connection fails, +it indicates that the port is closed or the host is unreachable.

    + +

    Args: + target (str): The hostname or IP address of the target host. + port (int): The port number to check.

    + +

    Returns: + bool: True if the port is open, False otherwise.

    +
    + +