From 509ed1e96f9ae188c54deab1fc03e86f5735cfcf Mon Sep 17 00:00:00 2001 From: "Remi GASCOU (Podalirius)" <79218792+p0dalirius@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:10:29 +0200 Subject: [PATCH] Added documentation --- documentation/index.html | 7 + documentation/search.js | 46 + documentation/smbclientng.html | 245 ++ documentation/smbclientng/core.html | 244 ++ .../smbclientng/core/CommandCompleter.html | 1547 +++++++ documentation/smbclientng/core/Config.html | 453 ++ .../smbclientng/core/InteractiveShell.html | 2601 ++++++++++++ .../smbclientng/core/LocalFileIO.html | 945 +++++ documentation/smbclientng/core/Module.html | 529 +++ .../core/ModuleArgumentParser.html | 425 ++ .../smbclientng/core/SMBSession.html | 3716 +++++++++++++++++ documentation/smbclientng/core/utils.html | 1055 +++++ documentation/smbclientng/modules.html | 246 ++ documentation/smbclientng/modules/Find.html | 935 +++++ run-tests.py | 28 - smbclientng/__main__.py | 16 + smbclientng/core/Config.py | 15 + smbclientng/core/InteractiveShell.py | 13 + smbclientng/core/ModuleArgumentParser.py | 27 +- smbclientng/core/utils.py | 35 +- smbclientng/modules/Find.py | 12 + smbclientng/tests/__init__.py | 5 - smbclientng/tests/common.py | 65 - smbclientng/tests/run-tests.py | 28 - smbclientng/tests/test_SMBSession.py | 22 - .../tests/test_SMBSession_path_isdir.py | 43 - .../tests/test_SMBSession_path_isfile.py | 43 - 27 files changed, 13108 insertions(+), 238 deletions(-) create mode 100644 documentation/index.html create mode 100644 documentation/search.js create mode 100644 documentation/smbclientng.html create mode 100644 documentation/smbclientng/core.html create mode 100644 documentation/smbclientng/core/CommandCompleter.html create mode 100644 documentation/smbclientng/core/Config.html create mode 100644 documentation/smbclientng/core/InteractiveShell.html create mode 100644 documentation/smbclientng/core/LocalFileIO.html create mode 100644 documentation/smbclientng/core/Module.html create mode 100644 documentation/smbclientng/core/ModuleArgumentParser.html create mode 100644 documentation/smbclientng/core/SMBSession.html create mode 100644 documentation/smbclientng/core/utils.html create mode 100644 documentation/smbclientng/modules.html create mode 100644 documentation/smbclientng/modules/Find.html delete mode 100644 run-tests.py delete mode 100644 smbclientng/tests/__init__.py delete mode 100644 smbclientng/tests/common.py delete mode 100644 smbclientng/tests/run-tests.py delete mode 100644 smbclientng/tests/test_SMBSession.py delete mode 100644 smbclientng/tests/test_SMBSession_path_isdir.py delete mode 100644 smbclientng/tests/test_SMBSession_path_isfile.py diff --git a/documentation/index.html b/documentation/index.html new file mode 100644 index 0000000..d4780a8 --- /dev/null +++ b/documentation/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/documentation/search.js b/documentation/search.js new file mode 100644 index 0000000..e403dc1 --- /dev/null +++ b/documentation/search.js @@ -0,0 +1,46 @@ +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": "{'cd': {'description': ['Change the current working directory.', "Syntax: 'cd <directory>'"], 'subcommands': []}, 'close': {'description': ['Closes the SMB connection to the remote machine.', "Syntax: 'close'"], 'subcommands': []}, 'connect': {'description': ['Connect to the remote machine (useful if connection timed out).', "Syntax: 'connect'"], 'subcommands': []}, 'dir': {'description': ['List the contents of the current working directory.', "Syntax: 'dir'"], 'subcommands': []}, 'exit': {'description': ['Exits the smbclient-ng script.', "Syntax: 'exit'"], 'subcommands': []}, 'get': {'description': ['Get a remote file.', "Syntax: 'get [-r] <directory or file>'"], 'subcommands': []}, 'help': {'description': ['Displays this help message.', "Syntax: 'help'"], 'subcommands': ['format']}, 'info': {'description': ['Get information about the server and or the share.', "Syntax: 'info [server|share]'"], 'subcommands': ['server', 'share']}, 'lcd': {'description': ['Changes the current local directory.', "Syntax: 'lcd <directory>'"], 'subcommands': []}, 'lls': {'description': ['Lists the contents of the current local directory.', "Syntax: 'lls'"], 'subcommands': []}, 'lmkdir': {'description': ['Creates a new local directory.', "Syntax: 'lmkdir <directory>'"], 'subcommands': []}, 'lpwd': {'description': ['Shows the current local directory.', "Syntax: 'lpwd'"], 'subcommands': []}, 'lrm': {'description': ['Removes a local file.', "Syntax: 'lrm <file>'"], 'subcommands': []}, 'lrmdir': {'description': ['Removes a local directory.', "Syntax: 'lrmdir <directory>'"], 'subcommands': []}, 'ls': {'description': ['List the contents of the current remote working directory.', "Syntax: 'ls'"], 'subcommands': []}, 'ltree': {'description': ['Displays a tree view of the local directories.', "Syntax: 'ltree [directory]'"], 'subcommands': []}, 'mkdir': {'description': ['Creates a new remote directory.', "Syntax: 'mkdir <directory>'"], 'subcommands': []}, 'module': {'description': ['Loads a specific module for additional functionalities.', "Syntax: 'module <name>'"], 'subcommands': []}, 'put': {'description': ['Put a local file or directory in a remote directory.', "Syntax: 'put [-r] <directory or file>'"], 'subcommands': []}, 'reconnect': {'description': ['Reconnect to the remote machine (useful if connection timed out).', "Syntax: 'reconnect'"], 'subcommands': []}, 'reset': {'description': ['Reset the TTY output, useful if it was broken after printing a binary file on stdout.', "Syntax: 'reset'"], 'subcommands': []}, 'rmdir': {'description': ['Removes a remote directory.', "Syntax: 'rmdir <directory>'"], 'subcommands': []}, 'rm': {'description': ['Removes a remote file.', "Syntax: 'rm <file>'"], 'subcommands': []}, 'shares': {'description': ['Lists the SMB shares served by the remote machine.', "Syntax: 'shares'"], 'subcommands': []}, 'use': {'description': ['Use a SMB share.', "Syntax: 'use <sharename>'"], 'subcommands': []}, 'tree': {'description': ['Displays a tree view of the remote directories.', "Syntax: 'tree [directory]'"], 'subcommands': []}}"}, {"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_cd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_cd", "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_lcd", "modulename": "smbclientng.core.InteractiveShell", "qualname": "InteractiveShell.command_lcd", "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_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_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_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_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.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.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.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.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.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"}]; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/documentation/smbclientng.html b/documentation/smbclientng.html new file mode 100644 index 0000000..7d766cd --- /dev/null +++ b/documentation/smbclientng.html @@ -0,0 +1,245 @@ + + + + + + + smbclientng API documentation + + + + + + + + + +
+
+

+smbclientng

+ + + + + + +
1#!/usr/bin/env python3
+2# -*- coding: utf-8 -*-
+3# File name          : __init__.py
+4# Author             : Podalirius (@podalirius_)
+5# Date created       : 23 may 2024
+6
+7from .core import *
+8from .modules import *
+
+ + +
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core.html b/documentation/smbclientng/core.html new file mode 100644 index 0000000..dc79d1e --- /dev/null +++ b/documentation/smbclientng/core.html @@ -0,0 +1,244 @@ + + + + + + + smbclientng.core API documentation + + + + + + + + + +
+
+

+smbclientng.core

+ + + + + +
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/CommandCompleter.html b/documentation/smbclientng/core/CommandCompleter.html new file mode 100644 index 0000000..92cb6ea --- /dev/null +++ b/documentation/smbclientng/core/CommandCompleter.html @@ -0,0 +1,1547 @@ + + + + + + + smbclientng.core.CommandCompleter API documentation + + + + + + + + + +
+
+

+smbclientng.core.CommandCompleter

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

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

+ +

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

+ +

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

+ +

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

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

Function to handle command completion in the LDAP console.

+ +

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

+ +

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

+ +

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

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

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

+ +

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

+ +

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

+ +

Returns: + None

+
+ + +
+
+ +
+ + def + print_help_format(self): + + + +
+ +
432    def print_help_format(self):
+433        """
+434        Prints the help information for the 'format' used in remote 'ls' and 'dir' commands.
+435
+436        This function displays the format of file attributes used in the smbclient-ng shell. It explains the meaning
+437        of each character in the file attribute string, such as whether a file is read-only, hidden, or a directory.
+438        """
+439
+440        print("File attributes format:\n")
+441        print("\x1b[1mdachnrst\x1b[0m")
+442        print("\x1b[90m│││││││└──>\x1b[0m Temporary")
+443        print("\x1b[90m││││││└───>\x1b[0m System")
+444        print("\x1b[90m│││││└────>\x1b[0m Read-Only")
+445        print("\x1b[90m││││└─────>\x1b[0m Normal")
+446        print("\x1b[90m│││└──────>\x1b[0m Hidden")
+447        print("\x1b[90m││└───────>\x1b[0m Compressed")
+448        print("\x1b[90m│└────────>\x1b[0m Archived")
+449        print("\x1b[90m└─────────>\x1b[0m Directory")
+
+ + +

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

+ +

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

+
+ + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/Config.html b/documentation/smbclientng/core/Config.html new file mode 100644 index 0000000..1ee9edb --- /dev/null +++ b/documentation/smbclientng/core/Config.html @@ -0,0 +1,453 @@ + + + + + + + smbclientng.core.Config API documentation + + + + + + + + + +
+
+

+smbclientng.core.Config

+ + + + + + +
 1#!/usr/bin/env python3
+ 2# -*- coding: utf-8 -*-
+ 3# File name          : Config.py
+ 4# Author             : Podalirius (@podalirius_)
+ 5# Date created       : 31 may 2024
+ 6
+ 7import platform
+ 8
+ 9
+10class Config(object):
+11    """
+12    Configuration handler for smbclientng.
+13
+14    This class manages the configuration settings for the smbclientng tool, including debug and color output settings.
+15    It provides a structured way to access and modify these settings throughout the application.
+16
+17    Attributes:
+18        _debug (bool): Flag to enable or disable debug mode.
+19        _no_colors (bool): Flag to enable or disable colored output, depending on the platform.
+20
+21    Methods:
+22        debug: Property to get or set the debug mode.
+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
+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")
+
+ + +
+
+ +
+ + class + Config: + + + +
+ +
11class Config(object):
+12    """
+13    Configuration handler for smbclientng.
+14
+15    This class manages the configuration settings for the smbclientng tool, including debug and color output settings.
+16    It provides a structured way to access and modify these settings throughout the application.
+17
+18    Attributes:
+19        _debug (bool): Flag to enable or disable debug mode.
+20        _no_colors (bool): Flag to enable or disable colored output, depending on the platform.
+21
+22    Methods:
+23        debug: Property to get or set the debug mode.
+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
+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")
+
+ + +

Configuration handler for smbclientng.

+ +

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

+ +

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

+ +

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

+
+ + +
+ +
+ + Config(debug=False, no_colors=None) + + + +
+ +
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
+
+ + + + +
+
+ +
+ debug + + + +
+ +
38    @property
+39    def debug(self):
+40        return self._debug
+
+ + + + +
+
+ +
+ no_colors + + + +
+ +
49    @property
+50    def no_colors(self):
+51        return self._no_colors
+
+ + + + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/InteractiveShell.html b/documentation/smbclientng/core/InteractiveShell.html new file mode 100644 index 0000000..57f4252 --- /dev/null +++ b/documentation/smbclientng/core/InteractiveShell.html @@ -0,0 +1,2601 @@ + + + + + + + smbclientng.core.InteractiveShell API documentation + + + + + + + + + +
+
+

+smbclientng.core.InteractiveShell

+ + + + + + +
  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 datetime
