You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2019/10/16 11:17:57 UTC

[mina-sshd] branch SSHD-914 updated: [SSHD-914] WIP

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch SSHD-914
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/SSHD-914 by this push:
     new 48f720a  [SSHD-914] WIP
48f720a is described below

commit 48f720a2be1342f20857ed97de5c737584d849a2
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Wed Oct 16 14:17:46 2019 +0300

    [SSHD-914] WIP
---
 .gitignore               |   3 +
 src/python/sftpclient.py | 365 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 368 insertions(+)

diff --git a/.gitignore b/.gitignore
index 485a2b4..39cdffd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@ Servers/
 RemoteSystemsTempFiles/
 .classpath
 .project
+# Puthon related files
+.pydevproject
+*.pyc
 .checkstyle
 .eclipse-pmd
 .pmd
diff --git a/src/python/sftpclient.py b/src/python/sftpclient.py
new file mode 100644
index 0000000..9c0ffba
--- /dev/null
+++ b/src/python/sftpclient.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+Simple wrapper for Paramiko SFTP client (see http://www.paramiko.org/)
+'''
+
+import json
+import os
+import signal
+import sys
+
+import paramiko
+
+
+VERSION='1.0'
+
+# -----------------------------------------------------------------------------------------
+
+def die(msg=None,rc=1):
+    """
+    Cleanly exits the program with an error message
+    """
+
+    if msg:
+        print(msg)
+
+    sys.exit(rc)
+
+# ----------------------------------------------------------------------------
+
+def isEmpty(s):
+    if (s is None) or (len(s) <= 0):
+        return True
+    else:
+        return False
+
+# ----------------------------------------------------------------------------
+
+def isNumberString(value):
+    """
+    Checks if value is a string that has only digits - possibly with leading '+' or '-'
+    """
+    if not value:
+        return False
+
+    sign = value[0]
+    if (sign == '+') or (sign == '-'):
+        if len(value) <= 1:
+            return False
+
+        absValue = value[1:]
+        return absValue.isdigit()
+    else:
+        if len(value) <= 0:
+            return False
+        else:
+            return value.isdigit()
+
+def isNumberValue(value):
+    return isinstance(value, (int, float))
+
+# ----------------------------------------------------------------------------
+
+def isFloatingPointString(value):
+    """
+    Checks if value is a string that has only digits - possibly with leading '+' or '-' - AND a single dot
+    """
+    if isEmpty(value):
+        return False
+
+    sign = value[0]
+    if (sign == '+') or (sign == '-'):
+        if len(value) <= 1:
+            return False
+
+        absValue = value[1:]
+    else:
+        absValue = value
+
+    dotPos = absValue.find('.')
+    # Must have a dot and it cannot be the last character
+    if (dotPos < 0) or (dotPos == (len(absValue) - 1)):
+        return False
+
+    # Must have EXACTLY one dot
+    dotCount = absValue.count('.')
+    if dotCount != 1:
+        return False
+
+    # Make sure both sides of the dot are integer numbers
+    intPart = absValue[0:dotPos]
+    if not isNumberString(intPart):
+        return False
+
+    facPart = absValue[dotPos + 1:]
+    # Do not allow 123.-5
+    sign = facPart[0]
+    if (sign == '+') or (sign == '-'):
+        return False
+
+    if not isNumberString(facPart):
+        return False
+
+    return True
+
+# ----------------------------------------------------------------------------
+
+def normalizeValue(value):
+    """
+    Checks if value is 'True', 'False' or all numeric and converts it accordingly
+    Otherwise it just returns it
+
+    Args:
+        value (str) - String value
+    """
+
+    if not value:
+        return value
+
+    loCase = value.lower()
+    if loCase == "none":
+        return None
+    elif loCase == "true":
+        return True
+    elif loCase == "false":
+        return False
+    elif isNumberString(loCase):
+        return int(loCase)
+    else:
+        return value
+
+# ----------------------------------------------------------------------------
+
+def parseCommandLineArguments(args):
+    """
+    Parses an array of arguments having the format: --name=value. If
+    only --name is provided then it is assumed to a TRUE boolean value.
+    If the value is all digits, then it is assumed to be a number.
+
+    If the same key is specified more than once, then a list of
+    the accumulated values is created. The result is a dictionary
+    with the names as the keys and value as their mapped values
+
+    Args:
+        args (str[]) - The command line arguments to parse
+    """
+
+    valsMap = {}
+    if len(args) <= 0:
+        return valsMap
+
+    for item in args:
+        if not item.startswith("--"):
+            raise Exception("Missing option identifier: %s" % item)
+
+        propPair = item[2:]     # strip the prefix
+        sepPos = propPair.find('=')
+
+        if sepPos == 0:
+            raise Exception("Missing name: %s" % item)
+        if sepPos >= (len(propPair) - 1):
+            raise Exception("Missing value: %s" % item)
+
+        propName = propPair
+        propValue = None
+        if sepPos < 0:
+            propValue = True
+        else:
+            propName = propPair[0:sepPos]
+            propValue = normalizeValue(propPair[sepPos + 1:])
+
+        if propName in valsMap:
+            curValue = valsMap[propName]
+            if not isinstance(curValue, list):
+                curValue = [ curValue ]
+            curValue.append(propValue)
+            valsMap[propName] = curValue
+        else:
+            valsMap[propName] = propValue
+
+    return valsMap
+
+# ----------------------------------------------------------------------------
+
+def resolvePathVariables(path):
+    """
+    Expands ~/xxx and ${XXX} variables
+    """
+    if isEmpty(path):
+        return path
+
+    path = os.path.expanduser(path)
+    path = os.path.expandvars(path)
+    return path
+
+# ----------------------------------------------------------------------------
+
+def _decode_list(data):
+    # can happen for internal sub-lists of objects
+    if isinstance(data, dict):
+        return _decode_dict(data)
+
+    rv = []
+    for item in data:
+        if isinstance(item, list):
+            item = _decode_list(item)
+        elif isinstance(item, dict):
+            item = _decode_dict(item)
+        rv.append(item)
+    return rv
+
+# ----------------------------------------------------------------------------
+
+def _decode_dict(data):
+    # can happen for internal sub-lists of objects
+    if isinstance(data, list):
+        return _decode_list(data)
+
+    rv = {}
+    for key, value in data.items():
+        if isinstance(value, list):
+            value = _decode_list(value)
+        elif isinstance(value, dict):
+            value = _decode_dict(value)
+        rv[key] = value
+
+    return rv
+
+# ----------------------------------------------------------------------------
+
+def loadJsonFile(configFile):
+    if isEmpty(configFile):
+        return {}
+
+    with open(configFile) as config_file:
+        return json.load(config_file, object_hook=_decode_dict);
+
+# ----------------------------------------------------------------------------
+
+def createSftpClient(host, port, username, password, keyfilepath=None, keyfiletype='RSA'):
+    """
+    createSftpClient(host, port, username, password, keyfilepath, keyfiletype) -> SFTPClient
+ 
+    Creates a SFTP client connected to the supplied host on the supplied port authenticating as the user with
+    supplied username and supplied password or with the private key in a file with the supplied path.
+    If a private key is used for authentication, the type of the keyfile needs to be specified as DSA or RSA.
+    :rtype: SFTPClient object.
+    """
+    sftp = None
+    transport = None
+    try:
+        key = None
+        if keyfilepath is not None:
+            # Get private key used to authenticate user.
+            if keyfiletype == 'DSA':
+                # The private key is a DSA type key.
+                key = paramiko.DSSKey.from_private_key_file(keyfilepath)
+            else:
+                # The private key is a RSA type key.
+                key = paramiko.RSAKey.from_private_key(keyfilepath)
+
+        # Create Transport object using supplied method of authentication.
+        transport = paramiko.Transport((host, port))
+        transport.connect(None, username, password, key)
+ 
+        sftp = paramiko.SFTPClient.from_transport(transport)
+        return sftp
+    except Exception as e:
+        print('An error occurred creating SFTP client: %s: %s' % (e.__class__, e))
+
+        if sftp is not None:
+            try:
+                sftp.close()
+            except Exception as err:
+                print('Failed to close SFTP client: %s: %s' % (err.__class__, err))
+
+        if transport is not None:
+            try:
+                transport.close()
+            except Exception as err:
+                print('Failed to close transport: %s: %s' % (err.__class__, err))
+
+        raise e
+    
+# =========================================================================================
+
+def doList(sftp, curdir, cmd):
+    dirPath = curdir;
+    pos = cmd.find(" ")
+    if pos > 0:
+        dirPath = cmd[pos + 1:]
+        dirPath = dirPath.strip()
+
+    # Also available: listdir_attr, listdir
+    dirlist = sftp.listdir_iter(path=dirPath)
+    for row in dirlist:
+        # see https://docs.paramiko.org/en/2.6/api/sftp.html#paramiko.sftp_attr.SFTPAttributes
+        print("    %s" % str(row))
+    
+# see https://github.com/paramiko/paramiko/blob/master/demos/demo_sftp.py
+# see https://docs.paramiko.org/en/2.6/api/sftp.html
+def doSftp(sftp, args):
+    curdir = sftp.normalize('.')
+    sftp.chdir(curdir)
+    
+    while True:
+        curdir = sftp.getcwd()
+        sys.stdout.write("%s > " % curdir)
+        l = sys.stdin.readline()
+        l = l.strip()
+    
+        if isEmpty(l):
+            continue
+        
+        if l.startswith("quit") or l.startswith("exit") or l.startswith("bye"):
+            break
+        if l.startswith("ls") or l.startswith("list"):
+            doList(sftp, curdir, l)
+        else:
+            print("Unknown command: %s" % l)
+
+def doMain(args):
+    host = args.get("host", "localhost")
+    port = args.get("port", 22)
+    username = args.get("username", None)
+    password = args.get("password", None)
+    keyfile = args.get("keyFile", None)
+    keytype = args.get("keyType", "RSA")
+    
+    sftp = createSftpClient(host, port, username, password, keyfilepath=keyfile, keyfiletype=keytype)
+    try:
+        doSftp(sftp, args);
+    except Exception as e:
+        print('An error occurred using the SFTP client: %s: %s' % (e.__class__, e))
+        raise e
+    finally:
+        sftp.close()
+        
+def main(args):
+    if len(args) > 0:
+        subArgs = parseCommandLineArguments(args)
+    else:
+        subArgs = {}
+    doMain(subArgs)
+    sys.exit(0)
+    
+# ----------------------------------------------------------------------------
+
+def signal_handler(signal, frame):
+    die('Exit due to Control+C')
+
+if __name__ == "__main__":
+    pyVersion = sys.version_info
+    if pyVersion.major != 3:
+        die("Major Python version must be 3.x: %s" % str(pyVersion))
+    if pyVersion.minor < 0:
+        print("Warning: minor Python version %s should be at least 3.0+" % str(pyVersion))
+
+    signal.signal(signal.SIGINT, signal_handler)
+    if os.name == 'nt':
+        print("Use Ctrl+Break to stop the script")
+    else:
+        print("Use Ctrl+C to stop the script")
+    main(sys.argv[1:])
\ No newline at end of file