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:28:03 UTC
[mina-sshd] 01/01: [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
commit e5db956854aca00c09fe2685b53b79daed66f514
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Wed Oct 16 14:17:46 2019 +0300
[SSHD-914] WIP
---
.gitignore | 3 +
src/python/sftpclient.py | 380 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 383 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..c8ef38e
--- /dev/null
+++ b/src/python/sftpclient.py
@@ -0,0 +1,380 @@
+#!/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, argsList):
+ dirPath = curdir;
+ if not isEmpty(argsList):
+ dirPath = argsList.pop(0)
+ dirPath = dirPath.strip()
+ dirPath = os.path.join(curdir, dirPath)
+
+ # 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))
+
+def doChdir(sftp, homedir, curdir, argsList):
+ dirPath = homedir
+ if not isEmpty(argsList):
+ dirPath = argsList.pop(0)
+ dirPath = dirPath.strip()
+ dirPath = os.path.join(curdir, dirPath)
+ sftp.chdir(dirPath)
+
+# ----------------------------------------------------------------------------
+
+# 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):
+ homedir = sftp.normalize('.')
+ sftp.chdir(homedir)
+
+ while True:
+ curdir = sftp.getcwd()
+ sys.stdout.write("%s > " % curdir)
+ l = sys.stdin.readline()
+ l = l.strip()
+
+ if isEmpty(l):
+ continue
+
+ argsList = l.split(' ')
+ op = argsList.pop(0)
+
+ if (op == "quit") or (op == "exit") or (op == "bye"):
+ break
+ elif (op == "ls") or (op == "list"):
+ doList(sftp, curdir, argsList)
+ elif (op == "cd"):
+ doChdir(sftp, homedir, curdir, argsList)
+ 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);
+ 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