+  9import impacket
+ 10from importlib import import_module
+ 11import ntpath
+ 12import os
+ 13import readline
+ 14import shutil
+ 15import sys
+ 16import traceback
+ 17from rich.console import Console
+ 18from rich.table import Table
+ 19from smbclientng.core.CommandCompleter import CommandCompleter
+ 20from smbclientng.core.utils import b_filesize, unix_permissions, windows_ls_entry, local_tree
+ 21
+ 22
+ 23## Decorators
+ 24
+ 25def command_arguments_required(func):
+ 26    def wrapper(*args, **kwargs):
+ 27        self, arguments,command  = args[0], args[1], args[2]
+ 28        if len(arguments) != 0:
+ 29            return func(*args, **kwargs)
+ 30        else:
+ 31            self.commandCompleterObject.print_help(command=command)
+ 32            return None
+ 33    return wrapper
+ 34
+ 35def active_smb_connection_needed(func):
+ 36    def wrapper(*args, **kwargs):
+ 37        self, arguments,command  = args[0], args[1], args[2]
+ 38        #
+ 39        self.smbSession.ping_smb_session()
+ 40        if self.smbSession.connected:
+ 41            return func(*args, **kwargs)
+ 42        else:
+ 43            print("[!] SMB Session is disconnected.")
+ 44            return None
+ 45    return wrapper
+ 46
+ 47def smb_share_is_set(func):
+ 48    def wrapper(*args, **kwargs):
+ 49        self, arguments,command  = args[0], args[1], args[2]
+ 50        if self.smbSession.smb_share is not None:
+ 51            return func(*args, **kwargs)
+ 52        else:
+ 53            print("[!] You must open a share first, try the 'use <share>' command.")
+ 54            return None
+ 55    return wrapper
+ 56
+ 57
+ 58class InteractiveShell(object):
+ 59    """
+ 60    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
+ 61    
+ 62    This class handles user input, executes commands, and manages the state of the SMB session. It provides
+ 63    a command line interface for users to interact with SMB shares, execute commands like directory listing,
+ 64    file transfer, and more.
+ 65
+ 66    Attributes:
+ 67        smbSession (SMBConnection): The active SMB connection session.
+ 68        debug (bool): Flag to enable or disable debug mode.
+ 69        smb_share (str): The current SMB share in use.
+ 70        smb_path (str): The current path within the SMB share.
+ 71        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
+ 72
+ 73    Methods:
+ 74        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
+ 75        run(self): Starts the command line interface loop, processing user input until exit.
+ 76    """
+ 77    
+ 78    def __init__(self, smbSession, config):
+ 79        # Objects
+ 80        self.smbSession = smbSession
+ 81        self.config = config
+ 82        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
+ 83        readline.set_completer(self.commandCompleterObject.complete)
+ 84        readline.parse_and_bind("tab: complete")
+ 85        readline.set_completer_delims("\n")
+ 86        # Additional modules
+ 87        self.modules = {}
+ 88        self.__load_modules()
+ 89
+ 90    def run(self):
+ 91        running = True
+ 92        while running:
+ 93            try:
+ 94                user_input = input(self.__prompt()).strip().split(" ")
+ 95                command, arguments = user_input[0].lower(), user_input[1:]
+ 96                
+ 97                # Exit the command line
+ 98                if command == "exit":
+ 99                    running = False
+100
+101                elif command in self.commandCompleterObject.commands.keys():
+102                    self.process_command(
+103                        command=command, 
+104                        arguments=arguments
+105                    )
+106
+107                elif command.strip() == "":
+108                    pass
+109
+110                # Fallback to unknown command
+111                else:
+112                    print("Unknown command. Type \"help\" for help.")
+113
+114            except KeyboardInterrupt as e:
+115                print()
+116
+117            except EOFError as e:
+118                print()
+119                running = False
+120
+121            except Exception as e:
+122                if self.config.debug:
+123                    traceback.print_exc()
+124                print("[!] Error: %s" % str(e))
+125
+126    def process_command(self, command, arguments=[]):
+127        # Skip
+128        if command.strip() == "":
+129            pass
+130        
+131        # Display help
+132        elif command == "help":
+133            self.command_help(arguments, command)
+134
+135        # Closes the current SMB session
+136        elif command == "close":
+137            self.command_close(arguments, command)
+138               
+139        # Change directory in the current share
+140        elif command == "cd":
+141            self.command_cd(arguments, command)
+142
+143        # Get a file
+144        elif command == "get":
+145            self.command_get(arguments, command)
+146
+147        # SMB server info
+148        elif command == "info":
+149            self.command_info(arguments, command)
+150
+151        # List directory contents in a share
+152        elif command in ["ls", "dir"]:
+153            self.command_ls(arguments, command)
+154
+155        # Creates a new remote directory
+156        elif command == "mkdir":
+157            self.command_mkdir(arguments, command)
+158
+159        # Put a file
+160        elif command == "put":
+161            self.command_put(arguments, command)
+162
+163        # Changes the current local directory
+164        elif command == "lcd":
+165            self.command_lcd(arguments, command)
+166
+167        # Lists the contents of the current local directory
+168        elif command == "lls":
+169            self.command_lls(arguments, command)
+170
+171        # Creates a new local directory
+172        elif command == "lmkdir":
+173            self.command_lmkdir(arguments, command)
+174
+175        # Shows the current local directory
+176        elif command == "lpwd":
+177            self.command_lpwd(arguments, command)
+178
+179        # Removes a local file
+180        elif command == "lrm":
+181            self.command_lrm(arguments, command)
+182
+183        # Removes a local directory
+184        elif command == "lrmdir":
+185            self.command_lrmdir(arguments, command)
+186
+187        # Shows the current local directory
+188        elif command == "ltree":
+189            self.command_ltree(arguments, command)
+190
+191        # Modules
+192        elif command == "module":
+193            self.command_module(arguments, command)
+194
+195        # Reconnects the current SMB session
+196        elif command in ["connect", "reconnect"]:
+197            self.command_reconnect(arguments, command)
+198
+199        # Reset the TTY output
+200        elif command == "reset":
+201            self.command_reset(arguments, command)
+202
+203        # Removes a remote file
+204        elif command == "rm":
+205            self.command_rm(arguments, command)
+206            
+207        # Removes a remote directory
+208        elif command == "rmdir":
+209            self.command_rmdir(arguments, command)
+210
+211        # List shares
+212        elif command == "shares":
+213            self.command_shares(arguments, command)
+214        
+215        # Displays a tree view of the CWD
+216        elif command == "tree":
+217            self.command_tree(arguments, command)
+218        
+219        # Use a share
+220        elif command == "use":
+221            self.command_use(arguments, command)
+222
+223    # Commands ================================================================
+224
+225    @command_arguments_required
+226    @active_smb_connection_needed
+227    @smb_share_is_set
+228    def command_cd(self, arguments, command):
+229        # Command arguments required   : Yes
+230        # Active SMB connection needed : Yes
+231        # SMB share needed             : Yes
+232
+233        path = ' '.join(arguments)
+234        try:
+235            self.smbSession.set_cwd(path=path)
+236        except impacket.smbconnection.SessionError as e:
+237            print("[!] SMB Error: %s" % e)
+238
+239    def command_close(self, arguments, command):
+240        # Command arguments required   : No
+241        # Active SMB connection needed : No
+242        # SMB share needed             : No
+243
+244        self.smbSession.ping_smb_session()
+245        if self.smbSession.connected:
+246            self.smbSession.close_smb_session()
+247
+248    @command_arguments_required
+249    @active_smb_connection_needed
+250    @smb_share_is_set
+251    def command_get(self, arguments, command):
+252        # Command arguments required   : Yes
+253        # Active SMB connection needed : Yes
+254        # SMB share needed             : Yes
+255
+256        # Get files recursively
+257        if arguments[0] == "-r":
+258            path = ' '.join(arguments[1:]).replace('/', ntpath.sep)
+259            try:
+260                self.smbSession.get_file_recursively(path=path)
+261            except impacket.smbconnection.SessionError as e:
+262                print("[!] SMB Error: %s" % e)
+263        # Get a single file
+264        else:
+265            path = ' '.join(arguments).replace('/', ntpath.sep)
+266            try:
+267                self.smbSession.get_file(path=path)
+268            except impacket.smbconnection.SessionError as e:
+269                print("[!] SMB Error: %s" % e)
+270
+271    def command_help(self, arguments, command):
+272        # Command arguments required   : No
+273        # Active SMB connection needed : No
+274        # SMB share needed             : No
+275
+276        if len(arguments) != 0:
+277            self.commandCompleterObject.print_help(command=arguments[0])
+278        else:
+279            self.commandCompleterObject.print_help(command=None)
+280
+281    @active_smb_connection_needed
+282    def command_info(self, arguments, command):
+283        # Command arguments required   : No
+284        # Active SMB connection needed : Yes
+285        # SMB share needed             : No
+286
+287        print_server_info = False
+288        print_share_info = False
+289        if len(arguments) != 0:
+290            print_server_info = (arguments[0].lower() == "server")
+291            print_share_info = (arguments[0].lower() == "share")
+292        else:
+293            print_server_info = True
+294            print_share_info = True
+295
+296        try:
+297            self.smbSession.info(
+298                share=print_share_info,
+299                server=print_server_info
+300            )
+301        except impacket.smbconnection.SessionError as e:
+302            print("[!] SMB Error: %s" % e)
+303
+304    @command_arguments_required
+305    def command_lcd(self, arguments, command):
+306        # Command arguments required   : Yes
+307        # Active SMB connection needed : No
+308        # SMB share needed             : No
+309
+310        path = ' '.join(arguments)
+311        if os.path.exists(path=path):
+312            if os.path.isdir(s=path):
+313                os.chdir(path=path)
+314            else:
+315                print("[!] Path '%s' is not a directory." % path)
+316        else:
+317            print("[!] Directory '%s' does not exists." % path)
+318
+319    def command_lls(self, arguments, command):
+320        # Command arguments required   : No
+321        # Active SMB connection needed : No
+322        # SMB share needed             : No
+323
+324        if len(arguments) == 0:
+325            path = '.'
+326        else:
+327            path = ' '.join(arguments)
+328
+329        # lls <directory>
+330        if os.path.isdir(path):
+331            directory_contents = os.listdir(path=path)
+332            for entryname in sorted(directory_contents):
+333                path_to_file = path + os.path.sep + entryname
+334                rights_str = unix_permissions(path_to_file)
+335                size_str = b_filesize(os.path.getsize(filename=path_to_file))
+336                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
+337
+338                if os.path.isdir(s=entryname):
+339                    if self.config.no_colors:
+340                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+341                    else:
+342                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+343                else:
+344                    if self.config.no_colors:
+345                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
+346                    else:
+347                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
+348        # lls <file>
+349        elif os.path.isfile(path):
+350            rights_str = unix_permissions(path)
+351            size_str = b_filesize(os.path.getsize(filename=path))
+352            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
+353            if self.config.no_colors:
+354                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
+355            else:
+356               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
+357        else:
+358            print("[!] No such file or directory.")
+359
+360    @command_arguments_required
+361    def command_lmkdir(self, arguments, command):
+362        # Command arguments required   : Yes
+363        # Active SMB connection needed : No
+364        # SMB share needed             : No
+365
+366        path = ' '.join(arguments)
+367
+368        # Split each dir
+369        if os.path.sep in path:
+370            path = path.strip(os.path.sep).split(os.path.sep)
+371        else:
+372            path = [path]
+373
+374        # Create each dir in the path
+375        for depth in range(1, len(path)+1):
+376            tmp_path = os.path.sep.join(path[:depth])
+377            if not os.path.exists(tmp_path):
+378                os.mkdir(path=tmp_path)
+379
+380    def command_lpwd(self, arguments, command):
+381        # Command arguments required   : No
+382        # Active SMB connection needed : No
+383        # SMB share needed             : No
+384
+385        print(os.getcwd())
+386
+387    @command_arguments_required
+388    def command_lrm(self, arguments, command):
+389        # Command arguments required   : Yes
+390        # Active SMB connection needed : No
+391        # SMB share needed             : No
+392
+393        path = ' '.join(arguments)
+394        if os.path.exists(path):
+395            if not os.path.isdir(s=path):
+396                try:
+397                    os.remove(path=path)
+398                except Exception as e:
+399                    print("[!] Error removing file '%s' : %s" % path)
+400            else:
+401                print("[!] Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
+402        else:
+403            print("[!] Path '%s' does not exist." % path)
+404
+405    @command_arguments_required
+406    def command_lrmdir(self, arguments, command):
+407        # Command arguments required   : Yes
+408        # Active SMB connection needed : No
+409        # SMB share needed             : No
+410
+411        path = ' '.join(arguments)
+412        if os.path.exists(path):
+413            if os.path.isdir(s=path):
+414                try:
+415                    shutil.rmtree(path=path)
+416                except Exception as e:
+417                    print("[!] Error removing directory '%s' : %s" % path)
+418            else:
+419                print("[!] Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
+420        else:
+421            print("[!] Path '%s' does not exist." % path)
+422
+423    def command_ltree(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            local_tree(path='.', config=self.config)
+430        else:
+431            local_tree(path=' '.join(arguments), config=self.config)
+432
+433    @active_smb_connection_needed
+434    @smb_share_is_set
+435    def command_ls(self, arguments, command):
+436        # Command arguments required   : No
+437        # Active SMB connection needed : Yes
+438        # SMB share needed             : Yes
+439
+440        # Read the files
+441        directory_contents = self.smbSession.list_contents(path=' '.join(arguments))
+442
+443        for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
+444            windows_ls_entry(directory_contents[longname], self.config)
+445            
+446    @command_arguments_required
+447    @active_smb_connection_needed
+448    @smb_share_is_set
+449    def command_mkdir(self, arguments, command):
+450        # Command arguments required   : Yes
+451        # Active SMB connection needed : Yes
+452        # SMB share needed             : Yes
+453
+454        path = ' '.join(arguments)
+455        self.smbSession.mkdir(path=path)
+456
+457    @command_arguments_required
+458    @active_smb_connection_needed
+459    @smb_share_is_set
+460    def command_module(self, arguments, command):
+461        module_name = arguments[0]
+462
+463        if module_name in self.modules.keys():
+464            module = self.modules[module_name](self.smbSession, self.config)
+465            module.run(' '.join(arguments[1:]))
+466        else:
+467            print("[!] Module '%s' does not exist." % module_name)
+468
+469    @command_arguments_required
+470    @active_smb_connection_needed
+471    @smb_share_is_set
+472    def command_put(self, arguments, command):
+473        # Command arguments required   : Yes
+474        # Active SMB connection needed : Yes
+475        # SMB share needed             : Yes
+476
+477        # Put files recursively
+478        if arguments[0] == "-r":
+479            localpath = ' '.join(arguments[1:])
+480            try:
+481                self.smbSession.put_file_recursively(localpath=localpath)
+482            except impacket.smbconnection.SessionError as e:
+483                print("[!] SMB Error: %s" % e)
+484
+485        # Put a single file
+486        else:
+487            localpath = ' '.join(arguments)
+488            try:
+489                self.smbSession.put_file(localpath=localpath)
+490            except impacket.smbconnection.SessionError as e:
+491                print("[!] SMB Error: %s" % e)
+492
+493    def command_reconnect(self, arguments, command):
+494        # Command arguments required   : No
+495        # Active SMB connection needed : No
+496        # SMB share needed             : No
+497
+498        self.smbSession.ping_smb_session()
+499        if self.smbSession.connected:
+500            self.smbSession.close_smb_session()
+501            self.smbSession.init_smb_session()
+502        else:
+503            self.smbSession.init_smb_session()
+504
+505    def command_reset(self, arguments, command):
+506        # Command arguments required   : No
+507        # Active SMB connection needed : No
+508        # SMB share needed             : No
+509        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
+510        sys.stdout.write('\x1b[v')  
+511        sys.stdout.write('\x1b[o') # Reset
+512        sys.stdout.flush()
+513
+514    @command_arguments_required
+515    @active_smb_connection_needed
+516    @smb_share_is_set
+517    def command_rm(self, arguments, command):
+518        # Command arguments required   : Yes
+519        # Active SMB connection needed : Yes
+520        # SMB share needed             : Yes
+521
+522        path = ' '.join(arguments)
+523        if self.smbSession.path_exists(path):
+524            if self.smbSession.path_isfile(path):
+525                try:
+526                    self.smbSession.rm(path=path)
+527                except Exception as e:
+528                    print("[!] Error removing file '%s' : %s" % path)
+529            else:
+530                print("[!] Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path)
+531        else:
+532            print("[!] Remote file '%s' does not exist." % path)
+533
+534    @command_arguments_required
+535    @active_smb_connection_needed
+536    @smb_share_is_set
+537    def command_rmdir(self, arguments, command):
+538        # Command arguments required   : Yes
+539        # Active SMB connection needed : Yes
+540        # SMB share needed             : Yes
+541
+542        path = ' '.join(arguments)
+543        if self.smbSession.path_exists(path):
+544            if self.smbSession.path_isdir(path):
+545                try:
+546                    self.smbSession.rmdir(path=path)
+547                except Exception as e:
+548                    print("[!] Error removing directory '%s' : %s" % path)
+549            else:
+550                print("[!] Cannot delete '%s': This is a file, use 'rm <file>' instead." % path)
+551        else:
+552            print("[!] Remote directory '%s' does not exist." % path)
+553
+554    @active_smb_connection_needed
+555    def command_shares(self, arguments, command):
+556        # Command arguments required   : No
+557        # Active SMB connection needed : Yes
+558        # SMB share needed             : No
+559
+560        shares = self.smbSession.list_shares()
+561        if len(shares.keys()) != 0:
+562            table = Table(title=None)
+563            table.add_column("Share")
+564            table.add_column("Hidden")
+565            table.add_column("Type")
+566            table.add_column("Description", justify="left")
+567
+568            for sharename in sorted(shares.keys()):
+569                is_hidden = bool(sharename.endswith('$'))
+570                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
+571                if is_hidden:
+572                    table.add_row(sharename, str(is_hidden), types, shares[sharename]["comment"])
+573                else:
+574                    table.add_row(sharename, str(is_hidden), types, shares[sharename]["comment"])
+575
+576            Console().print(table)
+577        else:
+578            print("[!] No share served on '%s'" % self.smbSession.address)
+579
+580    @active_smb_connection_needed
+581    @smb_share_is_set
+582    def command_tree(self, arguments, command):
+583        # Command arguments required   : No
+584        # Active SMB connection needed : Yes
+585        # SMB share needed             : Yes
+586
+587        if len(arguments) == 0:
+588            self.smbSession.tree(path='.')
+589        else:
+590            self.smbSession.tree(path=' '.join(arguments))
+591
+592    @command_arguments_required
+593    @active_smb_connection_needed
+594    def command_use(self, arguments, command):
+595        # Command arguments required   : Yes
+596        # Active SMB connection needed : Yes
+597        # SMB share needed             : No
+598
+599        sharename = ' '.join(arguments)
+600        # Reload the list of shares
+601        shares = self.smbSession.list_shares()
+602        shares = [s.lower() for s in shares.keys()]
+603        if sharename.lower() in shares:
+604            self.smbSession.set_share(sharename)
+605        else:
+606            print("[!] No share named '%s' on '%s'" % (sharename, self.smbSession.address))
+607
+608    # Private functions =======================================================
+609
+610    def __load_modules(self):
+611        
+612
+613        self.modules.clear()
+614
+615        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
+616        if self.config.debug:
+617            print("[debug] Loading modules from %s ..." % modules_dir)
+618        sys.path.extend([modules_dir])
+619
+620        for file in os.listdir(modules_dir):
+621            filepath = os.path.normpath(modules_dir + os.path.sep + file)
+622            if file.endswith('.py'):
+623                if os.path.isfile(filepath) and file not in ["__init__.py"]:
+624                    try:
+625                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
+626                        module = module_file.__getattribute__(file[:-3])
+627                        self.modules[module.name.lower()] = module
+628                    except AttributeError as e:
+629                        pass
+630
+631        if self.config.debug:
+632            if len(self.modules.keys()) == 0:
+633                print("[debug] Loaded 0 modules.")
+634            elif len(self.modules.keys()) == 1:
+635                print("[debug] Loaded 1 module:")
+636            else:
+637                print("[debug] Loaded %d modules:" % len(self.modules.keys()))
+638            for modulename in sorted(self.modules.keys()):
+639                print("[debug] %s : \"%s\"" % (module.name, module.description))
+640
+641        if self.commandCompleterObject is not None:
+642            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
+643
+644    def __prompt(self):
+645        """
+646        Prints the command prompt for the interactive shell.
+647
+648        This method constructs and returns the command prompt string based on the current state of the SMB session.
+649        The prompt indicates the connection status with a visual symbol and displays the current working directory
+650        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
+651
+652        Returns:
+653            str: The formatted command prompt string.
+654        """
+655
+656        self.smbSession.ping_smb_session()
+657        if self.smbSession.connected:
+658            if self.config.no_colors:
+659                connected_dot = "[v]"
+660            else:
+661                connected_dot = "\x1b[1;92m⏺\x1b[0m"
+662        else:
+663            if self.config.no_colors:
+664                connected_dot = "[x]"
+665            else:
+666                connected_dot = "\x1b[1;91m⏺\x1b[0m"
+667        
+668        if self.smbSession.smb_share is None:
+669            if self.config.no_colors:
+670                str_prompt = "%s[\\\\%s\\]> " % (connected_dot, self.smbSession.address)
+671            else:
+672                str_prompt = "%s[\x1b[1;94m\\\\%s\\\x1b[0m]> " % (connected_dot, self.smbSession.address)
+673        else:
+674            str_path = "\\\\%s\\%s\\%s" % (self.smbSession.address, self.smbSession.smb_share, self.smbSession.smb_cwd.lstrip(ntpath.sep))
+675            if self.config.no_colors:
+676                str_prompt = "%s[%s]> " % (connected_dot, str_path)
+677            else:
+678                str_prompt = "%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, str_path)
+679
+680        return str_prompt
+
+ + +
+
+ +
+ + def + command_arguments_required(func): + + + +
+ +
26def command_arguments_required(func):
+27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+34    return wrapper
+
+ + + + +
+
+ +
+ + def + active_smb_connection_needed(func): + + + +
+ +
36def active_smb_connection_needed(func):
+37    def wrapper(*args, **kwargs):
+38        self, arguments,command  = args[0], args[1], args[2]
+39        #
+40        self.smbSession.ping_smb_session()
+41        if self.smbSession.connected:
+42            return func(*args, **kwargs)
+43        else:
+44            print("[!] SMB Session is disconnected.")
+45            return None
+46    return wrapper
+
+ + + + +
+
+ +
+ + def + smb_share_is_set(func): + + + +
+ +
48def smb_share_is_set(func):
+49    def wrapper(*args, **kwargs):
+50        self, arguments,command  = args[0], args[1], args[2]
+51        if self.smbSession.smb_share is not None:
+52            return func(*args, **kwargs)
+53        else:
+54            print("[!] You must open a share first, try the 'use <share>' command.")
+55            return None
+56    return wrapper
+
+ + + + +
+
+ +
+ + class + InteractiveShell: + + + +
+ +
 59class InteractiveShell(object):
+ 60    """
+ 61    Class InteractiveShell is designed to manage the interactive command line interface for smbclient-ng.
+ 62    
+ 63    This class handles user input, executes commands, and manages the state of the SMB session. It provides
+ 64    a command line interface for users to interact with SMB shares, execute commands like directory listing,
+ 65    file transfer, and more.
+ 66
+ 67    Attributes:
+ 68        smbSession (SMBConnection): The active SMB connection session.
+ 69        debug (bool): Flag to enable or disable debug mode.
+ 70        smb_share (str): The current SMB share in use.
+ 71        smb_path (str): The current path within the SMB share.
+ 72        commandCompleterObject (CommandCompleter): Object to handle command completion and help generation.
+ 73
+ 74    Methods:
+ 75        __init__(self, smbSession, debug=False): Initializes the InteractiveShell with the given SMB session and debug mode.
+ 76        run(self): Starts the command line interface loop, processing user input until exit.
+ 77    """
+ 78    
+ 79    def __init__(self, smbSession, config):
+ 80        # Objects
+ 81        self.smbSession = smbSession
+ 82        self.config = config
+ 83        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
+ 84        readline.set_completer(self.commandCompleterObject.complete)
+ 85        readline.parse_and_bind("tab: complete")
+ 86        readline.set_completer_delims("\n")
+ 87        # Additional modules
+ 88        self.modules = {}
+ 89        self.__load_modules()
+ 90
+ 91    def run(self):
+ 92        running = True
+ 93        while running:
+ 94            try:
+ 95                user_input = input(self.__prompt()).strip().split(" ")
+ 96                command, arguments = user_input[0].lower(), user_input[1:]
+ 97                
+ 98                # Exit the command line
+ 99                if command == "exit":
+100                    running = False
+101
+102                elif command in self.commandCompleterObject.commands.keys():
+103                    self.process_command(
+104                        command=command, 
+105                        arguments=arguments
+106                    )
+107
+108                elif command.strip() == "":
+109                    pass
+110
+111                # Fallback to unknown command
+112                else:
+113                    print("Unknown command. Type \"help\" for help.")
+114
+115            except KeyboardInterrupt as e:
+116                print()
+117
+118            except EOFError as e:
+119                print()
+120                running = False
+121
+122            except Exception as e:
+123                if self.config.debug:
+124                    traceback.print_exc()
+125                print("[!] Error: %s" % str(e))
+126
+127    def process_command(self, command, arguments=[]):
+128        # Skip
+129        if command.strip() == "":
+130            pass
+131        
+132        # Display help
+133        elif command == "help":
+134            self.command_help(arguments, command)
+135
+136        # Closes the current SMB session
+137        elif command == "close":
+138            self.command_close(arguments, command)
+139               
+140        # Change directory in the current share
+141        elif command == "cd":
+142            self.command_cd(arguments, command)
+143
+144        # Get a file
+145        elif command == "get":
+146            self.command_get(arguments, command)
+147
+148        # SMB server info
+149        elif command == "info":
+150            self.command_info(arguments, command)
+151
+152        # List directory contents in a share
+153        elif command in ["ls", "dir"]:
+154            self.command_ls(arguments, command)
+155
+156        # Creates a new remote directory
+157        elif command == "mkdir":
+158            self.command_mkdir(arguments, command)
+159
+160        # Put a file
+161        elif command == "put":
+162            self.command_put(arguments, command)
+163
+164        # Changes the current local directory
+165        elif command == "lcd":
+166            self.command_lcd(arguments, command)
+167
+168        # Lists the contents of the current local directory
+169        elif command == "lls":
+170            self.command_lls(arguments, command)
+171
+172        # Creates a new local directory
+173        elif command == "lmkdir":
+174            self.command_lmkdir(arguments, command)
+175
+176        # Shows the current local directory
+177        elif command == "lpwd":
+178            self.command_lpwd(arguments, command)
+179
+180        # Removes a local file
+181        elif command == "lrm":
+182            self.command_lrm(arguments, command)
+183
+184        # Removes a local directory
+185        elif command == "lrmdir":
+186            self.command_lrmdir(arguments, command)
+187
+188        # Shows the current local directory
+189        elif command == "ltree":
+190            self.command_ltree(arguments, command)
+191
+192        # Modules
+193        elif command == "module":
+194            self.command_module(arguments, command)
+195
+196        # Reconnects the current SMB session
+197        elif command in ["connect", "reconnect"]:
+198            self.command_reconnect(arguments, command)
+199
+200        # Reset the TTY output
+201        elif command == "reset":
+202            self.command_reset(arguments, command)
+203
+204        # Removes a remote file
+205        elif command == "rm":
+206            self.command_rm(arguments, command)
+207            
+208        # Removes a remote directory
+209        elif command == "rmdir":
+210            self.command_rmdir(arguments, command)
+211
+212        # List shares
+213        elif command == "shares":
+214            self.command_shares(arguments, command)
+215        
+216        # Displays a tree view of the CWD
+217        elif command == "tree":
+218            self.command_tree(arguments, command)
+219        
+220        # Use a share
+221        elif command == "use":
+222            self.command_use(arguments, command)
+223
+224    # Commands ================================================================
+225
+226    @command_arguments_required
+227    @active_smb_connection_needed
+228    @smb_share_is_set
+229    def command_cd(self, arguments, command):
+230        # Command arguments required   : Yes
+231        # Active SMB connection needed : Yes
+232        # SMB share needed             : Yes
+233
+234        path = ' '.join(arguments)
+235        try:
+236            self.smbSession.set_cwd(path=path)
+237        except impacket.smbconnection.SessionError as e:
+238            print("[!] SMB Error: %s" % e)
+239
+240    def command_close(self, arguments, command):
+241        # Command arguments required   : No
+242        # Active SMB connection needed : No
+243        # SMB share needed             : No
+244
+245        self.smbSession.ping_smb_session()
+246        if self.smbSession.connected:
+247            self.smbSession.close_smb_session()
+248
+249    @command_arguments_required
+250    @active_smb_connection_needed
+251    @smb_share_is_set
+252    def command_get(self, arguments, command):
+253        # Command arguments required   : Yes
+254        # Active SMB connection needed : Yes
+255        # SMB share needed             : Yes
+256
+257        # Get files recursively
+258        if arguments[0] == "-r":
+259            path = ' '.join(arguments[1:]).replace('/', ntpath.sep)
+260            try:
+261                self.smbSession.get_file_recursively(path=path)
+262            except impacket.smbconnection.SessionError as e:
+263                print("[!] SMB Error: %s" % e)
+264        # Get a single file
+265        else:
+266            path = ' '.join(arguments).replace('/', ntpath.sep)
+267            try:
+268                self.smbSession.get_file(path=path)
+269            except impacket.smbconnection.SessionError as e:
+270                print("[!] SMB Error: %s" % e)
+271
+272    def command_help(self, arguments, command):
+273        # Command arguments required   : No
+274        # Active SMB connection needed : No
+275        # SMB share needed             : No
+276
+277        if len(arguments) != 0:
+278            self.commandCompleterObject.print_help(command=arguments[0])
+279        else:
+280            self.commandCompleterObject.print_help(command=None)
+281
+282    @active_smb_connection_needed
+283    def command_info(self, arguments, command):
+284        # Command arguments required   : No
+285        # Active SMB connection needed : Yes
+286        # SMB share needed             : No
+287
+288        print_server_info = False
+289        print_share_info = False
+290        if len(arguments) != 0:
+291            print_server_info = (arguments[0].lower() == "server")
+292            print_share_info = (arguments[0].lower() == "share")
+293        else:
+294            print_server_info = True
+295            print_share_info = True
+296
+297        try:
+298            self.smbSession.info(
+299                share=print_share_info,
+300                server=print_server_info
+301            )
+302        except impacket.smbconnection.SessionError as e:
+303            print("[!] SMB Error: %s" % e)
+304
+305    @command_arguments_required
+306    def command_lcd(self, arguments, command):
+307        # Command arguments required   : Yes
+308        # Active SMB connection needed : No
+309        # SMB share needed             : No
+310
+311        path = ' '.join(arguments)
+312        if os.path.exists(path=path):
+313            if os.path.isdir(s=path):
+314                os.chdir(path=path)
+315            else:
+316                print("[!] Path '%s' is not a directory." % path)
+317        else:
+318            print("[!] Directory '%s' does not exists." % path)
+319
+320    def command_lls(self, arguments, command):
+321        # Command arguments required   : No
+322        # Active SMB connection needed : No
+323        # SMB share needed             : No
+324
+325        if len(arguments) == 0:
+326            path = '.'
+327        else:
+328            path = ' '.join(arguments)
+329
+330        # lls <directory>
+331        if os.path.isdir(path):
+332            directory_contents = os.listdir(path=path)
+333            for entryname in sorted(directory_contents):
+334                path_to_file = path + os.path.sep + entryname
+335                rights_str = unix_permissions(path_to_file)
+336                size_str = b_filesize(os.path.getsize(filename=path_to_file))
+337                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
+338
+339                if os.path.isdir(s=entryname):
+340                    if self.config.no_colors:
+341                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+342                    else:
+343                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+344                else:
+345                    if self.config.no_colors:
+346                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
+347                    else:
+348                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
+349        # lls <file>
+350        elif os.path.isfile(path):
+351            rights_str = unix_permissions(path)
+352            size_str = b_filesize(os.path.getsize(filename=path))
+353            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
+354            if self.config.no_colors:
+355                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
+356            else:
+357               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
+358        else:
+359            print("[!] No such file or directory.")
+360
+361    @command_arguments_required
+362    def command_lmkdir(self, arguments, command):
+363        # Command arguments required   : Yes
+364        # Active SMB connection needed : No
+365        # SMB share needed             : No
+366
+367        path = ' '.join(arguments)
+368
+369        # Split each dir
+370        if os.path.sep in path:
+371            path = path.strip(os.path.sep).split(os.path.sep)
+372        else:
+373            path = [path]
+374
+375        # Create each dir in the path
+376        for depth in range(1, len(path)+1):
+377            tmp_path = os.path.sep.join(path[:depth])
+378            if not os.path.exists(tmp_path):
+379                os.mkdir(path=tmp_path)
+380
+381    def command_lpwd(self, arguments, command):
+382        # Command arguments required   : No
+383        # Active SMB connection needed : No
+384        # SMB share needed             : No
+385
+386        print(os.getcwd())
+387
+388    @command_arguments_required
+389    def command_lrm(self, arguments, command):
+390        # Command arguments required   : Yes
+391        # Active SMB connection needed : No
+392        # SMB share needed             : No
+393
+394        path = ' '.join(arguments)
+395        if os.path.exists(path):
+396            if not os.path.isdir(s=path):
+397                try:
+398                    os.remove(path=path)
+399                except Exception as e:
+400                    print("[!] Error removing file '%s' : %s" % path)
+401            else:
+402                print("[!] Cannot delete '%s'. It is a directory, use 'lrmdir <directory>' instead." % path)
+403        else:
+404            print("[!] Path '%s' does not exist." % path)
+405
+406    @command_arguments_required
+407    def command_lrmdir(self, arguments, command):
+408        # Command arguments required   : Yes
+409        # Active SMB connection needed : No
+410        # SMB share needed             : No
+411
+412        path = ' '.join(arguments)
+413        if os.path.exists(path):
+414            if os.path.isdir(s=path):
+415                try:
+416                    shutil.rmtree(path=path)
+417                except Exception as e:
+418                    print("[!] Error removing directory '%s' : %s" % path)
+419            else:
+420                print("[!] Cannot delete '%s'. It is a file, use 'lrm <file>' instead." % path)
+421        else:
+422            print("[!] Path '%s' does not exist." % path)
+423
+424    def command_ltree(self, arguments, command):
+425        # Command arguments required   : No
+426        # Active SMB connection needed : No
+427        # SMB share needed             : No
+428
+429        if len(arguments) == 0:
+430            local_tree(path='.', config=self.config)
+431        else:
+432            local_tree(path=' '.join(arguments), config=self.config)
+433
+434    @active_smb_connection_needed
+435    @smb_share_is_set
+436    def command_ls(self, arguments, command):
+437        # Command arguments required   : No
+438        # Active SMB connection needed : Yes
+439        # SMB share needed             : Yes
+440
+441        # Read the files
+442        directory_contents = self.smbSession.list_contents(path=' '.join(arguments))
+443
+444        for longname in sorted(directory_contents.keys(), key=lambda x:x.lower()):
+445            windows_ls_entry(directory_contents[longname], self.config)
+446            
+447    @command_arguments_required
+448    @active_smb_connection_needed
+449    @smb_share_is_set
+450    def command_mkdir(self, arguments, command):
+451        # Command arguments required   : Yes
+452        # Active SMB connection needed : Yes
+453        # SMB share needed             : Yes
+454
+455        path = ' '.join(arguments)
+456        self.smbSession.mkdir(path=path)
+457
+458    @command_arguments_required
+459    @active_smb_connection_needed
+460    @smb_share_is_set
+461    def command_module(self, arguments, command):
+462        module_name = arguments[0]
+463
+464        if module_name in self.modules.keys():
+465            module = self.modules[module_name](self.smbSession, self.config)
+466            module.run(' '.join(arguments[1:]))
+467        else:
+468            print("[!] Module '%s' does not exist." % module_name)
+469
+470    @command_arguments_required
+471    @active_smb_connection_needed
+472    @smb_share_is_set
+473    def command_put(self, arguments, command):
+474        # Command arguments required   : Yes
+475        # Active SMB connection needed : Yes
+476        # SMB share needed             : Yes
+477
+478        # Put files recursively
+479        if arguments[0] == "-r":
+480            localpath = ' '.join(arguments[1:])
+481            try:
+482                self.smbSession.put_file_recursively(localpath=localpath)
+483            except impacket.smbconnection.SessionError as e:
+484                print("[!] SMB Error: %s" % e)
+485
+486        # Put a single file
+487        else:
+488            localpath = ' '.join(arguments)
+489            try:
+490                self.smbSession.put_file(localpath=localpath)
+491            except impacket.smbconnection.SessionError as e:
+492                print("[!] SMB Error: %s" % e)
+493
+494    def command_reconnect(self, arguments, command):
+495        # Command arguments required   : No
+496        # Active SMB connection needed : No
+497        # SMB share needed             : No
+498
+499        self.smbSession.ping_smb_session()
+500        if self.smbSession.connected:
+501            self.smbSession.close_smb_session()
+502            self.smbSession.init_smb_session()
+503        else:
+504            self.smbSession.init_smb_session()
+505
+506    def command_reset(self, arguments, command):
+507        # Command arguments required   : No
+508        # Active SMB connection needed : No
+509        # SMB share needed             : No
+510        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
+511        sys.stdout.write('\x1b[v')  
+512        sys.stdout.write('\x1b[o') # Reset
+513        sys.stdout.flush()
+514
+515    @command_arguments_required
+516    @active_smb_connection_needed
+517    @smb_share_is_set
+518    def command_rm(self, arguments, command):
+519        # Command arguments required   : Yes
+520        # Active SMB connection needed : Yes
+521        # SMB share needed             : Yes
+522
+523        path = ' '.join(arguments)
+524        if self.smbSession.path_exists(path):
+525            if self.smbSession.path_isfile(path):
+526                try:
+527                    self.smbSession.rm(path=path)
+528                except Exception as e:
+529                    print("[!] Error removing file '%s' : %s" % path)
+530            else:
+531                print("[!] Cannot delete '%s': This is a directory, use 'rmdir <directory>' instead." % path)
+532        else:
+533            print("[!] Remote file '%s' does not exist." % path)
+534
+535    @command_arguments_required
+536    @active_smb_connection_needed
+537    @smb_share_is_set
+538    def command_rmdir(self, arguments, command):
+539        # Command arguments required   : Yes
+540        # Active SMB connection needed : Yes
+541        # SMB share needed             : Yes
+542
+543        path = ' '.join(arguments)
+544        if self.smbSession.path_exists(path):
+545            if self.smbSession.path_isdir(path):
+546                try:
+547                    self.smbSession.rmdir(path=path)
+548                except Exception as e:
+549                    print("[!] Error removing directory '%s' : %s" % path)
+550            else:
+551                print("[!] Cannot delete '%s': This is a file, use 'rm <file>' instead." % path)
+552        else:
+553            print("[!] Remote directory '%s' does not exist." % path)
+554
+555    @active_smb_connection_needed
+556    def command_shares(self, arguments, command):
+557        # Command arguments required   : No
+558        # Active SMB connection needed : Yes
+559        # SMB share needed             : No
+560
+561        shares = self.smbSession.list_shares()
+562        if len(shares.keys()) != 0:
+563            table = Table(title=None)
+564            table.add_column("Share")
+565            table.add_column("Hidden")
+566            table.add_column("Type")
+567            table.add_column("Description", justify="left")
+568
+569            for sharename in sorted(shares.keys()):
+570                is_hidden = bool(sharename.endswith('$'))
+571                types = ', '.join([s.replace("STYPE_","") for s in shares[sharename]["type"]])
+572                if is_hidden:
+573                    table.add_row(sharename, str(is_hidden), types, shares[sharename]["comment"])
+574                else:
+575                    table.add_row(sharename, str(is_hidden), types, shares[sharename]["comment"])
+576
+577            Console().print(table)
+578        else:
+579            print("[!] No share served on '%s'" % self.smbSession.address)
+580
+581    @active_smb_connection_needed
+582    @smb_share_is_set
+583    def command_tree(self, arguments, command):
+584        # Command arguments required   : No
+585        # Active SMB connection needed : Yes
+586        # SMB share needed             : Yes
+587
+588        if len(arguments) == 0:
+589            self.smbSession.tree(path='.')
+590        else:
+591            self.smbSession.tree(path=' '.join(arguments))
+592
+593    @command_arguments_required
+594    @active_smb_connection_needed
+595    def command_use(self, arguments, command):
+596        # Command arguments required   : Yes
+597        # Active SMB connection needed : Yes
+598        # SMB share needed             : No
+599
+600        sharename = ' '.join(arguments)
+601        # Reload the list of shares
+602        shares = self.smbSession.list_shares()
+603        shares = [s.lower() for s in shares.keys()]
+604        if sharename.lower() in shares:
+605            self.smbSession.set_share(sharename)
+606        else:
+607            print("[!] No share named '%s' on '%s'" % (sharename, self.smbSession.address))
+608
+609    # Private functions =======================================================
+610
+611    def __load_modules(self):
+612        
+613
+614        self.modules.clear()
+615
+616        modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules")
+617        if self.config.debug:
+618            print("[debug] Loading modules from %s ..." % modules_dir)
+619        sys.path.extend([modules_dir])
+620
+621        for file in os.listdir(modules_dir):
+622            filepath = os.path.normpath(modules_dir + os.path.sep + file)
+623            if file.endswith('.py'):
+624                if os.path.isfile(filepath) and file not in ["__init__.py"]:
+625                    try:
+626                        module_file = import_module('smbclientng.modules.%s' % (file[:-3]))
+627                        module = module_file.__getattribute__(file[:-3])
+628                        self.modules[module.name.lower()] = module
+629                    except AttributeError as e:
+630                        pass
+631
+632        if self.config.debug:
+633            if len(self.modules.keys()) == 0:
+634                print("[debug] Loaded 0 modules.")
+635            elif len(self.modules.keys()) == 1:
+636                print("[debug] Loaded 1 module:")
+637            else:
+638                print("[debug] Loaded %d modules:" % len(self.modules.keys()))
+639            for modulename in sorted(self.modules.keys()):
+640                print("[debug] %s : \"%s\"" % (module.name, module.description))
+641
+642        if self.commandCompleterObject is not None:
+643            self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys())
+644
+645    def __prompt(self):
+646        """
+647        Prints the command prompt for the interactive shell.
+648
+649        This method constructs and returns the command prompt string based on the current state of the SMB session.
+650        The prompt indicates the connection status with a visual symbol and displays the current working directory
+651        or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration.
+652
+653        Returns:
+654            str: The formatted command prompt string.
+655        """
+656
+657        self.smbSession.ping_smb_session()
+658        if self.smbSession.connected:
+659            if self.config.no_colors:
+660                connected_dot = "[v]"
+661            else:
+662                connected_dot = "\x1b[1;92m⏺\x1b[0m"
+663        else:
+664            if self.config.no_colors:
+665                connected_dot = "[x]"
+666            else:
+667                connected_dot = "\x1b[1;91m⏺\x1b[0m"
+668        
+669        if self.smbSession.smb_share is None:
+670            if self.config.no_colors:
+671                str_prompt = "%s[\\\\%s\\]> " % (connected_dot, self.smbSession.address)
+672            else:
+673                str_prompt = "%s[\x1b[1;94m\\\\%s\\\x1b[0m]> " % (connected_dot, self.smbSession.address)
+674        else:
+675            str_path = "\\\\%s\\%s\\%s" % (self.smbSession.address, self.smbSession.smb_share, self.smbSession.smb_cwd.lstrip(ntpath.sep))
+676            if self.config.no_colors:
+677                str_prompt = "%s[%s]> " % (connected_dot, str_path)
+678            else:
+679                str_prompt = "%s[\x1b[1;94m%s\x1b[0m]> " % (connected_dot, str_path)
+680
+681        return str_prompt
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+ +
+ + InteractiveShell(smbSession, config) + + + +
+ +
79    def __init__(self, smbSession, config):
+80        # Objects
+81        self.smbSession = smbSession
+82        self.config = config
+83        self.commandCompleterObject = CommandCompleter(smbSession=self.smbSession, config=self.config)
+84        readline.set_completer(self.commandCompleterObject.complete)
+85        readline.parse_and_bind("tab: complete")
+86        readline.set_completer_delims("\n")
+87        # Additional modules
+88        self.modules = {}
+89        self.__load_modules()
+
+ + + + +
+
+
+ smbSession + + +
+ + + + +
+
+
+ config + + +
+ + + + +
+
+
+ commandCompleterObject + + +
+ + + + +
+
+
+ modules + + +
+ + + + +
+
+ +
+ + def + run(self): + + + +
+ +
 91    def run(self):
+ 92        running = True
+ 93        while running:
+ 94            try:
+ 95                user_input = input(self.__prompt()).strip().split(" ")
+ 96                command, arguments = user_input[0].lower(), user_input[1:]
+ 97                
+ 98                # Exit the command line
+ 99                if command == "exit":
+100                    running = False
+101
+102                elif command in self.commandCompleterObject.commands.keys():
+103                    self.process_command(
+104                        command=command, 
+105                        arguments=arguments
+106                    )
+107
+108                elif command.strip() == "":
+109                    pass
+110
+111                # Fallback to unknown command
+112                else:
+113                    print("Unknown command. Type \"help\" for help.")
+114
+115            except KeyboardInterrupt as e:
+116                print()
+117
+118            except EOFError as e:
+119                print()
+120                running = False
+121
+122            except Exception as e:
+123                if self.config.debug:
+124                    traceback.print_exc()
+125                print("[!] Error: %s" % str(e))
+
+ + + + +
+
+ +
+ + def + process_command(self, command, arguments=[]): + + + +
+ +
127    def process_command(self, command, arguments=[]):
+128        # Skip
+129        if command.strip() == "":
+130            pass
+131        
+132        # Display help
+133        elif command == "help":
+134            self.command_help(arguments, command)
+135
+136        # Closes the current SMB session
+137        elif command == "close":
+138            self.command_close(arguments, command)
+139               
+140        # Change directory in the current share
+141        elif command == "cd":
+142            self.command_cd(arguments, command)
+143
+144        # Get a file
+145        elif command == "get":
+146            self.command_get(arguments, command)
+147
+148        # SMB server info
+149        elif command == "info":
+150            self.command_info(arguments, command)
+151
+152        # List directory contents in a share
+153        elif command in ["ls", "dir"]:
+154            self.command_ls(arguments, command)
+155
+156        # Creates a new remote directory
+157        elif command == "mkdir":
+158            self.command_mkdir(arguments, command)
+159
+160        # Put a file
+161        elif command == "put":
+162            self.command_put(arguments, command)
+163
+164        # Changes the current local directory
+165        elif command == "lcd":
+166            self.command_lcd(arguments, command)
+167
+168        # Lists the contents of the current local directory
+169        elif command == "lls":
+170            self.command_lls(arguments, command)
+171
+172        # Creates a new local directory
+173        elif command == "lmkdir":
+174            self.command_lmkdir(arguments, command)
+175
+176        # Shows the current local directory
+177        elif command == "lpwd":
+178            self.command_lpwd(arguments, command)
+179
+180        # Removes a local file
+181        elif command == "lrm":
+182            self.command_lrm(arguments, command)
+183
+184        # Removes a local directory
+185        elif command == "lrmdir":
+186            self.command_lrmdir(arguments, command)
+187
+188        # Shows the current local directory
+189        elif command == "ltree":
+190            self.command_ltree(arguments, command)
+191
+192        # Modules
+193        elif command == "module":
+194            self.command_module(arguments, command)
+195
+196        # Reconnects the current SMB session
+197        elif command in ["connect", "reconnect"]:
+198            self.command_reconnect(arguments, command)
+199
+200        # Reset the TTY output
+201        elif command == "reset":
+202            self.command_reset(arguments, command)
+203
+204        # Removes a remote file
+205        elif command == "rm":
+206            self.command_rm(arguments, command)
+207            
+208        # Removes a remote directory
+209        elif command == "rmdir":
+210            self.command_rmdir(arguments, command)
+211
+212        # List shares
+213        elif command == "shares":
+214            self.command_shares(arguments, command)
+215        
+216        # Displays a tree view of the CWD
+217        elif command == "tree":
+218            self.command_tree(arguments, command)
+219        
+220        # Use a share
+221        elif command == "use":
+222            self.command_use(arguments, command)
+
+ + + + +
+
+ +
+ + def + command_cd(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_close(self, arguments, command): + + + +
+ +
240    def command_close(self, arguments, command):
+241        # Command arguments required   : No
+242        # Active SMB connection needed : No
+243        # SMB share needed             : No
+244
+245        self.smbSession.ping_smb_session()
+246        if self.smbSession.connected:
+247            self.smbSession.close_smb_session()
+
+ + + + +
+
+ +
+ + def + command_get(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_help(self, arguments, command): + + + +
+ +
272    def command_help(self, arguments, command):
+273        # Command arguments required   : No
+274        # Active SMB connection needed : No
+275        # SMB share needed             : No
+276
+277        if len(arguments) != 0:
+278            self.commandCompleterObject.print_help(command=arguments[0])
+279        else:
+280            self.commandCompleterObject.print_help(command=None)
+
+ + + + +
+
+ +
+ + def + command_info(*args, **kwargs): + + + +
+ +
37    def wrapper(*args, **kwargs):
+38        self, arguments,command  = args[0], args[1], args[2]
+39        #
+40        self.smbSession.ping_smb_session()
+41        if self.smbSession.connected:
+42            return func(*args, **kwargs)
+43        else:
+44            print("[!] SMB Session is disconnected.")
+45            return None
+
+ + + + +
+
+ +
+ + def + command_lcd(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_lls(self, arguments, command): + + + +
+ +
320    def command_lls(self, arguments, command):
+321        # Command arguments required   : No
+322        # Active SMB connection needed : No
+323        # SMB share needed             : No
+324
+325        if len(arguments) == 0:
+326            path = '.'
+327        else:
+328            path = ' '.join(arguments)
+329
+330        # lls <directory>
+331        if os.path.isdir(path):
+332            directory_contents = os.listdir(path=path)
+333            for entryname in sorted(directory_contents):
+334                path_to_file = path + os.path.sep + entryname
+335                rights_str = unix_permissions(path_to_file)
+336                size_str = b_filesize(os.path.getsize(filename=path_to_file))
+337                date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path_to_file)).strftime("%Y-%m-%d %H:%M")
+338
+339                if os.path.isdir(s=entryname):
+340                    if self.config.no_colors:
+341                        print("%s %10s  %s  %s%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+342                    else:
+343                        print("%s %10s  %s  \x1b[1;96m%s\x1b[0m%s" % (rights_str, size_str, date_str, entryname, os.path.sep))
+344                else:
+345                    if self.config.no_colors:
+346                        print("%s %10s  %s  %s" % (rights_str, size_str, date_str, entryname))
+347                    else:
+348                        print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, entryname))
+349        # lls <file>
+350        elif os.path.isfile(path):
+351            rights_str = unix_permissions(path)
+352            size_str = b_filesize(os.path.getsize(filename=path))
+353            date_str = datetime.datetime.fromtimestamp(os.path.getmtime(filename=path)).strftime("%Y-%m-%d %H:%M")
+354            if self.config.no_colors:
+355                print("%s %10s  %s  %s" % (rights_str, size_str, date_str, os.path.basename(path)))
+356            else:
+357               print("%s %10s  %s  \x1b[1m%s\x1b[0m" % (rights_str, size_str, date_str, os.path.basename(path))) 
+358        else:
+359            print("[!] No such file or directory.")
+
+ + + + +
+
+ +
+ + def + command_lmkdir(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_lpwd(self, arguments, command): + + + +
+ +
381    def command_lpwd(self, arguments, command):
+382        # Command arguments required   : No
+383        # Active SMB connection needed : No
+384        # SMB share needed             : No
+385
+386        print(os.getcwd())
+
+ + + + +
+
+ +
+ + def + command_lrm(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_lrmdir(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_ltree(self, arguments, command): + + + +
+ +
424    def command_ltree(self, arguments, command):
+425        # Command arguments required   : No
+426        # Active SMB connection needed : No
+427        # SMB share needed             : No
+428
+429        if len(arguments) == 0:
+430            local_tree(path='.', config=self.config)
+431        else:
+432            local_tree(path=' '.join(arguments), config=self.config)
+
+ + + + +
+
+ +
+ + def + command_ls(*args, **kwargs): + + + +
+ +
37    def wrapper(*args, **kwargs):
+38        self, arguments,command  = args[0], args[1], args[2]
+39        #
+40        self.smbSession.ping_smb_session()
+41        if self.smbSession.connected:
+42            return func(*args, **kwargs)
+43        else:
+44            print("[!] SMB Session is disconnected.")
+45            return None
+
+ + + + +
+
+ +
+ + def + command_mkdir(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_module(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_put(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_reconnect(self, arguments, command): + + + +
+ +
494    def command_reconnect(self, arguments, command):
+495        # Command arguments required   : No
+496        # Active SMB connection needed : No
+497        # SMB share needed             : No
+498
+499        self.smbSession.ping_smb_session()
+500        if self.smbSession.connected:
+501            self.smbSession.close_smb_session()
+502            self.smbSession.init_smb_session()
+503        else:
+504            self.smbSession.init_smb_session()
+
+ + + + +
+
+ +
+ + def + command_reset(self, arguments, command): + + + +
+ +
506    def command_reset(self, arguments, command):
+507        # Command arguments required   : No
+508        # Active SMB connection needed : No
+509        # SMB share needed             : No
+510        sys.stdout.write('\x1b[?25h') # Sets the cursor to on
+511        sys.stdout.write('\x1b[v')  
+512        sys.stdout.write('\x1b[o') # Reset
+513        sys.stdout.flush()
+
+ + + + +
+
+ +
+ + def + command_rm(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_rmdir(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+ +
+ + def + command_shares(*args, **kwargs): + + + +
+ +
37    def wrapper(*args, **kwargs):
+38        self, arguments,command  = args[0], args[1], args[2]
+39        #
+40        self.smbSession.ping_smb_session()
+41        if self.smbSession.connected:
+42            return func(*args, **kwargs)
+43        else:
+44            print("[!] SMB Session is disconnected.")
+45            return None
+
+ + + + +
+
+ +
+ + def + command_tree(*args, **kwargs): + + + +
+ +
37    def wrapper(*args, **kwargs):
+38        self, arguments,command  = args[0], args[1], args[2]
+39        #
+40        self.smbSession.ping_smb_session()
+41        if self.smbSession.connected:
+42            return func(*args, **kwargs)
+43        else:
+44            print("[!] SMB Session is disconnected.")
+45            return None
+
+ + + + +
+
+ +
+ + def + command_use(*args, **kwargs): + + + +
+ +
27    def wrapper(*args, **kwargs):
+28        self, arguments,command  = args[0], args[1], args[2]
+29        if len(arguments) != 0:
+30            return func(*args, **kwargs)
+31        else:
+32            self.commandCompleterObject.print_help(command=command)
+33            return None
+
+ + + + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/LocalFileIO.html b/documentation/smbclientng/core/LocalFileIO.html new file mode 100644 index 0000000..4aacd1b --- /dev/null +++ b/documentation/smbclientng/core/LocalFileIO.html @@ -0,0 +1,945 @@ + + + + + + + smbclientng.core.LocalFileIO API documentation + + + + + + + + + +
+
+

+smbclientng.core.LocalFileIO

+ + + + + + +
  1#!/usr/bin/env python3
+  2# -*- coding: utf-8 -*-
+  3# File name          : LocalFileIO.py
+  4# Author             : Podalirius (@podalirius_)
+  5# Date created       : 23 may 2024
+  6
+  7
+  8import os
+  9import ntpath
+ 10from rich.progress import BarColumn, DownloadColumn, Progress, TextColumn, TimeRemainingColumn, TransferSpeedColumn
+ 11
+ 12
+ 13class LocalFileIO(object):
+ 14    """
+ 15    Class LocalFileIO is designed to handle local file input/output operations within the smbclient-ng tool.
+ 16    It provides functionalities to open, read, write, and manage progress of file operations based on the expected size of the file.
+ 17
+ 18    Attributes:
+ 19        mode (str): The mode in which the file should be opened (e.g., 'rb', 'wb').
+ 20        path (str): The path to the file that needs to be handled.
+ 21        expected_size (int, optional): The expected size of the file in bytes. This is used to display progress.
+ 22        debug (bool): Flag to enable debug mode which provides additional output during operations.
+ 23
+ 24    Methods:
+ 25        __init__(self, mode, path=None, expected_size=None, debug=False): Initializes the LocalFileIO instance.
+ 26        write(self, data): Writes data to the file and updates the progress bar if expected size is provided.
+ 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):
+ 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)
+ 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.
+ 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        """
+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
+123
+124    def close(self, remove=False):
+125        """
+126        Closes the file descriptor and optionally removes the file.
+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)
+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)
+
+ + +
+
+ +
+ + class + LocalFileIO: + + + +
+ +
 14class LocalFileIO(object):
+ 15    """
+ 16    Class LocalFileIO is designed to handle local file input/output operations within the smbclient-ng tool.
+ 17    It provides functionalities to open, read, write, and manage progress of file operations based on the expected size of the file.
+ 18
+ 19    Attributes:
+ 20        mode (str): The mode in which the file should be opened (e.g., 'rb', 'wb').
+ 21        path (str): The path to the file that needs to be handled.
+ 22        expected_size (int, optional): The expected size of the file in bytes. This is used to display progress.
+ 23        debug (bool): Flag to enable debug mode which provides additional output during operations.
+ 24
+ 25    Methods:
+ 26        __init__(self, mode, path=None, expected_size=None, debug=False): Initializes the LocalFileIO instance.
+ 27        write(self, data): Writes data to the file and updates the progress bar if expected size is provided.
+ 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):
+ 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)
+ 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.
+ 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        """
+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
+124
+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)
+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)
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+ +
+ + LocalFileIO( mode, path=None, expected_size=None, keepRemotePath=False, debug=False) + + + +
+ +
31    def __init__(self, mode, path=None, expected_size=None, keepRemotePath=False, debug=False):
+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)
+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            )
+
+ + + + +
+
+
+ mode + + +
+ + + + +
+
+
+ path + + +
+ + + + +
+
+
+ dir + + +
+ + + + +
+
+
+ debug + + +
+ + + + +
+
+
+ expected_size + + +
+ + + + +
+
+
+ keepRemotePath + + +
+ + + + +
+
+ +
+ + def + write(self, data): + + + +
+ +
 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)
+
+ + +

Writes data to the file.

+ +

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.

+ +

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

+ +

Returns: + int: The number of bytes written.

+
+ + +
+
+ +
+ + def + read(self, size): + + + +
+ +
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        """
+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
+
+ + +

Reads a specified amount of data from the file.

+ +

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.

+ +

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

+ +

Returns: + bytes: The data read from the file.

+
+ + +
+
+ +
+ + def + close(self, remove=False): + + + +
+ +
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)
+140
+141        if self.expected_size is not None:
+142            self.__progress.stop()
+143        
+144        del self
+
+ + +

Closes the file descriptor and optionally removes the file.

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + set_error(self, message): + + + +
+ +
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)
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/Module.html b/documentation/smbclientng/core/Module.html new file mode 100644 index 0000000..f3a042e --- /dev/null +++ b/documentation/smbclientng/core/Module.html @@ -0,0 +1,529 @@ + + + + + + + smbclientng.core.Module API documentation + + + + + + + + + +
+
+

+smbclientng.core.Module

+ + + + + + +
 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 shlex
+ 9
+10
+11class Module(object):
+12    """
+13    A parent class for all modules in the smbclient-ng tool.
+14
+15    This class provides common attributes and methods that are shared among different modules.
+16    """
+17
+18    name = ""
+19    description = ""
+20    smbSession = None
+21    options = None
+22
+23    def __init__(self, smbSession, config):
+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        
+
+ + +
+
+ +
+ + class + Module: + + + +
+ +
12class Module(object):
+13    """
+14    A parent class for all modules in the smbclient-ng tool.
+15
+16    This class provides common attributes and methods that are shared among different modules.
+17    """
+18
+19    name = ""
+20    description = ""
+21    smbSession = None
+22    options = None
+23
+24    def __init__(self, smbSession, config):
+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
+
+ + +

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

+ +

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

+
+ + +
+ +
+ + Module(smbSession, config) + + + +
+ +
24    def __init__(self, smbSession, config):
+25        self.smbSession = smbSession
+26        self.config = config
+
+ + + + +
+
+
+ name = +'' + + +
+ + + + +
+
+
+ description = +'' + + +
+ + + + +
+
+
+ smbSession = +None + + +
+ + + + +
+
+
+ options = +None + + +
+ + + + +
+
+
+ config + + +
+ + + + +
+
+ +
+ + def + parseArgs(self): + + + +
+ +
28    def parseArgs(self):
+29        raise NotImplementedError("Subclasses must implement this method")
+
+ + + + +
+
+ +
+ + def + run(self): + + + +
+ +
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")
+
+ + +

Placeholder method for running the module.

+ +

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

+
+ + +
+
+ +
+ + def + processArguments(self, parser, arguments): + + + +
+ +
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
+
+ + + + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/ModuleArgumentParser.html b/documentation/smbclientng/core/ModuleArgumentParser.html new file mode 100644 index 0000000..1bb8040 --- /dev/null +++ b/documentation/smbclientng/core/ModuleArgumentParser.html @@ -0,0 +1,425 @@ + + + + + + + smbclientng.core.ModuleArgumentParser API documentation + + + + + + + + + +
+
+

+smbclientng.core.ModuleArgumentParser

+ + + + + + +
 1#!/usr/bin/env python3
+ 2# -*- coding: utf-8 -*-
+ 3# File name          : ModuleArgumentParser.py
+ 4# Author             : Podalirius (@podalirius_)
+ 5# Date created       : 23 may 2024
+ 6
+ 7
+ 8import argparse
+ 9import sys
+10
+11
+12class ModuleArgumentParser(argparse.ArgumentParser):
+13    """
+14    A custom argument parser for handling module-specific command-line arguments in the smbclientng application.
+15
+16    This class extends the argparse.ArgumentParser and provides custom error handling specific to the needs of smbclientng modules.
+17    It is designed to provide clear and user-friendly command-line interfaces for various modules within the smbclientng suite.
+18
+19    Attributes:
+20        None
+21
+22    Methods:
+23        error(message: str):
+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()
+
+ + +
+
+ +
+ + class + ModuleArgumentParser(argparse.ArgumentParser): + + + +
+ +
13class ModuleArgumentParser(argparse.ArgumentParser):
+14    """
+15    A custom argument parser for handling module-specific command-line arguments in the smbclientng application.
+16
+17    This class extends the argparse.ArgumentParser and provides custom error handling specific to the needs of smbclientng modules.
+18    It is designed to provide clear and user-friendly command-line interfaces for various modules within the smbclientng suite.
+19
+20    Attributes:
+21        None
+22
+23    Methods:
+24        error(message: str):
+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()
+
+ + +

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

+ +

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

+ +

Attributes: + None

+ +

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

+
+ + +
+ +
+ + def + error(self, message): + + + +
+ +
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()
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+
Inherited Members
+
+
argparse.ArgumentParser
+
ArgumentParser
+
prog
+
usage
+
epilog
+
formatter_class
+
fromfile_prefix_chars
+
add_help
+
allow_abbrev
+
exit_on_error
+
add_subparsers
+
parse_args
+
parse_known_args
+
convert_arg_line_to_args
+
parse_intermixed_args
+
parse_known_intermixed_args
+
format_usage
+
format_help
+
print_usage
+
print_help
+
exit
+ +
+
argparse._ActionsContainer
+
description
+
argument_default
+
prefix_chars
+
conflict_handler
+
register
+
set_defaults
+
get_default
+
add_argument
+
add_argument_group
+
add_mutually_exclusive_group
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/SMBSession.html b/documentation/smbclientng/core/SMBSession.html new file mode 100644 index 0000000..7ff2290 --- /dev/null +++ b/documentation/smbclientng/core/SMBSession.html @@ -0,0 +1,3716 @@ + + + + + + + smbclientng.core.SMBSession API documentation + + + + + + + + + +
+
+

+smbclientng.core.SMBSession

+ + + + + + +
  1#!/usr/bin/env python3
+  2# -*- coding: utf-8 -*-
+  3# File name          : smbclient-ng.py
+  4# Author             : Podalirius (@podalirius_)
+  5# Date created       : 20 may 2024
+  6
+  7
+  8import impacket.smbconnection
+  9import ntpath
+ 10import os
+ 11import re
+ 12import traceback
+ 13from smbclientng.core.LocalFileIO import LocalFileIO
+ 14from smbclientng.core.utils import b_filesize, STYPE_MASK
+ 15
+ 16
+ 17class SMBSession(object):
+ 18    """
+ 19    Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections.
+ 20    It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.
+ 21
+ 22    Attributes:
+ 23        address (str): The IP address or hostname of the SMB server.
+ 24        domain (str): The domain name for SMB server authentication.
+ 25        username (str): The username for SMB server authentication.
+ 26        password (str): The password for SMB server authentication.
+ 27        lmhash (str): The LM hash of the user's password, if available.
+ 28        nthash (str): The NT hash of the user's password, if available.
+ 29        use_kerberos (bool): A flag to determine whether to use Kerberos for authentication.
+ 30        kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication.
+ 31        debug (bool): A flag to enable debug output.
+ 32        smbClient (object): The SMB client object used for the connection.
+ 33        connected (bool): A flag to check the status of the connection.
+ 34        smb_share (str): The current SMB share in use.
+ 35        smb_path (str): The current path within the SMB share.
+ 36
+ 37    Methods:
+ 38        __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False):
+ 39            Initializes the SMBSession with the specified parameters.
+ 40        init_smb_session():
+ 41            Initializes the SMB session by connecting to the server and authenticating using the specified method.
+ 42    """
+ 43
+ 44    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
+ 45        super(SMBSession, self).__init__()
+ 46        # Objects
+ 47        self.config = config
+ 48
+ 49        # Target server
+ 50        self.address = address
+ 51
+ 52        # Credentials
+ 53        self.domain = domain
+ 54        self.username = username
+ 55        self.password = password 
+ 56        self.lmhash = lmhash
+ 57        self.nthash = nthash
+ 58        self.use_kerberos = use_kerberos
+ 59        self.kdcHost = kdcHost
+ 60
+ 61        self.smbClient = None
+ 62        self.connected = False
+ 63
+ 64        self.available_shares = {}
+ 65        self.smb_share = None
+ 66        self.smb_cwd = ""
+ 67
+ 68        self.list_shares()
+ 69
+ 70    # Connect and disconnect SMB session
+ 71
+ 72    def init_smb_session(self):
+ 73        """
+ 74        Initializes and establishes a session with the SMB server.
+ 75
+ 76        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
+ 77        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
+ 78
+ 79        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.
+ 80
+ 81        Returns:
+ 82            bool: True if the connection and authentication are successful, False otherwise.
+ 83        """
+ 84
+ 85        if self.config.debug:
+ 86            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
+ 87        try:
+ 88            self.smbClient = impacket.smbconnection.SMBConnection(
+ 89                remoteName=self.address,
+ 90                remoteHost=self.address,
+ 91                sess_port=int(445)
+ 92            )
+ 93        except OSError as err:
+ 94            print("[!] %s" % err)
+ 95            self.smbClient = None
+ 96
+ 97        self.connected = False
+ 98        if self.smbClient is not None:
+ 99            if self.use_kerberos:
+100                if self.config.debug:
+101                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
+102                self.connected = self.smbClient.kerberosLogin(
+103                    user=self.username,
+104                    password=self.password,
+105                    domain=self.domain,
+106                    lmhash=self.lmhash,
+107                    nthash=self.nthash,
+108                    aesKey=self.aesKey,
+109                    kdcHost=self.kdcHost
+110                )
+111
+112            else:
+113                if self.config.debug:
+114                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
+115                self.connected = self.smbClient.login(
+116                    user=self.username,
+117                    password=self.password,
+118                    domain=self.domain,
+119                    lmhash=self.lmhash,
+120                    nthash=self.nthash
+121                )
+122
+123            if self.connected:
+124                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+125            else:
+126                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+127
+128        return self.connected
+129
+130    def close_smb_session(self):
+131        """
+132        Closes the current SMB session by disconnecting the SMB client.
+133
+134        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
+135        and if so, it closes the connection and resets the connection status.
+136
+137        Raises:
+138            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
+139        """
+140
+141        if self.smbClient is not None:
+142            if self.connected:
+143                self.smbClient.close()
+144                self.connected = False
+145                print("[+] SMB connection closed successfully.")
+146            else:
+147                print("[!] No active SMB connection to close.")
+148        else:
+149            raise Exception("SMB client is not initialized.")
+150
+151    # Operations
+152
+153    def get_file(self, path=None, keepRemotePath=False):
+154        """
+155        Retrieves a file from the specified path on the SMB share.
+156
+157        This method attempts to retrieve a file from the given path within the currently connected SMB share.
+158        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
+159        file object and writing the contents of the remote file to it using the SMB client's getFile method.
+160
+161        Parameters:
+162            path (str): The path of the file to retrieve. If None, uses the current smb_path.
+163
+164        Returns:
+165            None
+166        """
+167
+168        tmp_file_path = self.smb_cwd + ntpath.sep + path
+169        matches = self.smbClient.listPath(
+170            shareName=self.smb_share, 
+171            path=tmp_file_path
+172        )
+173        
+174        for entry in matches:
+175            if entry.is_directory():
+176                print("[>] Skipping '%s' because it is a directory." % tmp_file_path)
+177            else:
+178                try:
+179                    if ntpath.sep in path:
+180                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
+181                    else:
+182                        outputfile = entry.get_longname()
+183                    f = LocalFileIO(
+184                        mode="wb", 
+185                        path=outputfile,
+186                        expected_size=entry.get_filesize(), 
+187                        debug=self.config.debug,
+188                        keepRemotePath=keepRemotePath
+189                    )
+190                    self.smbClient.getFile(
+191                        shareName=self.smb_share, 
+192                        pathName=tmp_file_path, 
+193                        callback=f.write
+194                    )
+195                    f.close()
+196                except (BrokenPipeError, KeyboardInterrupt) as e:
+197                    f.close()
+198                    print("\x1b[v\x1b[o\r[!] Interrupted.")
+199                    self.close_smb_session()
+200                    self.init_smb_session()
+201                        
+202        return None
+203
+204    def get_file_recursively(self, path=None):
+205        """
+206        Recursively retrieves files from a specified path on the SMB share.
+207
+208        This method navigates through all directories starting from the given path,
+209        and downloads all files found. It handles directories recursively, ensuring
+210        that all nested files are retrieved. The method skips over directory entries
+211        and handles errors gracefully, attempting to continue the operation where possible.
+212
+213        Parameters:
+214            path (str): The initial directory path from which to start the recursive file retrieval.
+215                        If None, it starts from the root of the configured SMB share.
+216        """
+217        
+218        def recurse_action(base_dir="", path=[]):
+219            remote_smb_path = base_dir + ntpath.sep.join(path)
+220            entries = self.smbClient.listPath(
+221                shareName=self.smb_share, 
+222                path=remote_smb_path + '\\*'
+223            )
+224            if len(entries) != 0:
+225                files = [entry for entry in entries if not entry.is_directory()]
+226                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
+227
+228                # Files
+229                if len(files) != 0:
+230                    print("[>] Retrieving files of '%s'" % remote_smb_path)
+231                for entry_file in files:
+232                    if not entry_file.is_directory():
+233                        f = LocalFileIO(
+234                            mode="wb",
+235                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+236                            expected_size=entry_file.get_filesize(),
+237                            debug=self.config.debug
+238                        )
+239                        try:
+240                            self.smbClient.getFile(
+241                                shareName=self.smb_share, 
+242                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+243                                callback=f.write
+244                            )
+245                            f.close()
+246                        except BrokenPipeError as err:
+247                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+248                            f.close(remove=True)
+249                            break
+250                        except Exception as err:
+251                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+252                            f.close(remove=True)
+253                
+254                # Directories
+255                for entry_directory in directories:
+256                    if entry_directory.is_directory():
+257                        recurse_action(
+258                            base_dir=self.smb_cwd, 
+259                            path=path+[entry_directory.get_longname()]
+260                        )                   
+261        # Entrypoint
+262        try:
+263            recurse_action(
+264                base_dir=self.smb_cwd, 
+265                path=[path]
+266            )
+267        except (BrokenPipeError, KeyboardInterrupt) as e:
+268            print("\x1b[v\x1b[o\r[!] Interrupted.")
+269            self.close_smb_session()
+270            self.init_smb_session()
+271
+272    def info(self, share=True, server=True):
+273        """
+274        Displays information about the server and optionally the shares.
+275
+276        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.
+277
+278        Parameters:
+279            share (bool): If True, display information about the current share.
+280            server (bool): If True, display information about the server.
+281
+282        Returns:
+283            None
+284        """
+285
+286        if server:
+287            if self.config.no_colors:
+288                print("[+] Server:")
+289                print("  ├─NetBIOS:")
+290                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
+291                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
+292                print("  ├─DNS:")
+293                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
+294                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
+295                print("  ├─OS:")
+296                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
+297                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
+298                print("  ├─Server:")
+299                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
+300                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
+301                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
+302                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+303                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
+304                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+305                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+306                print("  └─")
+307            else:
+308                print("[+] Server:")
+309                print("  ├─NetBIOS:")
+310                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
+311                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
+312                print("  ├─DNS:")
+313                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
+314                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
+315                print("  ├─OS:")
+316                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
+317                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()))
+318                print("  ├─Server:")
+319                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
+320                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
+321                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
+322                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+323                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
+324                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+325                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+326                print("  └─")
+327
+328        if share and self.smb_share is not None:
+329            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
+330            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
+331            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
+332            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
+333            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
+334            if self.config.no_colors:
+335                print("\n[+] Share:")
+336                print("  ├─ Name ──────────── : %s" % (share_name))
+337                print("  ├─ Description ───── : %s" % (share_comment))
+338                print("  ├─ Type ──────────── : %s" % (share_type))
+339                print("  └─ Raw type value ── : %s" % (share_rawtype))
+340            else:
+341                print("\n[+] Share:")
+342                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
+343                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
+344                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
+345                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
+346
+347    def list_contents(self, path=None):
+348        """
+349        Lists the contents of a specified directory on the SMB share.
+350
+351        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
+352        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
+353        the long names of the files and directories as keys and their respective SMB entry objects as values.
+354
+355        Args:
+356            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
+357            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
+358
+359        Returns:
+360            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
+361        """
+362        
+363        if path is None or len(path) == 0:
+364            path = self.smb_cwd
+365        path = path.rstrip(ntpath.sep) + ntpath.sep + "*"
+366
+367        contents = {}
+368        entries = self.smbClient.listPath(
+369            shareName=self.smb_share, 
+370            path=path
+371        )
+372        for entry in entries:
+373            contents[entry.get_longname()] = entry
+374
+375        return contents
+376
+377    def list_shares(self):
+378        """
+379        Lists all the shares available on the connected SMB server.
+380
+381        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
+382        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
+383        such as its name, type, raw type, and any comments associated with the share.
+384
+385        Returns:
+386            dict: A dictionary containing information about each share available on the server.
+387        """
+388
+389        self.available_shares = {}
+390
+391        if self.connected:
+392            if self.smbClient is not None:
+393                resp = self.smbClient.listShares()
+394
+395                for share in resp:
+396                    # SHARE_INFO_1 structure (lmshare.h)
+397                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
+398                    sharename = share["shi1_netname"][:-1]
+399                    sharecomment = share["shi1_remark"][:-1]
+400                    sharetype = share["shi1_type"]
+401
+402                    self.available_shares[sharename.lower()] = {
+403                        "name": sharename, 
+404                        "type": STYPE_MASK(sharetype), 
+405                        "rawtype": sharetype, 
+406                        "comment": sharecomment
+407                    }
+408            else:
+409                print("[!] Error: SMBSession.smbClient is None.")
+410
+411        return self.available_shares
+412
+413    def mkdir(self, path=None):
+414        """
+415        Creates a directory at the specified path on the SMB share.
+416
+417        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
+418        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
+419        the creation for that directory without raising an error.
+420
+421        Args:
+422            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
+423
+424        Note:
+425            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
+426        """
+427
+428        if path is not None:
+429            # Prepare path
+430            path = path.replace('/',ntpath.sep)
+431            if ntpath.sep in path:
+432                path = path.strip(ntpath.sep).split(ntpath.sep)
+433            else:
+434                path = [path]
+435
+436            # Create each dir in the path
+437            for depth in range(1, len(path)+1):
+438                tmp_path = ntpath.sep.join(path[:depth])
+439                try:
+440                    self.smbClient.createDirectory(
+441                        shareName=self.smb_share, 
+442                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
+443                    )
+444                except impacket.smbconnection.SessionError as err:
+445                    if err.getErrorCode() == 0xc0000035:
+446                        # STATUS_OBJECT_NAME_COLLISION
+447                        # Remote directory already created, this is normal
+448                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
+449                        pass
+450                    else:
+451                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
+452                        if self.config.debug:
+453                            traceback.print_exc()
+454        else:
+455            pass
+456
+457    def path_exists(self, path=None):
+458        """
+459        Checks if the specified path exists on the SMB share.
+460
+461        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
+462        If the path listing is successful and returns one or more entries, the path is considered to exist.
+463
+464        Args:
+465            path (str, optional): The path to check on the SMB share. Defaults to None.
+466
+467        Returns:
+468            bool: True if the path exists, False otherwise or if an error occurs.
+469        """
+470
+471        if path is not None:
+472            path = path.replace('*','')
+473            try:
+474                contents = self.smbClient.listPath(
+475                    shareName=self.smb_share,
+476                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+477                )
+478                return (len(contents) != 0)
+479            except Exception as e:
+480                return False
+481        else:
+482            return False
+483   
+484    def path_isdir(self, pathFromRoot=None):
+485        """
+486        Checks if the specified path is a directory on the SMB share.
+487
+488        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
+489        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
+490
+491        Args:
+492            path (str, optional): The path to check on the SMB share. Defaults to None.
+493
+494        Returns:
+495            bool: True if the path is a directory, False otherwise or if an error occurs.
+496        """
+497
+498        if pathFromRoot is not None:
+499            # Replace slashes if any
+500            path = pathFromRoot.replace('/', ntpath.sep)
+501            
+502            # Strip wildcards to avoid injections
+503            path = path.replace('*','')
+504
+505            # Normalize path and strip leading backslash
+506            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
+507
+508            if path.strip() in ['', '.', '..']:
+509                # By defininition they exist on the filesystem
+510                return True
+511            else:
+512                try:
+513                    contents = self.smbClient.listPath(
+514                        shareName=self.smb_share,
+515                        path=path+'*'
+516                    )
+517                    # Filter on directories
+518                    contents = [
+519                        c for c in contents
+520                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
+521                    ]
+522                    return (len(contents) != 0)
+523                except Exception as e:
+524                    return False
+525        else:
+526            return False
+527
+528    def path_isfile(self, path=None):
+529        """
+530        Checks if the specified path is a file on the SMB share.
+531
+532        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
+533        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
+534
+535        Args:
+536            path (str, optional): The path to check on the SMB share. Defaults to None.
+537
+538        Returns:
+539            bool: True if the path is a file, False otherwise or if an error occurs.
+540        """
+541
+542        if path is not None:
+543            path = path.replace('*','')
+544            try:
+545                contents = self.smbClient.listPath(
+546                    shareName=self.smb_share,
+547                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+548                )
+549                # Filter on files
+550                contents = [
+551                    c for c in contents
+552                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
+553                ]
+554                return (len(contents) != 0)
+555            except Exception as e:
+556                return False
+557        else:
+558            return False
+559
+560    def ping_smb_session(self):
+561        """
+562        Tests the connectivity to the SMB server by sending an echo command.
+563
+564        This method attempts to send an echo command to the SMB server to check if the session is still active.
+565        It updates the `connected` attribute of the class based on the success or failure of the echo command.
+566
+567        Returns:
+568            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
+569        """
+570
+571        try:
+572            self.smbClient.getSMBServer().echo()
+573            self.connected = True
+574        except Exception as e:
+575            self.connected = False
+576        return self.connected
+577
+578    def put_file(self, localpath=None):
+579        """
+580        Uploads a single file to the SMB share.
+581
+582        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
+583        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
+584        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
+585
+586        Args:
+587            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
+588        """
+589
+590        if os.path.exists(localpath):
+591            if os.path.isfile(localpath):
+592                try:
+593                    localfile = os.path.basename(localpath)
+594                    f = LocalFileIO(
+595                        mode="rb", 
+596                        path=localpath, 
+597                        debug=self.config.debug
+598                    )
+599                    self.smbClient.putFile(
+600                        shareName=self.smb_share, 
+601                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
+602                        callback=f.read
+603                    )
+604                    f.close()
+605                except (BrokenPipeError, KeyboardInterrupt) as err:
+606                    print("[!] Interrupted.")
+607                    self.close_smb_session()
+608                    self.init_smb_session()
+609                except Exception as err:
+610                    print("[!] Failed to upload '%s': %s" % (localfile, err))
+611                    if self.config.debug:
+612                        traceback.print_exc()
+613            else:
+614                print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.")
+615        else:
+616            print("[!] The specified localpath does not exist.")
+617
+618    def put_file_recursively(self, localpath=None):
+619        """
+620        Recursively uploads files from a specified local directory to the SMB share.
+621
+622        This method walks through the given local directory and all its subdirectories, uploading each file to the
+623        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
+624        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
+625        and uploading files. If the local path is not a directory, it prints an error message.
+626
+627        Args:
+628            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
+629        """
+630
+631        if os.path.exists(localpath):
+632            if os.path.isfile(localpath):
+633                # Iterate over all files and directories within the local path
+634                local_files = {}
+635                for root, dirs, files in os.walk(localpath):
+636                    if len(files) != 0:
+637                        local_files[root] = files
+638
+639                # Iterate over the found files
+640                for local_dir_path in sorted(local_files.keys()):
+641                    print("[>] Putting files of '%s'" % local_dir_path)
+642
+643                    # Create remote directory
+644                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
+645                    self.mkdir(
+646                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
+647                    )
+648
+649                    for local_file_path in local_files[local_dir_path]:
+650                        try:
+651                            f = LocalFileIO(
+652                                mode="rb", 
+653                                path=local_dir_path + os.path.sep + local_file_path, 
+654                                debug=self.config.debug
+655                            )
+656                            self.smbClient.putFile(
+657                                shareName=self.smb_share, 
+658                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
+659                                callback=f.read
+660                            )
+661                            f.close()
+662
+663                        except BrokenPipeError as err:
+664                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+665                            f.close(remove=True)
+666                            break
+667                        except Exception as err:
+668                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+669                            f.close(remove=True)
+670                else:
+671                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
+672        else:
+673            print("[!] The specified localpath does not exist.")
+674
+675    def rmdir(self, path=None):
+676        """
+677        Removes a directory from the SMB share at the specified path.
+678
+679        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
+680        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+681        the stack trace of the exception.
+682
+683        Args:
+684            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
+685        """
+686        try:
+687            self.smbClient.deleteDirectory(
+688                shareName=self.smb_share, 
+689                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+690            )
+691        except Exception as err:
+692            print("[!] Failed to remove directory '%s': %s" % (path, err))
+693            if self.config.debug:
+694                traceback.print_exc()
+695
+696    def rm(self, path=None):
+697        """
+698        Removes a file from the SMB share at the specified path.
+699
+700        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
+701        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+702        the stack trace of the exception.
+703
+704        Args:
+705            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
+706        """
+707        try:
+708            self.smbClient.deleteFile(
+709                shareName=self.smb_share, 
+710                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+711            )
+712        except Exception as err:
+713            print("[!] Failed to remove file '%s': %s" % (path, err))
+714            if self.config.debug:
+715                traceback.print_exc()
+716
+717    def tree(self, path=None):
+718        """
+719        Recursively lists the directory structure of the SMB share starting from the specified path.
+720
+721        This function prints a visual representation of the directory tree of the remote SMB share. It uses
+722        recursion to navigate through directories and lists all files and subdirectories in each directory.
+723        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
+724
+725        Args:
+726            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
+727                                  Defaults to the root of the current share.
+728        """
+729        
+730        def recurse_action(base_dir="", path=[], prompt=[]):
+731            bars = ["│   ", "├── ", "└── "]
+732
+733            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
+734
+735            entries = []
+736            try:
+737                entries = self.smbClient.listPath(
+738                    shareName=self.smb_share, 
+739                    path=remote_smb_path+'\\*'
+740                )
+741            except impacket.smbconnection.SessionError as err:
+742                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
+743                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
+744                if self.config.no_colors:
+745                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
+746                else:
+747                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
+748                return 
+749
+750            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
+751            entries = sorted(entries, key=lambda x:x.get_longname())
+752
+753            # 
+754            if len(entries) > 1:
+755                index = 0
+756                for entry in entries:
+757                    index += 1
+758                    # This is the first entry 
+759                    if index == 0:
+760                        if entry.is_directory():
+761                            if self.config.no_colors:
+762                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+763                            else:
+764                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+765                            recurse_action(
+766                                base_dir=base_dir, 
+767                                path=path+[entry.get_longname()],
+768                                prompt=prompt+["│   "]
+769                            )
+770                        else:
+771                            if self.config.no_colors:
+772                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+773                            else:
+774                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+775
+776                    # This is the last entry
+777                    elif index == len(entries):
+778                        if entry.is_directory():
+779                            if self.config.no_colors:
+780                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+781                            else:
+782                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+783                            recurse_action(
+784                                base_dir=base_dir, 
+785                                path=path+[entry.get_longname()],
+786                                prompt=prompt+["    "]
+787                            )
+788                        else:
+789                            if self.config.no_colors:
+790                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+791                            else:
+792                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+793                        
+794                    # These are entries in the middle
+795                    else:
+796                        if entry.is_directory():
+797                            if self.config.no_colors:
+798                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+799                            else:
+800                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+801                            recurse_action(
+802                                base_dir=base_dir, 
+803                                path=path+[entry.get_longname()],
+804                                prompt=prompt+["│   "]
+805                            )
+806                        else:
+807                            if self.config.no_colors:
+808                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+809                            else:
+810                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+811
+812            # 
+813            elif len(entries) == 1:
+814                entry = entries[0]
+815                if entry.is_directory():
+816                    if self.config.no_colors:
+817                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+818                    else:
+819                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+820                    recurse_action(
+821                        base_dir=base_dir, 
+822                        path=path+[entry.get_longname()],
+823                        prompt=prompt+["    "]
+824                    )
+825                else:
+826                    if self.config.no_colors:
+827                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+828                    else:
+829                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+830
+831        # Entrypoint
+832        try:
+833            if self.config.no_colors:
+834                print("%s\\" % path)
+835            else:
+836                print("\x1b[1;96m%s\x1b[0m\\" % path)
+837            recurse_action(
+838                base_dir=self.smb_cwd, 
+839                path=[path],
+840                prompt=[""]
+841            )
+842        except (BrokenPipeError, KeyboardInterrupt) as e:
+843            print("[!] Interrupted.")
+844            self.close_smb_session()
+845            self.init_smb_session()
+846
+847    # Setter / Getter
+848
+849    def set_share(self, shareName):
+850        """
+851        Sets the current SMB share to the specified share name.
+852
+853        This method updates the SMB session to use the specified share name. It checks if the share name is valid
+854        and updates the smb_share attribute of the SMBSession instance.
+855
+856        Parameters:
+857            shareName (str): The name of the share to set as the current SMB share.
+858
+859        Raises:
+860            ValueError: If the shareName is None or an empty string.
+861        """
+862
+863        if shareName is not None:
+864            self.smb_share = shareName
+865
+866    def set_cwd(self, path=None):
+867        """
+868        Sets the current working directory on the SMB share to the specified path.
+869
+870        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
+871        If the specified path is not a directory, the cwd remains unchanged.
+872
+873        Parameters:
+874            path (str): The path to set as the current working directory.
+875
+876        Raises:
+877            ValueError: If the specified path is not a directory.
+878        """
+879
+880        if path is not None:
+881            # Set path separators to ntpath sep 
+882            if '/' in path:
+883                path = path.replace('/', ntpath.sep)
+884
+885            if path.startswith(ntpath.sep):
+886                # Absolute path
+887                path = path + ntpath.sep
+888            else:
+889                # Relative path to the CWD
+890                if len(self.smb_cwd) == 0:
+891                    path = path + ntpath.sep
+892                else:
+893                    path = self.smb_cwd + ntpath.sep + path
+894            
+895            # Path normalization
+896            path = ntpath.normpath(path)
+897            path = re.sub(r'\\+', r'\\', path)
+898
+899            if path in ["", ".", ".."]:
+900                self.smb_cwd = ""
+901            else:
+902                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
+903                    # Path exists on the remote 
+904                    self.smb_cwd = ntpath.normpath(path)
+905                else:
+906                    # Path does not exists or is not a directory on the remote 
+907                    print("[!] Remote directory '%s' does not exist." % path)
+
+ + +
+
+ +
+ + class + SMBSession: + + + +
+ +
 18class SMBSession(object):
+ 19    """
+ 20    Class SMBSession is designed to handle the session management for SMB (Server Message Block) protocol connections.
+ 21    It provides functionalities to connect to an SMB server, authenticate using either NTLM or Kerberos, and manage SMB shares.
+ 22
+ 23    Attributes:
+ 24        address (str): The IP address or hostname of the SMB server.
+ 25        domain (str): The domain name for SMB server authentication.
+ 26        username (str): The username for SMB server authentication.
+ 27        password (str): The password for SMB server authentication.
+ 28        lmhash (str): The LM hash of the user's password, if available.
+ 29        nthash (str): The NT hash of the user's password, if available.
+ 30        use_kerberos (bool): A flag to determine whether to use Kerberos for authentication.
+ 31        kdcHost (str): The Key Distribution Center (KDC) host for Kerberos authentication.
+ 32        debug (bool): A flag to enable debug output.
+ 33        smbClient (object): The SMB client object used for the connection.
+ 34        connected (bool): A flag to check the status of the connection.
+ 35        smb_share (str): The current SMB share in use.
+ 36        smb_path (str): The current path within the SMB share.
+ 37
+ 38    Methods:
+ 39        __init__(address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, debug=False):
+ 40            Initializes the SMBSession with the specified parameters.
+ 41        init_smb_session():
+ 42            Initializes the SMB session by connecting to the server and authenticating using the specified method.
+ 43    """
+ 44
+ 45    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
+ 46        super(SMBSession, self).__init__()
+ 47        # Objects
+ 48        self.config = config
+ 49
+ 50        # Target server
+ 51        self.address = address
+ 52
+ 53        # Credentials
+ 54        self.domain = domain
+ 55        self.username = username
+ 56        self.password = password 
+ 57        self.lmhash = lmhash
+ 58        self.nthash = nthash
+ 59        self.use_kerberos = use_kerberos
+ 60        self.kdcHost = kdcHost
+ 61
+ 62        self.smbClient = None
+ 63        self.connected = False
+ 64
+ 65        self.available_shares = {}
+ 66        self.smb_share = None
+ 67        self.smb_cwd = ""
+ 68
+ 69        self.list_shares()
+ 70
+ 71    # Connect and disconnect SMB session
+ 72
+ 73    def init_smb_session(self):
+ 74        """
+ 75        Initializes and establishes a session with the SMB server.
+ 76
+ 77        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
+ 78        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
+ 79
+ 80        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.
+ 81
+ 82        Returns:
+ 83            bool: True if the connection and authentication are successful, False otherwise.
+ 84        """
+ 85
+ 86        if self.config.debug:
+ 87            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
+ 88        try:
+ 89            self.smbClient = impacket.smbconnection.SMBConnection(
+ 90                remoteName=self.address,
+ 91                remoteHost=self.address,
+ 92                sess_port=int(445)
+ 93            )
+ 94        except OSError as err:
+ 95            print("[!] %s" % err)
+ 96            self.smbClient = None
+ 97
+ 98        self.connected = False
+ 99        if self.smbClient is not None:
+100            if self.use_kerberos:
+101                if self.config.debug:
+102                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
+103                self.connected = self.smbClient.kerberosLogin(
+104                    user=self.username,
+105                    password=self.password,
+106                    domain=self.domain,
+107                    lmhash=self.lmhash,
+108                    nthash=self.nthash,
+109                    aesKey=self.aesKey,
+110                    kdcHost=self.kdcHost
+111                )
+112
+113            else:
+114                if self.config.debug:
+115                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
+116                self.connected = self.smbClient.login(
+117                    user=self.username,
+118                    password=self.password,
+119                    domain=self.domain,
+120                    lmhash=self.lmhash,
+121                    nthash=self.nthash
+122                )
+123
+124            if self.connected:
+125                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+126            else:
+127                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+128
+129        return self.connected
+130
+131    def close_smb_session(self):
+132        """
+133        Closes the current SMB session by disconnecting the SMB client.
+134
+135        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
+136        and if so, it closes the connection and resets the connection status.
+137
+138        Raises:
+139            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
+140        """
+141
+142        if self.smbClient is not None:
+143            if self.connected:
+144                self.smbClient.close()
+145                self.connected = False
+146                print("[+] SMB connection closed successfully.")
+147            else:
+148                print("[!] No active SMB connection to close.")
+149        else:
+150            raise Exception("SMB client is not initialized.")
+151
+152    # Operations
+153
+154    def get_file(self, path=None, keepRemotePath=False):
+155        """
+156        Retrieves a file from the specified path on the SMB share.
+157
+158        This method attempts to retrieve a file from the given path within the currently connected SMB share.
+159        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
+160        file object and writing the contents of the remote file to it using the SMB client's getFile method.
+161
+162        Parameters:
+163            path (str): The path of the file to retrieve. If None, uses the current smb_path.
+164
+165        Returns:
+166            None
+167        """
+168
+169        tmp_file_path = self.smb_cwd + ntpath.sep + path
+170        matches = self.smbClient.listPath(
+171            shareName=self.smb_share, 
+172            path=tmp_file_path
+173        )
+174        
+175        for entry in matches:
+176            if entry.is_directory():
+177                print("[>] Skipping '%s' because it is a directory." % tmp_file_path)
+178            else:
+179                try:
+180                    if ntpath.sep in path:
+181                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
+182                    else:
+183                        outputfile = entry.get_longname()
+184                    f = LocalFileIO(
+185                        mode="wb", 
+186                        path=outputfile,
+187                        expected_size=entry.get_filesize(), 
+188                        debug=self.config.debug,
+189                        keepRemotePath=keepRemotePath
+190                    )
+191                    self.smbClient.getFile(
+192                        shareName=self.smb_share, 
+193                        pathName=tmp_file_path, 
+194                        callback=f.write
+195                    )
+196                    f.close()
+197                except (BrokenPipeError, KeyboardInterrupt) as e:
+198                    f.close()
+199                    print("\x1b[v\x1b[o\r[!] Interrupted.")
+200                    self.close_smb_session()
+201                    self.init_smb_session()
+202                        
+203        return None
+204
+205    def get_file_recursively(self, path=None):
+206        """
+207        Recursively retrieves files from a specified path on the SMB share.
+208
+209        This method navigates through all directories starting from the given path,
+210        and downloads all files found. It handles directories recursively, ensuring
+211        that all nested files are retrieved. The method skips over directory entries
+212        and handles errors gracefully, attempting to continue the operation where possible.
+213
+214        Parameters:
+215            path (str): The initial directory path from which to start the recursive file retrieval.
+216                        If None, it starts from the root of the configured SMB share.
+217        """
+218        
+219        def recurse_action(base_dir="", path=[]):
+220            remote_smb_path = base_dir + ntpath.sep.join(path)
+221            entries = self.smbClient.listPath(
+222                shareName=self.smb_share, 
+223                path=remote_smb_path + '\\*'
+224            )
+225            if len(entries) != 0:
+226                files = [entry for entry in entries if not entry.is_directory()]
+227                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
+228
+229                # Files
+230                if len(files) != 0:
+231                    print("[>] Retrieving files of '%s'" % remote_smb_path)
+232                for entry_file in files:
+233                    if not entry_file.is_directory():
+234                        f = LocalFileIO(
+235                            mode="wb",
+236                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+237                            expected_size=entry_file.get_filesize(),
+238                            debug=self.config.debug
+239                        )
+240                        try:
+241                            self.smbClient.getFile(
+242                                shareName=self.smb_share, 
+243                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+244                                callback=f.write
+245                            )
+246                            f.close()
+247                        except BrokenPipeError as err:
+248                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+249                            f.close(remove=True)
+250                            break
+251                        except Exception as err:
+252                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+253                            f.close(remove=True)
+254                
+255                # Directories
+256                for entry_directory in directories:
+257                    if entry_directory.is_directory():
+258                        recurse_action(
+259                            base_dir=self.smb_cwd, 
+260                            path=path+[entry_directory.get_longname()]
+261                        )                   
+262        # Entrypoint
+263        try:
+264            recurse_action(
+265                base_dir=self.smb_cwd, 
+266                path=[path]
+267            )
+268        except (BrokenPipeError, KeyboardInterrupt) as e:
+269            print("\x1b[v\x1b[o\r[!] Interrupted.")
+270            self.close_smb_session()
+271            self.init_smb_session()
+272
+273    def info(self, share=True, server=True):
+274        """
+275        Displays information about the server and optionally the shares.
+276
+277        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.
+278
+279        Parameters:
+280            share (bool): If True, display information about the current share.
+281            server (bool): If True, display information about the server.
+282
+283        Returns:
+284            None
+285        """
+286
+287        if server:
+288            if self.config.no_colors:
+289                print("[+] Server:")
+290                print("  ├─NetBIOS:")
+291                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
+292                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
+293                print("  ├─DNS:")
+294                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
+295                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
+296                print("  ├─OS:")
+297                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
+298                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
+299                print("  ├─Server:")
+300                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
+301                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
+302                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
+303                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+304                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
+305                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+306                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+307                print("  └─")
+308            else:
+309                print("[+] Server:")
+310                print("  ├─NetBIOS:")
+311                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
+312                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
+313                print("  ├─DNS:")
+314                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
+315                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
+316                print("  ├─OS:")
+317                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
+318                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()))
+319                print("  ├─Server:")
+320                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
+321                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
+322                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
+323                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+324                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
+325                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+326                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+327                print("  └─")
+328
+329        if share and self.smb_share is not None:
+330            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
+331            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
+332            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
+333            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
+334            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
+335            if self.config.no_colors:
+336                print("\n[+] Share:")
+337                print("  ├─ Name ──────────── : %s" % (share_name))
+338                print("  ├─ Description ───── : %s" % (share_comment))
+339                print("  ├─ Type ──────────── : %s" % (share_type))
+340                print("  └─ Raw type value ── : %s" % (share_rawtype))
+341            else:
+342                print("\n[+] Share:")
+343                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
+344                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
+345                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
+346                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
+347
+348    def list_contents(self, path=None):
+349        """
+350        Lists the contents of a specified directory on the SMB share.
+351
+352        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
+353        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
+354        the long names of the files and directories as keys and their respective SMB entry objects as values.
+355
+356        Args:
+357            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
+358            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
+359
+360        Returns:
+361            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
+362        """
+363        
+364        if path is None or len(path) == 0:
+365            path = self.smb_cwd
+366        path = path.rstrip(ntpath.sep) + ntpath.sep + "*"
+367
+368        contents = {}
+369        entries = self.smbClient.listPath(
+370            shareName=self.smb_share, 
+371            path=path
+372        )
+373        for entry in entries:
+374            contents[entry.get_longname()] = entry
+375
+376        return contents
+377
+378    def list_shares(self):
+379        """
+380        Lists all the shares available on the connected SMB server.
+381
+382        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
+383        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
+384        such as its name, type, raw type, and any comments associated with the share.
+385
+386        Returns:
+387            dict: A dictionary containing information about each share available on the server.
+388        """
+389
+390        self.available_shares = {}
+391
+392        if self.connected:
+393            if self.smbClient is not None:
+394                resp = self.smbClient.listShares()
+395
+396                for share in resp:
+397                    # SHARE_INFO_1 structure (lmshare.h)
+398                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
+399                    sharename = share["shi1_netname"][:-1]
+400                    sharecomment = share["shi1_remark"][:-1]
+401                    sharetype = share["shi1_type"]
+402
+403                    self.available_shares[sharename.lower()] = {
+404                        "name": sharename, 
+405                        "type": STYPE_MASK(sharetype), 
+406                        "rawtype": sharetype, 
+407                        "comment": sharecomment
+408                    }
+409            else:
+410                print("[!] Error: SMBSession.smbClient is None.")
+411
+412        return self.available_shares
+413
+414    def mkdir(self, path=None):
+415        """
+416        Creates a directory at the specified path on the SMB share.
+417
+418        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
+419        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
+420        the creation for that directory without raising an error.
+421
+422        Args:
+423            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
+424
+425        Note:
+426            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
+427        """
+428
+429        if path is not None:
+430            # Prepare path
+431            path = path.replace('/',ntpath.sep)
+432            if ntpath.sep in path:
+433                path = path.strip(ntpath.sep).split(ntpath.sep)
+434            else:
+435                path = [path]
+436
+437            # Create each dir in the path
+438            for depth in range(1, len(path)+1):
+439                tmp_path = ntpath.sep.join(path[:depth])
+440                try:
+441                    self.smbClient.createDirectory(
+442                        shareName=self.smb_share, 
+443                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
+444                    )
+445                except impacket.smbconnection.SessionError as err:
+446                    if err.getErrorCode() == 0xc0000035:
+447                        # STATUS_OBJECT_NAME_COLLISION
+448                        # Remote directory already created, this is normal
+449                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
+450                        pass
+451                    else:
+452                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
+453                        if self.config.debug:
+454                            traceback.print_exc()
+455        else:
+456            pass
+457
+458    def path_exists(self, path=None):
+459        """
+460        Checks if the specified path exists on the SMB share.
+461
+462        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
+463        If the path listing is successful and returns one or more entries, the path is considered to exist.
+464
+465        Args:
+466            path (str, optional): The path to check on the SMB share. Defaults to None.
+467
+468        Returns:
+469            bool: True if the path exists, False otherwise or if an error occurs.
+470        """
+471
+472        if path is not None:
+473            path = path.replace('*','')
+474            try:
+475                contents = self.smbClient.listPath(
+476                    shareName=self.smb_share,
+477                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+478                )
+479                return (len(contents) != 0)
+480            except Exception as e:
+481                return False
+482        else:
+483            return False
+484   
+485    def path_isdir(self, pathFromRoot=None):
+486        """
+487        Checks if the specified path is a directory on the SMB share.
+488
+489        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
+490        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
+491
+492        Args:
+493            path (str, optional): The path to check on the SMB share. Defaults to None.
+494
+495        Returns:
+496            bool: True if the path is a directory, False otherwise or if an error occurs.
+497        """
+498
+499        if pathFromRoot is not None:
+500            # Replace slashes if any
+501            path = pathFromRoot.replace('/', ntpath.sep)
+502            
+503            # Strip wildcards to avoid injections
+504            path = path.replace('*','')
+505
+506            # Normalize path and strip leading backslash
+507            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
+508
+509            if path.strip() in ['', '.', '..']:
+510                # By defininition they exist on the filesystem
+511                return True
+512            else:
+513                try:
+514                    contents = self.smbClient.listPath(
+515                        shareName=self.smb_share,
+516                        path=path+'*'
+517                    )
+518                    # Filter on directories
+519                    contents = [
+520                        c for c in contents
+521                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
+522                    ]
+523                    return (len(contents) != 0)
+524                except Exception as e:
+525                    return False
+526        else:
+527            return False
+528
+529    def path_isfile(self, path=None):
+530        """
+531        Checks if the specified path is a file on the SMB share.
+532
+533        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
+534        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
+535
+536        Args:
+537            path (str, optional): The path to check on the SMB share. Defaults to None.
+538
+539        Returns:
+540            bool: True if the path is a file, False otherwise or if an error occurs.
+541        """
+542
+543        if path is not None:
+544            path = path.replace('*','')
+545            try:
+546                contents = self.smbClient.listPath(
+547                    shareName=self.smb_share,
+548                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+549                )
+550                # Filter on files
+551                contents = [
+552                    c for c in contents
+553                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
+554                ]
+555                return (len(contents) != 0)
+556            except Exception as e:
+557                return False
+558        else:
+559            return False
+560
+561    def ping_smb_session(self):
+562        """
+563        Tests the connectivity to the SMB server by sending an echo command.
+564
+565        This method attempts to send an echo command to the SMB server to check if the session is still active.
+566        It updates the `connected` attribute of the class based on the success or failure of the echo command.
+567
+568        Returns:
+569            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
+570        """
+571
+572        try:
+573            self.smbClient.getSMBServer().echo()
+574            self.connected = True
+575        except Exception as e:
+576            self.connected = False
+577        return self.connected
+578
+579    def put_file(self, localpath=None):
+580        """
+581        Uploads a single file to the SMB share.
+582
+583        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
+584        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
+585        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
+586
+587        Args:
+588            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
+589        """
+590
+591        if os.path.exists(localpath):
+592            if os.path.isfile(localpath):
+593                try:
+594                    localfile = os.path.basename(localpath)
+595                    f = LocalFileIO(
+596                        mode="rb", 
+597                        path=localpath, 
+598                        debug=self.config.debug
+599                    )
+600                    self.smbClient.putFile(
+601                        shareName=self.smb_share, 
+602                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
+603                        callback=f.read
+604                    )
+605                    f.close()
+606                except (BrokenPipeError, KeyboardInterrupt) as err:
+607                    print("[!] Interrupted.")
+608                    self.close_smb_session()
+609                    self.init_smb_session()
+610                except Exception as err:
+611                    print("[!] Failed to upload '%s': %s" % (localfile, err))
+612                    if self.config.debug:
+613                        traceback.print_exc()
+614            else:
+615                print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.")
+616        else:
+617            print("[!] The specified localpath does not exist.")
+618
+619    def put_file_recursively(self, localpath=None):
+620        """
+621        Recursively uploads files from a specified local directory to the SMB share.
+622
+623        This method walks through the given local directory and all its subdirectories, uploading each file to the
+624        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
+625        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
+626        and uploading files. If the local path is not a directory, it prints an error message.
+627
+628        Args:
+629            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
+630        """
+631
+632        if os.path.exists(localpath):
+633            if os.path.isfile(localpath):
+634                # Iterate over all files and directories within the local path
+635                local_files = {}
+636                for root, dirs, files in os.walk(localpath):
+637                    if len(files) != 0:
+638                        local_files[root] = files
+639
+640                # Iterate over the found files
+641                for local_dir_path in sorted(local_files.keys()):
+642                    print("[>] Putting files of '%s'" % local_dir_path)
+643
+644                    # Create remote directory
+645                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
+646                    self.mkdir(
+647                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
+648                    )
+649
+650                    for local_file_path in local_files[local_dir_path]:
+651                        try:
+652                            f = LocalFileIO(
+653                                mode="rb", 
+654                                path=local_dir_path + os.path.sep + local_file_path, 
+655                                debug=self.config.debug
+656                            )
+657                            self.smbClient.putFile(
+658                                shareName=self.smb_share, 
+659                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
+660                                callback=f.read
+661                            )
+662                            f.close()
+663
+664                        except BrokenPipeError as err:
+665                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+666                            f.close(remove=True)
+667                            break
+668                        except Exception as err:
+669                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+670                            f.close(remove=True)
+671                else:
+672                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
+673        else:
+674            print("[!] The specified localpath does not exist.")
+675
+676    def rmdir(self, path=None):
+677        """
+678        Removes a directory from the SMB share at the specified path.
+679
+680        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
+681        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+682        the stack trace of the exception.
+683
+684        Args:
+685            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
+686        """
+687        try:
+688            self.smbClient.deleteDirectory(
+689                shareName=self.smb_share, 
+690                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+691            )
+692        except Exception as err:
+693            print("[!] Failed to remove directory '%s': %s" % (path, err))
+694            if self.config.debug:
+695                traceback.print_exc()
+696
+697    def rm(self, path=None):
+698        """
+699        Removes a file from the SMB share at the specified path.
+700
+701        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
+702        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+703        the stack trace of the exception.
+704
+705        Args:
+706            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
+707        """
+708        try:
+709            self.smbClient.deleteFile(
+710                shareName=self.smb_share, 
+711                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+712            )
+713        except Exception as err:
+714            print("[!] Failed to remove file '%s': %s" % (path, err))
+715            if self.config.debug:
+716                traceback.print_exc()
+717
+718    def tree(self, path=None):
+719        """
+720        Recursively lists the directory structure of the SMB share starting from the specified path.
+721
+722        This function prints a visual representation of the directory tree of the remote SMB share. It uses
+723        recursion to navigate through directories and lists all files and subdirectories in each directory.
+724        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
+725
+726        Args:
+727            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
+728                                  Defaults to the root of the current share.
+729        """
+730        
+731        def recurse_action(base_dir="", path=[], prompt=[]):
+732            bars = ["│   ", "├── ", "└── "]
+733
+734            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
+735
+736            entries = []
+737            try:
+738                entries = self.smbClient.listPath(
+739                    shareName=self.smb_share, 
+740                    path=remote_smb_path+'\\*'
+741                )
+742            except impacket.smbconnection.SessionError as err:
+743                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
+744                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
+745                if self.config.no_colors:
+746                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
+747                else:
+748                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
+749                return 
+750
+751            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
+752            entries = sorted(entries, key=lambda x:x.get_longname())
+753
+754            # 
+755            if len(entries) > 1:
+756                index = 0
+757                for entry in entries:
+758                    index += 1
+759                    # This is the first entry 
+760                    if index == 0:
+761                        if entry.is_directory():
+762                            if self.config.no_colors:
+763                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+764                            else:
+765                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+766                            recurse_action(
+767                                base_dir=base_dir, 
+768                                path=path+[entry.get_longname()],
+769                                prompt=prompt+["│   "]
+770                            )
+771                        else:
+772                            if self.config.no_colors:
+773                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+774                            else:
+775                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+776
+777                    # This is the last entry
+778                    elif index == len(entries):
+779                        if entry.is_directory():
+780                            if self.config.no_colors:
+781                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+782                            else:
+783                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+784                            recurse_action(
+785                                base_dir=base_dir, 
+786                                path=path+[entry.get_longname()],
+787                                prompt=prompt+["    "]
+788                            )
+789                        else:
+790                            if self.config.no_colors:
+791                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+792                            else:
+793                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+794                        
+795                    # These are entries in the middle
+796                    else:
+797                        if entry.is_directory():
+798                            if self.config.no_colors:
+799                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+800                            else:
+801                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+802                            recurse_action(
+803                                base_dir=base_dir, 
+804                                path=path+[entry.get_longname()],
+805                                prompt=prompt+["│   "]
+806                            )
+807                        else:
+808                            if self.config.no_colors:
+809                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+810                            else:
+811                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+812
+813            # 
+814            elif len(entries) == 1:
+815                entry = entries[0]
+816                if entry.is_directory():
+817                    if self.config.no_colors:
+818                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+819                    else:
+820                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+821                    recurse_action(
+822                        base_dir=base_dir, 
+823                        path=path+[entry.get_longname()],
+824                        prompt=prompt+["    "]
+825                    )
+826                else:
+827                    if self.config.no_colors:
+828                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+829                    else:
+830                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+831
+832        # Entrypoint
+833        try:
+834            if self.config.no_colors:
+835                print("%s\\" % path)
+836            else:
+837                print("\x1b[1;96m%s\x1b[0m\\" % path)
+838            recurse_action(
+839                base_dir=self.smb_cwd, 
+840                path=[path],
+841                prompt=[""]
+842            )
+843        except (BrokenPipeError, KeyboardInterrupt) as e:
+844            print("[!] Interrupted.")
+845            self.close_smb_session()
+846            self.init_smb_session()
+847
+848    # Setter / Getter
+849
+850    def set_share(self, shareName):
+851        """
+852        Sets the current SMB share to the specified share name.
+853
+854        This method updates the SMB session to use the specified share name. It checks if the share name is valid
+855        and updates the smb_share attribute of the SMBSession instance.
+856
+857        Parameters:
+858            shareName (str): The name of the share to set as the current SMB share.
+859
+860        Raises:
+861            ValueError: If the shareName is None or an empty string.
+862        """
+863
+864        if shareName is not None:
+865            self.smb_share = shareName
+866
+867    def set_cwd(self, path=None):
+868        """
+869        Sets the current working directory on the SMB share to the specified path.
+870
+871        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
+872        If the specified path is not a directory, the cwd remains unchanged.
+873
+874        Parameters:
+875            path (str): The path to set as the current working directory.
+876
+877        Raises:
+878            ValueError: If the specified path is not a directory.
+879        """
+880
+881        if path is not None:
+882            # Set path separators to ntpath sep 
+883            if '/' in path:
+884                path = path.replace('/', ntpath.sep)
+885
+886            if path.startswith(ntpath.sep):
+887                # Absolute path
+888                path = path + ntpath.sep
+889            else:
+890                # Relative path to the CWD
+891                if len(self.smb_cwd) == 0:
+892                    path = path + ntpath.sep
+893                else:
+894                    path = self.smb_cwd + ntpath.sep + path
+895            
+896            # Path normalization
+897            path = ntpath.normpath(path)
+898            path = re.sub(r'\\+', r'\\', path)
+899
+900            if path in ["", ".", ".."]:
+901                self.smb_cwd = ""
+902            else:
+903                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
+904                    # Path exists on the remote 
+905                    self.smb_cwd = ntpath.normpath(path)
+906                else:
+907                    # Path does not exists or is not a directory on the remote 
+908                    print("[!] 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.

+ +

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. + smb_share (str): The current SMB share in use. + smb_path (str): The current path within the 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.

+
+ + +
+ +
+ + SMBSession( address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None) + + + +
+ +
45    def __init__(self, address, domain, username, password, lmhash, nthash, use_kerberos=False, kdcHost=None, config=None):
+46        super(SMBSession, self).__init__()
+47        # Objects
+48        self.config = config
+49
+50        # Target server
+51        self.address = address
+52
+53        # Credentials
+54        self.domain = domain
+55        self.username = username
+56        self.password = password 
+57        self.lmhash = lmhash
+58        self.nthash = nthash
+59        self.use_kerberos = use_kerberos
+60        self.kdcHost = kdcHost
+61
+62        self.smbClient = None
+63        self.connected = False
+64
+65        self.available_shares = {}
+66        self.smb_share = None
+67        self.smb_cwd = ""
+68
+69        self.list_shares()
+
+ + + + +
+
+
+ config + + +
+ + + + +
+
+
+ address + + +
+ + + + +
+
+
+ domain + + +
+ + + + +
+
+
+ username + + +
+ + + + +
+
+
+ password + + +
+ + + + +
+
+
+ lmhash + + +
+ + + + +
+
+
+ nthash + + +
+ + + + +
+
+
+ use_kerberos + + +
+ + + + +
+
+
+ kdcHost + + +
+ + + + +
+
+
+ smbClient + + +
+ + + + +
+
+
+ connected + + +
+ + + + +
+
+
+ available_shares + + +
+ + + + +
+
+
+ smb_share + + +
+ + + + +
+
+
+ smb_cwd + + +
+ + + + +
+
+ +
+ + def + init_smb_session(self): + + + +
+ +
 73    def init_smb_session(self):
+ 74        """
+ 75        Initializes and establishes a session with the SMB server.
+ 76
+ 77        This method sets up the SMB connection using either Kerberos or NTLM authentication based on the configuration.
+ 78        It attempts to connect to the SMB server specified by the `address` attribute and authenticate using the credentials provided during the object's initialization.
+ 79
+ 80        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.
+ 81
+ 82        Returns:
+ 83            bool: True if the connection and authentication are successful, False otherwise.
+ 84        """
+ 85
+ 86        if self.config.debug:
+ 87            print("[debug] [>] Connecting to remote SMB server '%s' ... " % self.address)
+ 88        try:
+ 89            self.smbClient = impacket.smbconnection.SMBConnection(
+ 90                remoteName=self.address,
+ 91                remoteHost=self.address,
+ 92                sess_port=int(445)
+ 93            )
+ 94        except OSError as err:
+ 95            print("[!] %s" % err)
+ 96            self.smbClient = None
+ 97
+ 98        self.connected = False
+ 99        if self.smbClient is not None:
+100            if self.use_kerberos:
+101                if self.config.debug:
+102                    print("[debug] [>] Authenticating as '%s\\%s' with kerberos ... " % (self.domain, self.username))
+103                self.connected = self.smbClient.kerberosLogin(
+104                    user=self.username,
+105                    password=self.password,
+106                    domain=self.domain,
+107                    lmhash=self.lmhash,
+108                    nthash=self.nthash,
+109                    aesKey=self.aesKey,
+110                    kdcHost=self.kdcHost
+111                )
+112
+113            else:
+114                if self.config.debug:
+115                    print("[debug] [>] Authenticating as '%s\\%s' with NTLM ... " % (self.domain, self.username))
+116                self.connected = self.smbClient.login(
+117                    user=self.username,
+118                    password=self.password,
+119                    domain=self.domain,
+120                    lmhash=self.lmhash,
+121                    nthash=self.nthash
+122                )
+123
+124            if self.connected:
+125                print("[+] Successfully authenticated to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+126            else:
+127                print("[!] Failed to authenticate to '%s' as '%s\\%s'!" % (self.address, self.domain, self.username))
+128
+129        return self.connected
+
+ + +

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.

+ +

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 + close_smb_session(self): + + + +
+ +
131    def close_smb_session(self):
+132        """
+133        Closes the current SMB session by disconnecting the SMB client.
+134
+135        This method ensures that the SMB client connection is properly closed. It checks if the client is connected
+136        and if so, it closes the connection and resets the connection status.
+137
+138        Raises:
+139            Exception: If the SMB client is not initialized or if there's an error during the disconnection process.
+140        """
+141
+142        if self.smbClient is not None:
+143            if self.connected:
+144                self.smbClient.close()
+145                self.connected = False
+146                print("[+] SMB connection closed successfully.")
+147            else:
+148                print("[!] No active SMB connection to close.")
+149        else:
+150            raise Exception("SMB client is not initialized.")
+
+ + +

Closes the current SMB session by disconnecting the SMB client.

+ +

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.

+ +

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

+
+ + +
+
+ +
+ + def + get_file(self, path=None, keepRemotePath=False): + + + +
+ +
154    def get_file(self, path=None, keepRemotePath=False):
+155        """
+156        Retrieves a file from the specified path on the SMB share.
+157
+158        This method attempts to retrieve a file from the given path within the currently connected SMB share.
+159        If the path points to a directory, it skips the retrieval. It handles file retrieval by creating a local
+160        file object and writing the contents of the remote file to it using the SMB client's getFile method.
+161
+162        Parameters:
+163            path (str): The path of the file to retrieve. If None, uses the current smb_path.
+164
+165        Returns:
+166            None
+167        """
+168
+169        tmp_file_path = self.smb_cwd + ntpath.sep + path
+170        matches = self.smbClient.listPath(
+171            shareName=self.smb_share, 
+172            path=tmp_file_path
+173        )
+174        
+175        for entry in matches:
+176            if entry.is_directory():
+177                print("[>] Skipping '%s' because it is a directory." % tmp_file_path)
+178            else:
+179                try:
+180                    if ntpath.sep in path:
+181                        outputfile = ntpath.dirname(path) + ntpath.sep + entry.get_longname()
+182                    else:
+183                        outputfile = entry.get_longname()
+184                    f = LocalFileIO(
+185                        mode="wb", 
+186                        path=outputfile,
+187                        expected_size=entry.get_filesize(), 
+188                        debug=self.config.debug,
+189                        keepRemotePath=keepRemotePath
+190                    )
+191                    self.smbClient.getFile(
+192                        shareName=self.smb_share, 
+193                        pathName=tmp_file_path, 
+194                        callback=f.write
+195                    )
+196                    f.close()
+197                except (BrokenPipeError, KeyboardInterrupt) as e:
+198                    f.close()
+199                    print("\x1b[v\x1b[o\r[!] Interrupted.")
+200                    self.close_smb_session()
+201                    self.init_smb_session()
+202                        
+203        return None
+
+ + +

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

+ +

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

+ +

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

+ +

Returns: + None

+
+ + +
+
+ +
+ + def + get_file_recursively(self, path=None): + + + +
+ +
205    def get_file_recursively(self, path=None):
+206        """
+207        Recursively retrieves files from a specified path on the SMB share.
+208
+209        This method navigates through all directories starting from the given path,
+210        and downloads all files found. It handles directories recursively, ensuring
+211        that all nested files are retrieved. The method skips over directory entries
+212        and handles errors gracefully, attempting to continue the operation where possible.
+213
+214        Parameters:
+215            path (str): The initial directory path from which to start the recursive file retrieval.
+216                        If None, it starts from the root of the configured SMB share.
+217        """
+218        
+219        def recurse_action(base_dir="", path=[]):
+220            remote_smb_path = base_dir + ntpath.sep.join(path)
+221            entries = self.smbClient.listPath(
+222                shareName=self.smb_share, 
+223                path=remote_smb_path + '\\*'
+224            )
+225            if len(entries) != 0:
+226                files = [entry for entry in entries if not entry.is_directory()]
+227                directories = [entry for entry in entries if entry.is_directory() and entry.get_longname() not in [".", ".."]]
+228
+229                # Files
+230                if len(files) != 0:
+231                    print("[>] Retrieving files of '%s'" % remote_smb_path)
+232                for entry_file in files:
+233                    if not entry_file.is_directory():
+234                        f = LocalFileIO(
+235                            mode="wb",
+236                            path=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+237                            expected_size=entry_file.get_filesize(),
+238                            debug=self.config.debug
+239                        )
+240                        try:
+241                            self.smbClient.getFile(
+242                                shareName=self.smb_share, 
+243                                pathName=remote_smb_path + ntpath.sep + entry_file.get_longname(), 
+244                                callback=f.write
+245                            )
+246                            f.close()
+247                        except BrokenPipeError as err:
+248                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+249                            f.close(remove=True)
+250                            break
+251                        except Exception as err:
+252                            f.set_error(message="[bold red]Failed downloading '%s': %s" % (f.path, err))
+253                            f.close(remove=True)
+254                
+255                # Directories
+256                for entry_directory in directories:
+257                    if entry_directory.is_directory():
+258                        recurse_action(
+259                            base_dir=self.smb_cwd, 
+260                            path=path+[entry_directory.get_longname()]
+261                        )                   
+262        # Entrypoint
+263        try:
+264            recurse_action(
+265                base_dir=self.smb_cwd, 
+266                path=[path]
+267            )
+268        except (BrokenPipeError, KeyboardInterrupt) as e:
+269            print("\x1b[v\x1b[o\r[!] Interrupted.")
+270            self.close_smb_session()
+271            self.init_smb_session()
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + info(self, share=True, server=True): + + + +
+ +
273    def info(self, share=True, server=True):
+274        """
+275        Displays information about the server and optionally the shares.
+276
+277        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.
+278
+279        Parameters:
+280            share (bool): If True, display information about the current share.
+281            server (bool): If True, display information about the server.
+282
+283        Returns:
+284            None
+285        """
+286
+287        if server:
+288            if self.config.no_colors:
+289                print("[+] Server:")
+290                print("  ├─NetBIOS:")
+291                print("  │ ├─ NetBIOS Hostname ──────── : %s" % (self.smbClient.getServerName()))
+292                print("  │ └─ NetBIOS Domain ────────── : %s" % (self.smbClient.getServerDomain()))
+293                print("  ├─DNS:")
+294                print("  │ ├─ DNS Hostname ──────────── : %s" % (self.smbClient.getServerDNSHostName()))
+295                print("  │ └─ DNS Domain ────────────── : %s" % (self.smbClient.getServerDNSDomainName()))
+296                print("  ├─OS:")
+297                print("  │ ├─ OS Name ───────────────── : %s" % (self.smbClient.getServerOS()))
+298                print("  │ └─ OS Version ────────────── : %s.%s.%s" % (self.smbClient.getServerOSMajor(), self.smbClient.getServerOSMinor(), self.smbClient.getServerOSBuild()))
+299                print("  ├─Server:")
+300                print("  │ ├─ Signing Required ──────── : %s" % (self.smbClient.isSigningRequired()))
+301                print("  │ ├─ Login Required ────────── : %s" % (self.smbClient.isLoginRequired()))
+302                print("  │ ├─ Supports NTLMv2 ───────── : %s" % (self.smbClient.doesSupportNTLMv2()))
+303                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+304                print("  │ ├─ Max size of read chunk ── : %d bytes (%s)" % (MaxReadSize, b_filesize(MaxReadSize)))
+305                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+306                print("  │ └─ Max size of write chunk ─ : %d bytes (%s)" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+307                print("  └─")
+308            else:
+309                print("[+] Server:")
+310                print("  ├─NetBIOS:")
+311                print("  │ ├─ \x1b[94mNetBIOS Hostname\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerName()))
+312                print("  │ └─ \x1b[94mNetBIOS Domain\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDomain()))
+313                print("  ├─DNS:")
+314                print("  │ ├─ \x1b[94mDNS Hostname\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSHostName()))
+315                print("  │ └─ \x1b[94mDNS Domain\x1b[0m \x1b[90m──────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerDNSDomainName()))
+316                print("  ├─OS:")
+317                print("  │ ├─ \x1b[94mOS Name\x1b[0m \x1b[90m─────────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.getServerOS()))
+318                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()))
+319                print("  ├─Server:")
+320                print("  │ ├─ \x1b[94mSigning Required\x1b[0m \x1b[90m────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isSigningRequired()))
+321                print("  │ ├─ \x1b[94mLogin Required\x1b[0m \x1b[90m──────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.isLoginRequired()))
+322                print("  │ ├─ \x1b[94mSupports NTLMv2\x1b[0m \x1b[90m─────────\x1b[0m : \x1b[93m%s\x1b[0m" % (self.smbClient.doesSupportNTLMv2()))
+323                MaxReadSize = self.smbClient.getIOCapabilities()["MaxReadSize"]
+324                print("  │ ├─ \x1b[94mMax size of read chunk\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxReadSize, b_filesize(MaxReadSize)))
+325                MaxWriteSize = self.smbClient.getIOCapabilities()["MaxWriteSize"]
+326                print("  │ └─ \x1b[94mMax size of write chunk\x1b[0m \x1b[90m─\x1b[0m : \x1b[93m%d bytes (%s)\x1b[0m" % (MaxWriteSize, b_filesize(MaxWriteSize)))
+327                print("  └─")
+328
+329        if share and self.smb_share is not None:
+330            share_name = self.available_shares.get(self.smb_share.lower(), "")["name"]
+331            share_comment = self.available_shares.get(self.smb_share.lower(), "")["comment"]
+332            share_type = self.available_shares.get(self.smb_share.lower(), "")["type"]
+333            share_type =', '.join([s.replace("STYPE_","") for s in share_type])
+334            share_rawtype = self.available_shares.get(self.smb_share.lower(), "")["rawtype"]
+335            if self.config.no_colors:
+336                print("\n[+] Share:")
+337                print("  ├─ Name ──────────── : %s" % (share_name))
+338                print("  ├─ Description ───── : %s" % (share_comment))
+339                print("  ├─ Type ──────────── : %s" % (share_type))
+340                print("  └─ Raw type value ── : %s" % (share_rawtype))
+341            else:
+342                print("\n[+] Share:")
+343                print("  ├─ \x1b[94mName\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_name))
+344                print("  ├─ \x1b[94mDescription\x1b[0m \x1b[90m─────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_comment))
+345                print("  ├─ \x1b[94mType\x1b[0m \x1b[90m────────────\x1b[0m : \x1b[93m%s\x1b[0m" % (share_type))
+346                print("  └─ \x1b[94mRaw type value\x1b[0m \x1b[90m──\x1b[0m : \x1b[93m%s\x1b[0m" % (share_rawtype))
+
+ + +

Displays information about the server and optionally the shares.

+ +

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.

+ +

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

+ +

Returns: + None

+
+ + +
+
+ +
+ + def + list_contents(self, path=None): + + + +
+ +
348    def list_contents(self, path=None):
+349        """
+350        Lists the contents of a specified directory on the SMB share.
+351
+352        This method retrieves the contents of a directory specified by `shareName` and `path`. If `shareName` or `path`
+353        is not provided, it defaults to the instance's current SMB share or path. The method returns a dictionary with
+354        the long names of the files and directories as keys and their respective SMB entry objects as values.
+355
+356        Args:
+357            shareName (str, optional): The name of the SMB share. Defaults to the current SMB share if None.
+358            path (str, optional): The directory path to list contents from. Defaults to the current path if None.
+359
+360        Returns:
+361            dict: A dictionary with file and directory names as keys and their SMB entry objects as values.
+362        """
+363        
+364        if path is None or len(path) == 0:
+365            path = self.smb_cwd
+366        path = path.rstrip(ntpath.sep) + ntpath.sep + "*"
+367
+368        contents = {}
+369        entries = self.smbClient.listPath(
+370            shareName=self.smb_share, 
+371            path=path
+372        )
+373        for entry in entries:
+374            contents[entry.get_longname()] = entry
+375
+376        return contents
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + list_shares(self): + + + +
+ +
378    def list_shares(self):
+379        """
+380        Lists all the shares available on the connected SMB server.
+381
+382        This method queries the SMB server to retrieve a list of all available shares. It populates the `shares` dictionary
+383        with key-value pairs where the key is the share name and the value is a dictionary containing details about the share
+384        such as its name, type, raw type, and any comments associated with the share.
+385
+386        Returns:
+387            dict: A dictionary containing information about each share available on the server.
+388        """
+389
+390        self.available_shares = {}
+391
+392        if self.connected:
+393            if self.smbClient is not None:
+394                resp = self.smbClient.listShares()
+395
+396                for share in resp:
+397                    # SHARE_INFO_1 structure (lmshare.h)
+398                    # https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_1
+399                    sharename = share["shi1_netname"][:-1]
+400                    sharecomment = share["shi1_remark"][:-1]
+401                    sharetype = share["shi1_type"]
+402
+403                    self.available_shares[sharename.lower()] = {
+404                        "name": sharename, 
+405                        "type": STYPE_MASK(sharetype), 
+406                        "rawtype": sharetype, 
+407                        "comment": sharecomment
+408                    }
+409            else:
+410                print("[!] Error: SMBSession.smbClient is None.")
+411
+412        return self.available_shares
+
+ + +

Lists all the shares available on the connected SMB server.

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + mkdir(self, path=None): + + + +
+ +
414    def mkdir(self, path=None):
+415        """
+416        Creates a directory at the specified path on the SMB share.
+417
+418        This method takes a path and attempts to create the directory structure on the SMB share. If the path includes
+419        nested directories, it will create each directory in the sequence. If a directory already exists, it will skip
+420        the creation for that directory without raising an error.
+421
+422        Args:
+423            path (str, optional): The full path of the directory to create on the SMB share. Defaults to None.
+424
+425        Note:
+426            The path should use forward slashes ('/') which will be converted to backslashes (ntpath.sep) for SMB compatibility.
+427        """
+428
+429        if path is not None:
+430            # Prepare path
+431            path = path.replace('/',ntpath.sep)
+432            if ntpath.sep in path:
+433                path = path.strip(ntpath.sep).split(ntpath.sep)
+434            else:
+435                path = [path]
+436
+437            # Create each dir in the path
+438            for depth in range(1, len(path)+1):
+439                tmp_path = ntpath.sep.join(path[:depth])
+440                try:
+441                    self.smbClient.createDirectory(
+442                        shareName=self.smb_share, 
+443                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + tmp_path + ntpath.sep)
+444                    )
+445                except impacket.smbconnection.SessionError as err:
+446                    if err.getErrorCode() == 0xc0000035:
+447                        # STATUS_OBJECT_NAME_COLLISION
+448                        # Remote directory already created, this is normal
+449                        # Src: https://github.com/fortra/impacket/blob/269ce69872f0e8f2188a80addb0c39fedfa6dcb8/impacket/nt_errors.py#L268C9-L268C19
+450                        pass
+451                    else:
+452                        print("[!] Failed to create directory '%s': %s" % (tmp_path, err))
+453                        if self.config.debug:
+454                            traceback.print_exc()
+455        else:
+456            pass
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + path_exists(self, path=None): + + + +
+ +
458    def path_exists(self, path=None):
+459        """
+460        Checks if the specified path exists on the SMB share.
+461
+462        This method determines if a given path exists on the SMB share by attempting to list the contents of the path.
+463        If the path listing is successful and returns one or more entries, the path is considered to exist.
+464
+465        Args:
+466            path (str, optional): The path to check on the SMB share. Defaults to None.
+467
+468        Returns:
+469            bool: True if the path exists, False otherwise or if an error occurs.
+470        """
+471
+472        if path is not None:
+473            path = path.replace('*','')
+474            try:
+475                contents = self.smbClient.listPath(
+476                    shareName=self.smb_share,
+477                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+478                )
+479                return (len(contents) != 0)
+480            except Exception as e:
+481                return False
+482        else:
+483            return False
+
+ + +

Checks if the specified path exists on the SMB share.

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + path_isdir(self, pathFromRoot=None): + + + +
+ +
485    def path_isdir(self, pathFromRoot=None):
+486        """
+487        Checks if the specified path is a directory on the SMB share.
+488
+489        This method determines if a given path corresponds to a directory on the SMB share. It does this by listing the
+490        contents of the path and filtering for entries that match the basename of the path and are marked as directories.
+491
+492        Args:
+493            path (str, optional): The path to check on the SMB share. Defaults to None.
+494
+495        Returns:
+496            bool: True if the path is a directory, False otherwise or if an error occurs.
+497        """
+498
+499        if pathFromRoot is not None:
+500            # Replace slashes if any
+501            path = pathFromRoot.replace('/', ntpath.sep)
+502            
+503            # Strip wildcards to avoid injections
+504            path = path.replace('*','')
+505
+506            # Normalize path and strip leading backslash
+507            path = ntpath.normpath(path + ntpath.sep).lstrip(ntpath.sep)
+508
+509            if path.strip() in ['', '.', '..']:
+510                # By defininition they exist on the filesystem
+511                return True
+512            else:
+513                try:
+514                    contents = self.smbClient.listPath(
+515                        shareName=self.smb_share,
+516                        path=path+'*'
+517                    )
+518                    # Filter on directories
+519                    contents = [
+520                        c for c in contents
+521                        if c.get_longname() == ntpath.basename(path) and c.is_directory()
+522                    ]
+523                    return (len(contents) != 0)
+524                except Exception as e:
+525                    return False
+526        else:
+527            return False
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + path_isfile(self, path=None): + + + +
+ +
529    def path_isfile(self, path=None):
+530        """
+531        Checks if the specified path is a file on the SMB share.
+532
+533        This method determines if a given path corresponds to a file on the SMB share. It does this by listing the
+534        contents of the path and filtering for entries that match the basename of the path and are not marked as directories.
+535
+536        Args:
+537            path (str, optional): The path to check on the SMB share. Defaults to None.
+538
+539        Returns:
+540            bool: True if the path is a file, False otherwise or if an error occurs.
+541        """
+542
+543        if path is not None:
+544            path = path.replace('*','')
+545            try:
+546                contents = self.smbClient.listPath(
+547                    shareName=self.smb_share,
+548                    path=ntpath.normpath(self.smb_cwd + ntpath.sep + path + ntpath.sep)
+549                )
+550                # Filter on files
+551                contents = [
+552                    c for c in contents
+553                    if c.get_longname() == ntpath.basename(path) and not c.is_directory()
+554                ]
+555                return (len(contents) != 0)
+556            except Exception as e:
+557                return False
+558        else:
+559            return False
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + ping_smb_session(self): + + + +
+ +
561    def ping_smb_session(self):
+562        """
+563        Tests the connectivity to the SMB server by sending an echo command.
+564
+565        This method attempts to send an echo command to the SMB server to check if the session is still active.
+566        It updates the `connected` attribute of the class based on the success or failure of the echo command.
+567
+568        Returns:
+569            bool: True if the echo command succeeds (indicating the session is active), False otherwise.
+570        """
+571
+572        try:
+573            self.smbClient.getSMBServer().echo()
+574            self.connected = True
+575        except Exception as e:
+576            self.connected = False
+577        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.

+
+ + +
+
+ +
+ + def + put_file(self, localpath=None): + + + +
+ +
579    def put_file(self, localpath=None):
+580        """
+581        Uploads a single file to the SMB share.
+582
+583        This method takes a local file path, opens the file, and uploads it to the SMB share at the specified path.
+584        It handles exceptions such as broken pipe errors or keyboard interrupts by closing and reinitializing the SMB session.
+585        General exceptions are caught and logged, with a traceback provided if debugging is enabled.
+586
+587        Args:
+588            localpath (str, optional): The local file path of the file to be uploaded. Defaults to None.
+589        """
+590
+591        if os.path.exists(localpath):
+592            if os.path.isfile(localpath):
+593                try:
+594                    localfile = os.path.basename(localpath)
+595                    f = LocalFileIO(
+596                        mode="rb", 
+597                        path=localpath, 
+598                        debug=self.config.debug
+599                    )
+600                    self.smbClient.putFile(
+601                        shareName=self.smb_share, 
+602                        pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + localfile + ntpath.sep), 
+603                        callback=f.read
+604                    )
+605                    f.close()
+606                except (BrokenPipeError, KeyboardInterrupt) as err:
+607                    print("[!] Interrupted.")
+608                    self.close_smb_session()
+609                    self.init_smb_session()
+610                except Exception as err:
+611                    print("[!] Failed to upload '%s': %s" % (localfile, err))
+612                    if self.config.debug:
+613                        traceback.print_exc()
+614            else:
+615                print("[!] The specified localpath is a directory. Use 'put -r <directory>' instead.")
+616        else:
+617            print("[!] The specified localpath does not exist.")
+
+ + +

Uploads a single file to the SMB share.

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + put_file_recursively(self, localpath=None): + + + +
+ +
619    def put_file_recursively(self, localpath=None):
+620        """
+621        Recursively uploads files from a specified local directory to the SMB share.
+622
+623        This method walks through the given local directory and all its subdirectories, uploading each file to the
+624        corresponding directory structure on the SMB share. It first checks if the local path is a directory. If it is,
+625        it iterates over all files and directories within the local path, creating necessary directories on the SMB share
+626        and uploading files. If the local path is not a directory, it prints an error message.
+627
+628        Args:
+629            localpath (str, optional): The local directory path from which files will be uploaded. Defaults to None.
+630        """
+631
+632        if os.path.exists(localpath):
+633            if os.path.isfile(localpath):
+634                # Iterate over all files and directories within the local path
+635                local_files = {}
+636                for root, dirs, files in os.walk(localpath):
+637                    if len(files) != 0:
+638                        local_files[root] = files
+639
+640                # Iterate over the found files
+641                for local_dir_path in sorted(local_files.keys()):
+642                    print("[>] Putting files of '%s'" % local_dir_path)
+643
+644                    # Create remote directory
+645                    remote_dir_path = local_dir_path.replace(os.path.sep, ntpath.sep)
+646                    self.mkdir(
+647                        path=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep)
+648                    )
+649
+650                    for local_file_path in local_files[local_dir_path]:
+651                        try:
+652                            f = LocalFileIO(
+653                                mode="rb", 
+654                                path=local_dir_path + os.path.sep + local_file_path, 
+655                                debug=self.config.debug
+656                            )
+657                            self.smbClient.putFile(
+658                                shareName=self.smb_share, 
+659                                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + remote_dir_path + ntpath.sep + local_file_path), 
+660                                callback=f.read
+661                            )
+662                            f.close()
+663
+664                        except BrokenPipeError as err:
+665                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+666                            f.close(remove=True)
+667                            break
+668                        except Exception as err:
+669                            f.set_error(message="[bold red]Failed uploading '%s': %s" % (f.path, err))
+670                            f.close(remove=True)
+671                else:
+672                    print("[!] The specified localpath is a file. Use 'put <file>' instead.")
+673        else:
+674            print("[!] The specified localpath does not exist.")
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + rmdir(self, path=None): + + + +
+ +
676    def rmdir(self, path=None):
+677        """
+678        Removes a directory from the SMB share at the specified path.
+679
+680        This method attempts to delete a directory located at the given path on the SMB share. If the operation fails,
+681        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+682        the stack trace of the exception.
+683
+684        Args:
+685            path (str, optional): The path of the directory to be removed on the SMB share. Defaults to None.
+686        """
+687        try:
+688            self.smbClient.deleteDirectory(
+689                shareName=self.smb_share, 
+690                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+691            )
+692        except Exception as err:
+693            print("[!] Failed to remove directory '%s': %s" % (path, err))
+694            if self.config.debug:
+695                traceback.print_exc()
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + rm(self, path=None): + + + +
+ +
697    def rm(self, path=None):
+698        """
+699        Removes a file from the SMB share at the specified path.
+700
+701        This method attempts to delete a file located at the given path on the SMB share. If the operation fails,
+702        it prints an error message indicating the failure and the reason. If debugging is enabled, it also prints
+703        the stack trace of the exception.
+704
+705        Args:
+706            path (str, optional): The path of the file to be removed on the SMB share. Defaults to None.
+707        """
+708        try:
+709            self.smbClient.deleteFile(
+710                shareName=self.smb_share, 
+711                pathName=ntpath.normpath(self.smb_cwd + ntpath.sep + path), 
+712            )
+713        except Exception as err:
+714            print("[!] Failed to remove file '%s': %s" % (path, err))
+715            if self.config.debug:
+716                traceback.print_exc()
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + tree(self, path=None): + + + +
+ +
718    def tree(self, path=None):
+719        """
+720        Recursively lists the directory structure of the SMB share starting from the specified path.
+721
+722        This function prints a visual representation of the directory tree of the remote SMB share. It uses
+723        recursion to navigate through directories and lists all files and subdirectories in each directory.
+724        The output is color-coded and formatted to enhance readability, with directories highlighted in cyan.
+725
+726        Args:
+727            path (str, optional): The starting path on the SMB share from which to begin listing the tree.
+728                                  Defaults to the root of the current share.
+729        """
+730        
+731        def recurse_action(base_dir="", path=[], prompt=[]):
+732            bars = ["│   ", "├── ", "└── "]
+733
+734            remote_smb_path = ntpath.normpath(base_dir + ntpath.sep + ntpath.sep.join(path))
+735
+736            entries = []
+737            try:
+738                entries = self.smbClient.listPath(
+739                    shareName=self.smb_share, 
+740                    path=remote_smb_path+'\\*'
+741                )
+742            except impacket.smbconnection.SessionError as err:
+743                code, const, text = err.getErrorCode(), err.getErrorString()[0], err.getErrorString()[1]
+744                errmsg = "Error 0x%08x (%s): %s" % (code, const, text)
+745                if self.config.no_colors:
+746                    print("%s%s" % (''.join(prompt+[bars[2]]), errmsg))
+747                else:
+748                    print("%s\x1b[1;91m%s\x1b[0m" % (''.join(prompt+[bars[2]]), errmsg))
+749                return 
+750
+751            entries = [e for e in entries if e.get_longname() not in [".", ".."]]
+752            entries = sorted(entries, key=lambda x:x.get_longname())
+753
+754            # 
+755            if len(entries) > 1:
+756                index = 0
+757                for entry in entries:
+758                    index += 1
+759                    # This is the first entry 
+760                    if index == 0:
+761                        if entry.is_directory():
+762                            if self.config.no_colors:
+763                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+764                            else:
+765                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+766                            recurse_action(
+767                                base_dir=base_dir, 
+768                                path=path+[entry.get_longname()],
+769                                prompt=prompt+["│   "]
+770                            )
+771                        else:
+772                            if self.config.no_colors:
+773                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+774                            else:
+775                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+776
+777                    # This is the last entry
+778                    elif index == len(entries):
+779                        if entry.is_directory():
+780                            if self.config.no_colors:
+781                                print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+782                            else:
+783                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+784                            recurse_action(
+785                                base_dir=base_dir, 
+786                                path=path+[entry.get_longname()],
+787                                prompt=prompt+["    "]
+788                            )
+789                        else:
+790                            if self.config.no_colors:
+791                                print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+792                            else:
+793                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+794                        
+795                    # These are entries in the middle
+796                    else:
+797                        if entry.is_directory():
+798                            if self.config.no_colors:
+799                                print("%s%s\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+800                            else:
+801                                print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+802                            recurse_action(
+803                                base_dir=base_dir, 
+804                                path=path+[entry.get_longname()],
+805                                prompt=prompt+["│   "]
+806                            )
+807                        else:
+808                            if self.config.no_colors:
+809                                print("%s%s" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+810                            else:
+811                                print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[1]]), entry.get_longname()))
+812
+813            # 
+814            elif len(entries) == 1:
+815                entry = entries[0]
+816                if entry.is_directory():
+817                    if self.config.no_colors:
+818                        print("%s%s\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+819                    else:
+820                        print("%s\x1b[1;96m%s\x1b[0m\\" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+821                    recurse_action(
+822                        base_dir=base_dir, 
+823                        path=path+[entry.get_longname()],
+824                        prompt=prompt+["    "]
+825                    )
+826                else:
+827                    if self.config.no_colors:
+828                        print("%s%s" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+829                    else:
+830                        print("%s\x1b[1m%s\x1b[0m" % (''.join(prompt+[bars[2]]), entry.get_longname()))
+831
+832        # Entrypoint
+833        try:
+834            if self.config.no_colors:
+835                print("%s\\" % path)
+836            else:
+837                print("\x1b[1;96m%s\x1b[0m\\" % path)
+838            recurse_action(
+839                base_dir=self.smb_cwd, 
+840                path=[path],
+841                prompt=[""]
+842            )
+843        except (BrokenPipeError, KeyboardInterrupt) as e:
+844            print("[!] Interrupted.")
+845            self.close_smb_session()
+846            self.init_smb_session()
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + set_share(self, shareName): + + + +
+ +
850    def set_share(self, shareName):
+851        """
+852        Sets the current SMB share to the specified share name.
+853
+854        This method updates the SMB session to use the specified share name. It checks if the share name is valid
+855        and updates the smb_share attribute of the SMBSession instance.
+856
+857        Parameters:
+858            shareName (str): The name of the share to set as the current SMB share.
+859
+860        Raises:
+861            ValueError: If the shareName is None or an empty string.
+862        """
+863
+864        if shareName is not None:
+865            self.smb_share = shareName
+
+ + +

Sets the current SMB share to the specified share name.

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + set_cwd(self, path=None): + + + +
+ +
867    def set_cwd(self, path=None):
+868        """
+869        Sets the current working directory on the SMB share to the specified path.
+870
+871        This method updates the current working directory (cwd) of the SMB session to the given path if it is a valid directory.
+872        If the specified path is not a directory, the cwd remains unchanged.
+873
+874        Parameters:
+875            path (str): The path to set as the current working directory.
+876
+877        Raises:
+878            ValueError: If the specified path is not a directory.
+879        """
+880
+881        if path is not None:
+882            # Set path separators to ntpath sep 
+883            if '/' in path:
+884                path = path.replace('/', ntpath.sep)
+885
+886            if path.startswith(ntpath.sep):
+887                # Absolute path
+888                path = path + ntpath.sep
+889            else:
+890                # Relative path to the CWD
+891                if len(self.smb_cwd) == 0:
+892                    path = path + ntpath.sep
+893                else:
+894                    path = self.smb_cwd + ntpath.sep + path
+895            
+896            # Path normalization
+897            path = ntpath.normpath(path)
+898            path = re.sub(r'\\+', r'\\', path)
+899
+900            if path in ["", ".", ".."]:
+901                self.smb_cwd = ""
+902            else:
+903                if self.path_isdir(pathFromRoot=path.strip(ntpath.sep)):
+904                    # Path exists on the remote 
+905                    self.smb_cwd = ntpath.normpath(path)
+906                else:
+907                    # Path does not exists or is not a directory on the remote 
+908                    print("[!] Remote directory '%s' does not exist." % path)
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/core/utils.html b/documentation/smbclientng/core/utils.html new file mode 100644 index 0000000..068c3e3 --- /dev/null +++ b/documentation/smbclientng/core/utils.html @@ -0,0 +1,1055 @@ + + + + + + + smbclientng.core.utils API documentation + + + + + + + + + +
+
+

+smbclientng.core.utils

+ + + + + + +
  1#!/usr/bin/env python3
+  2# -*- coding: utf-8 -*-
+  3# File name          : utils.py
+  4# Author             : Podalirius (@podalirius_)
+  5# Date created       : 23 may 2024
+  6
+  7
+  8import datetime
+  9import os
+ 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 '-')
+ 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)
+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())
+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)
+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)
+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.")
+
+ + +
+
+ +
+ + def + parse_lm_nt_hashes(lm_nt_hashes_string): + + + +
+ +
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
+
+ + +

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

+ +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + b_filesize(l): + + + +
+ +
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])
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + unix_permissions(entryname): + + + +
+ +
 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 '-')
+ 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)
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + STYPE_MASK(stype_value): + + + +
+ +
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
+
+ + +

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

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + windows_ls_entry(entry, config, pathToPrint=None): + + + +
+ +
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())
+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))
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + local_tree(path, config): + + + +
+ +
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)
+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)
+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.")
+
+ + +

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

+ +

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

+ +

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

+
+ + +
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/modules.html b/documentation/smbclientng/modules.html new file mode 100644 index 0000000..da054db --- /dev/null +++ b/documentation/smbclientng/modules.html @@ -0,0 +1,246 @@ + + + + + + + smbclientng.modules API documentation + + + + + + + + + +
+
+

+smbclientng.modules

+ + + + + + +
1#!/usr/bin/env python3
+2# -*- coding: utf-8 -*-
+3# File name          : __init__.py
+4# Author             : Podalirius (@podalirius_)
+5# Date created       : 23 may 2024
+
+ + +
+
+ + \ No newline at end of file diff --git a/documentation/smbclientng/modules/Find.html b/documentation/smbclientng/modules/Find.html new file mode 100644 index 0000000..9e513f6 --- /dev/null +++ b/documentation/smbclientng/modules/Find.html @@ -0,0 +1,935 @@ + + + + + + + smbclientng.modules.Find API documentation + + + + + + + + + +
+
+

+smbclientng.modules.Find

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

A class to search for files in a directory hierarchy.

+ +

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

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

Parses the command line arguments provided to the module.

+ +

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

+ +

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

+ +

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

+
+ + +
+
+ +
+ + def + run(self, arguments): + + + +
+ +
214    def run(self, arguments):
+215        """
+216        This function recursively searches for files in a directory hierarchy and prints the results based on specified criteria.
+217
+218        Args:
+219            base_dir (str): The base directory to start the search from.
+220            paths (list): List of paths to search within the base directory.
+221            depth (int): The current depth level in the directory hierarchy.
+222
+223        Returns:
+224            None
+225        """
+226
+227        self.options = self.parseArgs(arguments=arguments)
+228
+229        if self.options is not None:
+230            # Entrypoint
+231            try:
+232                next_directories_to_explore = []
+233                for path in list(set(self.options.paths)):
+234                    next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
+235                next_directories_to_explore = sorted(list(set(next_directories_to_explore)))
+236                
+237                depth = 0
+238                
+239                while len(next_directories_to_explore) != 0:
+240                    next_directories_to_explore = self.__recurse_action(
+241                        paths=next_directories_to_explore,
+242                        depth=depth
+243                    )
+244                    depth = depth + 1
+245
+246            except (BrokenPipeError, KeyboardInterrupt) as e:
+247                print("[!] Interrupted.")
+248                self.smbSession.close_smb_session()
+249                self.smbSession.init_smb_session()
+
+ + +

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

+ +

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

+ +

Returns: + None

+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/run-tests.py b/run-tests.py deleted file mode 100644 index 95e5434..0000000 --- a/run-tests.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : __main__.py -# Author : Podalirius (@podalirius_) -# Date created : 23 may 2024 - -from smbclientng.tests.test_SMBSession import test_SMBSession -from smbclientng.tests.test_SMBSession_path_isdir import test_SMBSession_path_isdir -from smbclientng.tests.test_SMBSession_path_isfile import test_SMBSession_path_isfile - - -__builtins__.print = lambda x:None - - -testCases = [ - test_SMBSession, - test_SMBSession_path_isdir, - test_SMBSession_path_isfile -] - - -def main(): - for t in testCases: - t().runAll() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/smbclientng/__main__.py b/smbclientng/__main__.py index c8c8445..972fc87 100644 --- a/smbclientng/__main__.py +++ b/smbclientng/__main__.py @@ -52,6 +52,22 @@ def parseArgs(): def main(): + """ + Main function to execute the smbclient-ng tool. + + This function handles the command-line arguments, initializes the SMB session, + and starts the interactive shell. It also manages the authentication process + using either password or hashes, and sets up the session configuration based + on the provided command-line options. + + If Kerberos authentication is specified, it ensures that the KDC host is provided. + It exits with an error message if necessary conditions are not met for the session + to start properly. + + The function also handles debug mode outputs and exits cleanly, providing feedback + about the session termination if debug mode is enabled. + """ + options = parseArgs() # Parse hashes diff --git a/smbclientng/core/Config.py b/smbclientng/core/Config.py index f0caf56..a8ff644 100644 --- a/smbclientng/core/Config.py +++ b/smbclientng/core/Config.py @@ -8,6 +8,21 @@ class Config(object): + """ + Configuration handler for smbclientng. + + This class manages the configuration settings for the smbclientng tool, including debug and color output settings. + It provides a structured way to access and modify these settings throughout the application. + + Attributes: + _debug (bool): Flag to enable or disable debug mode. + _no_colors (bool): Flag to enable or disable colored output, depending on the platform. + + Methods: + debug: Property to get or set the debug mode. + no_colors: Property to get or set the colored output preference. + """ + def __init__(self, debug=False, no_colors=None): self._debug = debug diff --git a/smbclientng/core/InteractiveShell.py b/smbclientng/core/InteractiveShell.py index efc4fda..7f1a9db 100644 --- a/smbclientng/core/InteractiveShell.py +++ b/smbclientng/core/InteractiveShell.py @@ -608,6 +608,8 @@ def command_use(self, arguments, command): # Private functions ======================================================= def __load_modules(self): + + self.modules.clear() modules_dir = os.path.normpath(os.path.dirname(__file__) + os.path.sep + ".." + os.path.sep + "modules") @@ -640,6 +642,17 @@ def __load_modules(self): self.commandCompleterObject.commands["module"]["subcommands"] = list(self.modules.keys()) def __prompt(self): + """ + Prints the command prompt for the interactive shell. + + This method constructs and returns the command prompt string based on the current state of the SMB session. + The prompt indicates the connection status with a visual symbol and displays the current working directory + or the SMB share path. The prompt appearance changes based on whether colors are enabled in the configuration. + + Returns: + str: The formatted command prompt string. + """ + self.smbSession.ping_smb_session() if self.smbSession.connected: if self.config.no_colors: diff --git a/smbclientng/core/ModuleArgumentParser.py b/smbclientng/core/ModuleArgumentParser.py index 89c988c..752fc34 100644 --- a/smbclientng/core/ModuleArgumentParser.py +++ b/smbclientng/core/ModuleArgumentParser.py @@ -8,9 +8,32 @@ import argparse import sys + class ModuleArgumentParser(argparse.ArgumentParser): + """ + A custom argument parser for handling module-specific command-line arguments in the smbclientng application. + + This class extends the argparse.ArgumentParser and provides custom error handling specific to the needs of smbclientng modules. + It is designed to provide clear and user-friendly command-line interfaces for various modules within the smbclientng suite. + + Attributes: + None + + Methods: + error(message: str): + Overrides the default error handling to provide a more informative error message and display the help text. + """ def error(self, message): + """ + Overrides the default error handling of argparse.ArgumentParser to provide a custom error message and help display. + + This method is called when ArgumentParser encounters an error. It writes the error message to stderr, + displays the help message, and then exits the program with a status code of 2. + + Args: + message (str): The error message to be displayed. + """ + sys.stderr.write('[!] Error: %s\n' % message) - self.print_help() - #sys.exit(2) \ No newline at end of file + self.print_help() \ No newline at end of file diff --git a/smbclientng/core/utils.py b/smbclientng/core/utils.py index e191d9e..d5b15ad 100644 --- a/smbclientng/core/utils.py +++ b/smbclientng/core/utils.py @@ -12,9 +12,27 @@ import stat -# Extracted from p0dalirius/sectools library -# Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24 def parse_lm_nt_hashes(lm_nt_hashes_string): + """ + Parse the input string containing LM and NT hash values and return them separately. + + This function takes a string containing LM and NT hash values, typically separated by a colon (:). + It returns the LM and NT hash values as separate strings. If only one hash value is provided, it is + assumed to be the NT hash and the LM hash is set to its default value. If no valid hash values are + found, both return values are empty strings. + + Args: + lm_nt_hashes_string (str): A string containing LM and NT hash values separated by a colon. + + Returns: + tuple: A tuple containing two strings (lm_hash_value, nt_hash_value). + - lm_hash_value: The LM hash value or its default if not provided. + - nt_hash_value: The NT hash value or its default if not provided. + + Extracted from p0dalirius/sectools library + Src: https://github.com/p0dalirius/sectools/blob/7bb3f5cb7815ad4d4845713c8739e2e2b0ea4e75/sectools/windows/crypto.py#L11-L24 + """ + lm_hash_value, nt_hash_value = "", "" if lm_nt_hashes_string is not None: matched = re.match("([0-9a-f]{32})?(:)?([0-9a-f]{32})?", lm_nt_hashes_string.strip().lower()) @@ -44,6 +62,7 @@ def b_filesize(l): Returns: str: A string representing the file size in a more readable format, including the appropriate unit. """ + units = ['B','kB','MB','GB','TB','PB'] for k in range(len(units)): if l < (1024**(k+1)): @@ -67,6 +86,7 @@ def unix_permissions(entryname): three groups of 'r', 'w', 'x' (read, write, execute permissions) for owner, group, and others respectively. """ + mode = os.lstat(entryname).st_mode permissions = [] @@ -182,6 +202,17 @@ def windows_ls_entry(entry, config, pathToPrint=None): def local_tree(path, config): + """ + This function recursively lists the contents of a directory in a tree-like format. + + Parameters: + path (str): The path to the directory to list. + config (object): Configuration settings which may affect the output, such as whether to use colors. + + Returns: + None: This function does not return anything but prints the directory tree to the console. + """ + def recurse_action(base_dir="", path=[], prompt=[]): bars = ["│ ", "├── ", "└── "] diff --git a/smbclientng/modules/Find.py b/smbclientng/modules/Find.py index 7911231..869b963 100644 --- a/smbclientng/modules/Find.py +++ b/smbclientng/modules/Find.py @@ -24,6 +24,18 @@ class Find(Module): description = "Search for files in a directory hierarchy" def parseArgs(self, arguments): + """ + Parses the command line arguments provided to the module. + + This method initializes the argument parser with the module's name and description, and defines all the necessary arguments that the module accepts. It then parses the provided command line arguments based on these definitions. + + Args: + arguments (str): A string of command line arguments. + + Returns: + ModuleArgumentParser.Namespace | None: The parsed arguments as a Namespace object if successful, None if there are no arguments or help is requested. + """ + parser = ModuleArgumentParser(prog=self.name, description=self.description) # Adding positional arguments diff --git a/smbclientng/tests/__init__.py b/smbclientng/tests/__init__.py deleted file mode 100644 index 288d68d..0000000 --- a/smbclientng/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : __init__.py -# Author : Podalirius (@podalirius_) -# Date created : 23 may 2024 \ No newline at end of file diff --git a/smbclientng/tests/common.py b/smbclientng/tests/common.py deleted file mode 100644 index da16b11..0000000 --- a/smbclientng/tests/common.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - - -from smbclientng.core.SMBSession import SMBSession -import sys - - -class CustomTestCase(object): - - title = "CustomTestCase" - - def setUp(self): - pass - - def assertFalse(self, value, message): - self.__printTestResult( - (value == False), - message - ) - - def assertTrue(self, value, message): - self.__printTestResult( - (value == True), - message - ) - - def assertIsNotNone(self, value, message): - self.__printTestResult( - (value is not None), - message - ) - - def __printTestResult(self, testPassed, message): - message = message + " \x1b[90m" + "─"*(70-len(message)) + "\x1b[0m" - if testPassed: - sys.stdout.write(" ├── %s \x1b[1;48;2;83;170;51;97m PASSED \x1b[0m\n" % message) - else: - sys.stdout.write(" ├── %s \x1b[1;48;2;233;61;3;97m FAILED \x1b[0m\n" % message) - sys.stdout.flush() - - @classmethod - def runAll(cls): - sys.stdout.write("[>] \x1b[1m%s\x1b[0m\n" % cls.title) - sys.stdout.flush() - - testCases = [f for f in dir(cls) if f.startswith('test_')] - for fname in testCases: - self = cls() - self.setUp() - getattr(self, fname)() - - -def CreateSMBSession(): - return SMBSession( - address="10.0.0.201", - domain="LAB", - username="Administrator", - password="Admin123!", - lmhash="", - nthash="", - use_kerberos=False, - debug=True - ) - diff --git a/smbclientng/tests/run-tests.py b/smbclientng/tests/run-tests.py deleted file mode 100644 index 95e5434..0000000 --- a/smbclientng/tests/run-tests.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : __main__.py -# Author : Podalirius (@podalirius_) -# Date created : 23 may 2024 - -from smbclientng.tests.test_SMBSession import test_SMBSession -from smbclientng.tests.test_SMBSession_path_isdir import test_SMBSession_path_isdir -from smbclientng.tests.test_SMBSession_path_isfile import test_SMBSession_path_isfile - - -__builtins__.print = lambda x:None - - -testCases = [ - test_SMBSession, - test_SMBSession_path_isdir, - test_SMBSession_path_isfile -] - - -def main(): - for t in testCases: - t().runAll() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/smbclientng/tests/test_SMBSession.py b/smbclientng/tests/test_SMBSession.py deleted file mode 100644 index 9dbbb6c..0000000 --- a/smbclientng/tests/test_SMBSession.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : test_SMBSession.py -# Author : Podalirius (@podalirius_) -# Date created : 26 may 2024 - - -from .common import CreateSMBSession, CustomTestCase - - -class test_SMBSession(CustomTestCase): - - title = "SMBSession()" - - def test_initialization(self): - session = CreateSMBSession() - self.assertIsNotNone(session, "Correct SMB Session initialization") - - def test_connection_status(self): - session = CreateSMBSession() - self.assertFalse(session.connected, "Session should not be connected initially") - diff --git a/smbclientng/tests/test_SMBSession_path_isdir.py b/smbclientng/tests/test_SMBSession_path_isdir.py deleted file mode 100644 index f956061..0000000 --- a/smbclientng/tests/test_SMBSession_path_isdir.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : test_SMBSession_path_isdir.py -# Author : Podalirius (@podalirius_) -# Date created : 26 may 2024 - - -from .common import CreateSMBSession, CustomTestCase - - -class test_SMBSession_path_isdir(CustomTestCase): - - title = "SMBSession.path_isdir()" - - def setUp(self): - self.session = CreateSMBSession() - - def test_path_none(self): - self.assertFalse(self.session.path_isdir(None), "Testing a None path") - - def test_path_true_empty(self): - self.assertTrue(self.session.path_isdir(""), "Testing valididty of path ''") - - def test_path_true_space(self): - self.assertTrue(self.session.path_isdir(" "), "Testing valididty of path ' '") - - def test_path_true_with_dot(self): - self.assertTrue(self.session.path_isdir("."), "Testing valididty of path '.'") - - def test_path_true_with_forward_slash(self): - self.assertTrue(self.session.path_isdir("/"), "Testing valididty of path '/'") - - def test_path_true_with_back_slash(self): - self.assertTrue(self.session.path_isdir("\\"), "Testing valididty of path '\\'") - - def test_path_existence(self): - self.session.set_share("TestShare") - self.session.set_cwd("path_isdir") - path = "path_isdir\\a\\b" - self.assertTrue( - self.session.path_isdir(path), - "Testing existence of path '%s' in share '%s'" % (path, self.session.smb_share) - ) \ No newline at end of file diff --git a/smbclientng/tests/test_SMBSession_path_isfile.py b/smbclientng/tests/test_SMBSession_path_isfile.py deleted file mode 100644 index cdcb9c6..0000000 --- a/smbclientng/tests/test_SMBSession_path_isfile.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# File name : test_SMBSession_path_isdir.py -# Author : Podalirius (@podalirius_) -# Date created : 26 may 2024 - - -from .common import CreateSMBSession, CustomTestCase - - -class test_SMBSession_path_isfile(CustomTestCase): - - title = "SMBSession.path_isfile()" - - def setUp(self): - self.session = CreateSMBSession() - - def test_path_none(self): - self.assertFalse(self.session.path_isfile(None), "Testing a None path") - - def test_path_empty(self): - self.assertFalse(self.session.path_isfile(""), "Testing validity of path ''") - - def test_path_space(self): - self.assertFalse(self.session.path_isfile(" "), "Testing validity of path ' '") - - def test_path_with_dot(self): - self.assertFalse(self.session.path_isfile("."), "Testing validity of path '.'") - - def test_path_with_forward_slash(self): - self.assertFalse(self.session.path_isfile("/"), "Testing validity of path '/'") - - def test_path_with_back_slash(self): - self.assertFalse(self.session.path_isfile("\\"), "Testing validity of path '\\'") - - def test_path_existence(self): - self.session.set_share("TestShare") - self.session.set_cwd("path_isfile") - path = "path_isfile\\a\\b\\file.txt" - self.assertTrue( - self.session.path_isfile(path), - "Testing existence of file '%s' in share '%s'" % (path, self.session.smb_share) - )