Open Media Library Platform
This commit is contained in:
commit
411ad5b16f
5849 changed files with 1778641 additions and 0 deletions
|
|
@ -0,0 +1 @@
|
|||
'conch scripts'
|
||||
832
Darwin/lib/python2.7/site-packages/twisted/conch/scripts/cftp.py
Normal file
832
Darwin/lib/python2.7/site-packages/twisted/conch/scripts/cftp.py
Normal file
|
|
@ -0,0 +1,832 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_cftp -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation module for the I{cftp} command.
|
||||
"""
|
||||
|
||||
import os, sys, getpass, struct, tty, fcntl, stat
|
||||
import fnmatch, pwd, glob
|
||||
|
||||
from twisted.conch.client import connect, default, options
|
||||
from twisted.conch.ssh import connection, common
|
||||
from twisted.conch.ssh import channel, filetransfer
|
||||
from twisted.protocols import basic
|
||||
from twisted.internet import reactor, stdio, defer, utils
|
||||
from twisted.python import log, usage, failure
|
||||
|
||||
class ClientOptions(options.ConchOptions):
|
||||
|
||||
synopsis = """Usage: cftp [options] [user@]host
|
||||
cftp [options] [user@]host[:dir[/]]
|
||||
cftp [options] [user@]host[:file [localfile]]
|
||||
"""
|
||||
longdesc = ("cftp is a client for logging into a remote machine and "
|
||||
"executing commands to send and receive file information")
|
||||
|
||||
optParameters = [
|
||||
['buffersize', 'B', 32768, 'Size of the buffer to use for sending/receiving.'],
|
||||
['batchfile', 'b', None, 'File to read commands from, or \'-\' for stdin.'],
|
||||
['requests', 'R', 5, 'Number of requests to make before waiting for a reply.'],
|
||||
['subsystem', 's', 'sftp', 'Subsystem/server program to connect to.']]
|
||||
|
||||
compData = usage.Completions(
|
||||
descriptions={
|
||||
"buffersize": "Size of send/receive buffer (default: 32768)"},
|
||||
extraActions=[usage.CompleteUserAtHost(),
|
||||
usage.CompleteFiles(descr="local file")])
|
||||
|
||||
def parseArgs(self, host, localPath=None):
|
||||
self['remotePath'] = ''
|
||||
if ':' in host:
|
||||
host, self['remotePath'] = host.split(':', 1)
|
||||
self['remotePath'].rstrip('/')
|
||||
self['host'] = host
|
||||
self['localPath'] = localPath
|
||||
|
||||
def run():
|
||||
# import hotshot
|
||||
# prof = hotshot.Profile('cftp.prof')
|
||||
# prof.start()
|
||||
args = sys.argv[1:]
|
||||
if '-l' in args: # cvs is an idiot
|
||||
i = args.index('-l')
|
||||
args = args[i:i+2]+args
|
||||
del args[i+2:i+4]
|
||||
options = ClientOptions()
|
||||
try:
|
||||
options.parseOptions(args)
|
||||
except usage.UsageError, u:
|
||||
print 'ERROR: %s' % u
|
||||
sys.exit(1)
|
||||
if options['log']:
|
||||
realout = sys.stdout
|
||||
log.startLogging(sys.stderr)
|
||||
sys.stdout = realout
|
||||
else:
|
||||
log.discardLogs()
|
||||
doConnect(options)
|
||||
reactor.run()
|
||||
# prof.stop()
|
||||
# prof.close()
|
||||
|
||||
def handleError():
|
||||
global exitStatus
|
||||
exitStatus = 2
|
||||
try:
|
||||
reactor.stop()
|
||||
except: pass
|
||||
log.err(failure.Failure())
|
||||
raise
|
||||
|
||||
def doConnect(options):
|
||||
# log.deferr = handleError # HACK
|
||||
if '@' in options['host']:
|
||||
options['user'], options['host'] = options['host'].split('@',1)
|
||||
host = options['host']
|
||||
if not options['user']:
|
||||
options['user'] = getpass.getuser()
|
||||
if not options['port']:
|
||||
options['port'] = 22
|
||||
else:
|
||||
options['port'] = int(options['port'])
|
||||
host = options['host']
|
||||
port = options['port']
|
||||
conn = SSHConnection()
|
||||
conn.options = options
|
||||
vhk = default.verifyHostKey
|
||||
uao = default.SSHUserAuthClient(options['user'], options, conn)
|
||||
connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
|
||||
|
||||
def _ebExit(f):
|
||||
#global exitStatus
|
||||
if hasattr(f.value, 'value'):
|
||||
s = f.value.value
|
||||
else:
|
||||
s = str(f)
|
||||
print s
|
||||
#exitStatus = "conch: exiting with error %s" % f
|
||||
try:
|
||||
reactor.stop()
|
||||
except: pass
|
||||
|
||||
def _ignore(*args): pass
|
||||
|
||||
class FileWrapper:
|
||||
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
self.total = 0.0
|
||||
f.seek(0, 2) # seek to the end
|
||||
self.size = f.tell()
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.f, attr)
|
||||
|
||||
class StdioClient(basic.LineReceiver):
|
||||
|
||||
_pwd = pwd
|
||||
|
||||
ps = 'cftp> '
|
||||
delimiter = '\n'
|
||||
|
||||
reactor = reactor
|
||||
|
||||
def __init__(self, client, f = None):
|
||||
self.client = client
|
||||
self.currentDirectory = ''
|
||||
self.file = f
|
||||
self.useProgressBar = (not f and 1) or 0
|
||||
|
||||
def connectionMade(self):
|
||||
self.client.realPath('').addCallback(self._cbSetCurDir)
|
||||
|
||||
def _cbSetCurDir(self, path):
|
||||
self.currentDirectory = path
|
||||
self._newLine()
|
||||
|
||||
def lineReceived(self, line):
|
||||
if self.client.transport.localClosed:
|
||||
return
|
||||
log.msg('got line %s' % repr(line))
|
||||
line = line.lstrip()
|
||||
if not line:
|
||||
self._newLine()
|
||||
return
|
||||
if self.file and line.startswith('-'):
|
||||
self.ignoreErrors = 1
|
||||
line = line[1:]
|
||||
else:
|
||||
self.ignoreErrors = 0
|
||||
d = self._dispatchCommand(line)
|
||||
if d is not None:
|
||||
d.addCallback(self._cbCommand)
|
||||
d.addErrback(self._ebCommand)
|
||||
|
||||
|
||||
def _dispatchCommand(self, line):
|
||||
if ' ' in line:
|
||||
command, rest = line.split(' ', 1)
|
||||
rest = rest.lstrip()
|
||||
else:
|
||||
command, rest = line, ''
|
||||
if command.startswith('!'): # command
|
||||
f = self.cmd_EXEC
|
||||
rest = (command[1:] + ' ' + rest).strip()
|
||||
else:
|
||||
command = command.upper()
|
||||
log.msg('looking up cmd %s' % command)
|
||||
f = getattr(self, 'cmd_%s' % command, None)
|
||||
if f is not None:
|
||||
return defer.maybeDeferred(f, rest)
|
||||
else:
|
||||
self._ebCommand(failure.Failure(NotImplementedError(
|
||||
"No command called `%s'" % command)))
|
||||
self._newLine()
|
||||
|
||||
def _printFailure(self, f):
|
||||
log.msg(f)
|
||||
e = f.trap(NotImplementedError, filetransfer.SFTPError, OSError, IOError)
|
||||
if e == NotImplementedError:
|
||||
self.transport.write(self.cmd_HELP(''))
|
||||
elif e == filetransfer.SFTPError:
|
||||
self.transport.write("remote error %i: %s\n" %
|
||||
(f.value.code, f.value.message))
|
||||
elif e in (OSError, IOError):
|
||||
self.transport.write("local error %i: %s\n" %
|
||||
(f.value.errno, f.value.strerror))
|
||||
|
||||
def _newLine(self):
|
||||
if self.client.transport.localClosed:
|
||||
return
|
||||
self.transport.write(self.ps)
|
||||
self.ignoreErrors = 0
|
||||
if self.file:
|
||||
l = self.file.readline()
|
||||
if not l:
|
||||
self.client.transport.loseConnection()
|
||||
else:
|
||||
self.transport.write(l)
|
||||
self.lineReceived(l.strip())
|
||||
|
||||
def _cbCommand(self, result):
|
||||
if result is not None:
|
||||
self.transport.write(result)
|
||||
if not result.endswith('\n'):
|
||||
self.transport.write('\n')
|
||||
self._newLine()
|
||||
|
||||
def _ebCommand(self, f):
|
||||
self._printFailure(f)
|
||||
if self.file and not self.ignoreErrors:
|
||||
self.client.transport.loseConnection()
|
||||
self._newLine()
|
||||
|
||||
def cmd_CD(self, path):
|
||||
path, rest = self._getFilename(path)
|
||||
if not path.endswith('/'):
|
||||
path += '/'
|
||||
newPath = path and os.path.join(self.currentDirectory, path) or ''
|
||||
d = self.client.openDirectory(newPath)
|
||||
d.addCallback(self._cbCd)
|
||||
d.addErrback(self._ebCommand)
|
||||
return d
|
||||
|
||||
def _cbCd(self, directory):
|
||||
directory.close()
|
||||
d = self.client.realPath(directory.name)
|
||||
d.addCallback(self._cbCurDir)
|
||||
return d
|
||||
|
||||
def _cbCurDir(self, path):
|
||||
self.currentDirectory = path
|
||||
|
||||
def cmd_CHGRP(self, rest):
|
||||
grp, rest = rest.split(None, 1)
|
||||
path, rest = self._getFilename(rest)
|
||||
grp = int(grp)
|
||||
d = self.client.getAttrs(path)
|
||||
d.addCallback(self._cbSetUsrGrp, path, grp=grp)
|
||||
return d
|
||||
|
||||
def cmd_CHMOD(self, rest):
|
||||
mod, rest = rest.split(None, 1)
|
||||
path, rest = self._getFilename(rest)
|
||||
mod = int(mod, 8)
|
||||
d = self.client.setAttrs(path, {'permissions':mod})
|
||||
d.addCallback(_ignore)
|
||||
return d
|
||||
|
||||
def cmd_CHOWN(self, rest):
|
||||
usr, rest = rest.split(None, 1)
|
||||
path, rest = self._getFilename(rest)
|
||||
usr = int(usr)
|
||||
d = self.client.getAttrs(path)
|
||||
d.addCallback(self._cbSetUsrGrp, path, usr=usr)
|
||||
return d
|
||||
|
||||
def _cbSetUsrGrp(self, attrs, path, usr=None, grp=None):
|
||||
new = {}
|
||||
new['uid'] = (usr is not None) and usr or attrs['uid']
|
||||
new['gid'] = (grp is not None) and grp or attrs['gid']
|
||||
d = self.client.setAttrs(path, new)
|
||||
d.addCallback(_ignore)
|
||||
return d
|
||||
|
||||
def cmd_GET(self, rest):
|
||||
remote, rest = self._getFilename(rest)
|
||||
if '*' in remote or '?' in remote: # wildcard
|
||||
if rest:
|
||||
local, rest = self._getFilename(rest)
|
||||
if not os.path.isdir(local):
|
||||
return "Wildcard get with non-directory target."
|
||||
else:
|
||||
local = ''
|
||||
d = self._remoteGlob(remote)
|
||||
d.addCallback(self._cbGetMultiple, local)
|
||||
return d
|
||||
if rest:
|
||||
local, rest = self._getFilename(rest)
|
||||
else:
|
||||
local = os.path.split(remote)[1]
|
||||
log.msg((remote, local))
|
||||
lf = file(local, 'w', 0)
|
||||
path = os.path.join(self.currentDirectory, remote)
|
||||
d = self.client.openFile(path, filetransfer.FXF_READ, {})
|
||||
d.addCallback(self._cbGetOpenFile, lf)
|
||||
d.addErrback(self._ebCloseLf, lf)
|
||||
return d
|
||||
|
||||
def _cbGetMultiple(self, files, local):
|
||||
#if self._useProgressBar: # one at a time
|
||||
# XXX this can be optimized for times w/o progress bar
|
||||
return self._cbGetMultipleNext(None, files, local)
|
||||
|
||||
def _cbGetMultipleNext(self, res, files, local):
|
||||
if isinstance(res, failure.Failure):
|
||||
self._printFailure(res)
|
||||
elif res:
|
||||
self.transport.write(res)
|
||||
if not res.endswith('\n'):
|
||||
self.transport.write('\n')
|
||||
if not files:
|
||||
return
|
||||
f = files.pop(0)[0]
|
||||
lf = file(os.path.join(local, os.path.split(f)[1]), 'w', 0)
|
||||
path = os.path.join(self.currentDirectory, f)
|
||||
d = self.client.openFile(path, filetransfer.FXF_READ, {})
|
||||
d.addCallback(self._cbGetOpenFile, lf)
|
||||
d.addErrback(self._ebCloseLf, lf)
|
||||
d.addBoth(self._cbGetMultipleNext, files, local)
|
||||
return d
|
||||
|
||||
def _ebCloseLf(self, f, lf):
|
||||
lf.close()
|
||||
return f
|
||||
|
||||
def _cbGetOpenFile(self, rf, lf):
|
||||
return rf.getAttrs().addCallback(self._cbGetFileSize, rf, lf)
|
||||
|
||||
def _cbGetFileSize(self, attrs, rf, lf):
|
||||
if not stat.S_ISREG(attrs['permissions']):
|
||||
rf.close()
|
||||
lf.close()
|
||||
return "Can't get non-regular file: %s" % rf.name
|
||||
rf.size = attrs['size']
|
||||
bufferSize = self.client.transport.conn.options['buffersize']
|
||||
numRequests = self.client.transport.conn.options['requests']
|
||||
rf.total = 0.0
|
||||
dList = []
|
||||
chunks = []
|
||||
startTime = self.reactor.seconds()
|
||||
for i in range(numRequests):
|
||||
d = self._cbGetRead('', rf, lf, chunks, 0, bufferSize, startTime)
|
||||
dList.append(d)
|
||||
dl = defer.DeferredList(dList, fireOnOneErrback=1)
|
||||
dl.addCallback(self._cbGetDone, rf, lf)
|
||||
return dl
|
||||
|
||||
def _getNextChunk(self, chunks):
|
||||
end = 0
|
||||
for chunk in chunks:
|
||||
if end == 'eof':
|
||||
return # nothing more to get
|
||||
if end != chunk[0]:
|
||||
i = chunks.index(chunk)
|
||||
chunks.insert(i, (end, chunk[0]))
|
||||
return (end, chunk[0] - end)
|
||||
end = chunk[1]
|
||||
bufSize = int(self.client.transport.conn.options['buffersize'])
|
||||
chunks.append((end, end + bufSize))
|
||||
return (end, bufSize)
|
||||
|
||||
def _cbGetRead(self, data, rf, lf, chunks, start, size, startTime):
|
||||
if data and isinstance(data, failure.Failure):
|
||||
log.msg('get read err: %s' % data)
|
||||
reason = data
|
||||
reason.trap(EOFError)
|
||||
i = chunks.index((start, start + size))
|
||||
del chunks[i]
|
||||
chunks.insert(i, (start, 'eof'))
|
||||
elif data:
|
||||
log.msg('get read data: %i' % len(data))
|
||||
lf.seek(start)
|
||||
lf.write(data)
|
||||
if len(data) != size:
|
||||
log.msg('got less than we asked for: %i < %i' %
|
||||
(len(data), size))
|
||||
i = chunks.index((start, start + size))
|
||||
del chunks[i]
|
||||
chunks.insert(i, (start, start + len(data)))
|
||||
rf.total += len(data)
|
||||
if self.useProgressBar:
|
||||
self._printProgressBar(rf, startTime)
|
||||
chunk = self._getNextChunk(chunks)
|
||||
if not chunk:
|
||||
return
|
||||
else:
|
||||
start, length = chunk
|
||||
log.msg('asking for %i -> %i' % (start, start+length))
|
||||
d = rf.readChunk(start, length)
|
||||
d.addBoth(self._cbGetRead, rf, lf, chunks, start, length, startTime)
|
||||
return d
|
||||
|
||||
def _cbGetDone(self, ignored, rf, lf):
|
||||
log.msg('get done')
|
||||
rf.close()
|
||||
lf.close()
|
||||
if self.useProgressBar:
|
||||
self.transport.write('\n')
|
||||
return "Transferred %s to %s" % (rf.name, lf.name)
|
||||
|
||||
def cmd_PUT(self, rest):
|
||||
local, rest = self._getFilename(rest)
|
||||
if '*' in local or '?' in local: # wildcard
|
||||
if rest:
|
||||
remote, rest = self._getFilename(rest)
|
||||
path = os.path.join(self.currentDirectory, remote)
|
||||
d = self.client.getAttrs(path)
|
||||
d.addCallback(self._cbPutTargetAttrs, remote, local)
|
||||
return d
|
||||
else:
|
||||
remote = ''
|
||||
files = glob.glob(local)
|
||||
return self._cbPutMultipleNext(None, files, remote)
|
||||
if rest:
|
||||
remote, rest = self._getFilename(rest)
|
||||
else:
|
||||
remote = os.path.split(local)[1]
|
||||
lf = file(local, 'r')
|
||||
path = os.path.join(self.currentDirectory, remote)
|
||||
flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
|
||||
d = self.client.openFile(path, flags, {})
|
||||
d.addCallback(self._cbPutOpenFile, lf)
|
||||
d.addErrback(self._ebCloseLf, lf)
|
||||
return d
|
||||
|
||||
def _cbPutTargetAttrs(self, attrs, path, local):
|
||||
if not stat.S_ISDIR(attrs['permissions']):
|
||||
return "Wildcard put with non-directory target."
|
||||
return self._cbPutMultipleNext(None, files, path)
|
||||
|
||||
def _cbPutMultipleNext(self, res, files, path):
|
||||
if isinstance(res, failure.Failure):
|
||||
self._printFailure(res)
|
||||
elif res:
|
||||
self.transport.write(res)
|
||||
if not res.endswith('\n'):
|
||||
self.transport.write('\n')
|
||||
f = None
|
||||
while files and not f:
|
||||
try:
|
||||
f = files.pop(0)
|
||||
lf = file(f, 'r')
|
||||
except:
|
||||
self._printFailure(failure.Failure())
|
||||
f = None
|
||||
if not f:
|
||||
return
|
||||
name = os.path.split(f)[1]
|
||||
remote = os.path.join(self.currentDirectory, path, name)
|
||||
log.msg((name, remote, path))
|
||||
flags = filetransfer.FXF_WRITE|filetransfer.FXF_CREAT|filetransfer.FXF_TRUNC
|
||||
d = self.client.openFile(remote, flags, {})
|
||||
d.addCallback(self._cbPutOpenFile, lf)
|
||||
d.addErrback(self._ebCloseLf, lf)
|
||||
d.addBoth(self._cbPutMultipleNext, files, path)
|
||||
return d
|
||||
|
||||
def _cbPutOpenFile(self, rf, lf):
|
||||
numRequests = self.client.transport.conn.options['requests']
|
||||
if self.useProgressBar:
|
||||
lf = FileWrapper(lf)
|
||||
dList = []
|
||||
chunks = []
|
||||
startTime = self.reactor.seconds()
|
||||
for i in range(numRequests):
|
||||
d = self._cbPutWrite(None, rf, lf, chunks, startTime)
|
||||
if d:
|
||||
dList.append(d)
|
||||
dl = defer.DeferredList(dList, fireOnOneErrback=1)
|
||||
dl.addCallback(self._cbPutDone, rf, lf)
|
||||
return dl
|
||||
|
||||
def _cbPutWrite(self, ignored, rf, lf, chunks, startTime):
|
||||
chunk = self._getNextChunk(chunks)
|
||||
start, size = chunk
|
||||
lf.seek(start)
|
||||
data = lf.read(size)
|
||||
if self.useProgressBar:
|
||||
lf.total += len(data)
|
||||
self._printProgressBar(lf, startTime)
|
||||
if data:
|
||||
d = rf.writeChunk(start, data)
|
||||
d.addCallback(self._cbPutWrite, rf, lf, chunks, startTime)
|
||||
return d
|
||||
else:
|
||||
return
|
||||
|
||||
def _cbPutDone(self, ignored, rf, lf):
|
||||
lf.close()
|
||||
rf.close()
|
||||
if self.useProgressBar:
|
||||
self.transport.write('\n')
|
||||
return 'Transferred %s to %s' % (lf.name, rf.name)
|
||||
|
||||
def cmd_LCD(self, path):
|
||||
os.chdir(path)
|
||||
|
||||
def cmd_LN(self, rest):
|
||||
linkpath, rest = self._getFilename(rest)
|
||||
targetpath, rest = self._getFilename(rest)
|
||||
linkpath, targetpath = map(
|
||||
lambda x: os.path.join(self.currentDirectory, x),
|
||||
(linkpath, targetpath))
|
||||
return self.client.makeLink(linkpath, targetpath).addCallback(_ignore)
|
||||
|
||||
def cmd_LS(self, rest):
|
||||
# possible lines:
|
||||
# ls current directory
|
||||
# ls name_of_file that file
|
||||
# ls name_of_directory that directory
|
||||
# ls some_glob_string current directory, globbed for that string
|
||||
options = []
|
||||
rest = rest.split()
|
||||
while rest and rest[0] and rest[0][0] == '-':
|
||||
opts = rest.pop(0)[1:]
|
||||
for o in opts:
|
||||
if o == 'l':
|
||||
options.append('verbose')
|
||||
elif o == 'a':
|
||||
options.append('all')
|
||||
rest = ' '.join(rest)
|
||||
path, rest = self._getFilename(rest)
|
||||
if not path:
|
||||
fullPath = self.currentDirectory + '/'
|
||||
else:
|
||||
fullPath = os.path.join(self.currentDirectory, path)
|
||||
d = self._remoteGlob(fullPath)
|
||||
d.addCallback(self._cbDisplayFiles, options)
|
||||
return d
|
||||
|
||||
def _cbDisplayFiles(self, files, options):
|
||||
files.sort()
|
||||
if 'all' not in options:
|
||||
files = [f for f in files if not f[0].startswith('.')]
|
||||
if 'verbose' in options:
|
||||
lines = [f[1] for f in files]
|
||||
else:
|
||||
lines = [f[0] for f in files]
|
||||
if not lines:
|
||||
return None
|
||||
else:
|
||||
return '\n'.join(lines)
|
||||
|
||||
def cmd_MKDIR(self, path):
|
||||
path, rest = self._getFilename(path)
|
||||
path = os.path.join(self.currentDirectory, path)
|
||||
return self.client.makeDirectory(path, {}).addCallback(_ignore)
|
||||
|
||||
def cmd_RMDIR(self, path):
|
||||
path, rest = self._getFilename(path)
|
||||
path = os.path.join(self.currentDirectory, path)
|
||||
return self.client.removeDirectory(path).addCallback(_ignore)
|
||||
|
||||
def cmd_LMKDIR(self, path):
|
||||
os.system("mkdir %s" % path)
|
||||
|
||||
def cmd_RM(self, path):
|
||||
path, rest = self._getFilename(path)
|
||||
path = os.path.join(self.currentDirectory, path)
|
||||
return self.client.removeFile(path).addCallback(_ignore)
|
||||
|
||||
def cmd_LLS(self, rest):
|
||||
os.system("ls %s" % rest)
|
||||
|
||||
def cmd_RENAME(self, rest):
|
||||
oldpath, rest = self._getFilename(rest)
|
||||
newpath, rest = self._getFilename(rest)
|
||||
oldpath, newpath = map (
|
||||
lambda x: os.path.join(self.currentDirectory, x),
|
||||
(oldpath, newpath))
|
||||
return self.client.renameFile(oldpath, newpath).addCallback(_ignore)
|
||||
|
||||
def cmd_EXIT(self, ignored):
|
||||
self.client.transport.loseConnection()
|
||||
|
||||
cmd_QUIT = cmd_EXIT
|
||||
|
||||
def cmd_VERSION(self, ignored):
|
||||
return "SFTP version %i" % self.client.version
|
||||
|
||||
def cmd_HELP(self, ignored):
|
||||
return """Available commands:
|
||||
cd path Change remote directory to 'path'.
|
||||
chgrp gid path Change gid of 'path' to 'gid'.
|
||||
chmod mode path Change mode of 'path' to 'mode'.
|
||||
chown uid path Change uid of 'path' to 'uid'.
|
||||
exit Disconnect from the server.
|
||||
get remote-path [local-path] Get remote file.
|
||||
help Get a list of available commands.
|
||||
lcd path Change local directory to 'path'.
|
||||
lls [ls-options] [path] Display local directory listing.
|
||||
lmkdir path Create local directory.
|
||||
ln linkpath targetpath Symlink remote file.
|
||||
lpwd Print the local working directory.
|
||||
ls [-l] [path] Display remote directory listing.
|
||||
mkdir path Create remote directory.
|
||||
progress Toggle progress bar.
|
||||
put local-path [remote-path] Put local file.
|
||||
pwd Print the remote working directory.
|
||||
quit Disconnect from the server.
|
||||
rename oldpath newpath Rename remote file.
|
||||
rmdir path Remove remote directory.
|
||||
rm path Remove remote file.
|
||||
version Print the SFTP version.
|
||||
? Synonym for 'help'.
|
||||
"""
|
||||
|
||||
def cmd_PWD(self, ignored):
|
||||
return self.currentDirectory
|
||||
|
||||
def cmd_LPWD(self, ignored):
|
||||
return os.getcwd()
|
||||
|
||||
def cmd_PROGRESS(self, ignored):
|
||||
self.useProgressBar = not self.useProgressBar
|
||||
return "%ssing progess bar." % (self.useProgressBar and "U" or "Not u")
|
||||
|
||||
def cmd_EXEC(self, rest):
|
||||
"""
|
||||
Run C{rest} using the user's shell (or /bin/sh if they do not have
|
||||
one).
|
||||
"""
|
||||
shell = self._pwd.getpwnam(getpass.getuser())[6]
|
||||
if not shell:
|
||||
shell = '/bin/sh'
|
||||
if rest:
|
||||
cmds = ['-c', rest]
|
||||
return utils.getProcessOutput(shell, cmds, errortoo=1)
|
||||
else:
|
||||
os.system(shell)
|
||||
|
||||
# accessory functions
|
||||
|
||||
def _remoteGlob(self, fullPath):
|
||||
log.msg('looking up %s' % fullPath)
|
||||
head, tail = os.path.split(fullPath)
|
||||
if '*' in tail or '?' in tail:
|
||||
glob = 1
|
||||
else:
|
||||
glob = 0
|
||||
if tail and not glob: # could be file or directory
|
||||
# try directory first
|
||||
d = self.client.openDirectory(fullPath)
|
||||
d.addCallback(self._cbOpenList, '')
|
||||
d.addErrback(self._ebNotADirectory, head, tail)
|
||||
else:
|
||||
d = self.client.openDirectory(head)
|
||||
d.addCallback(self._cbOpenList, tail)
|
||||
return d
|
||||
|
||||
def _cbOpenList(self, directory, glob):
|
||||
files = []
|
||||
d = directory.read()
|
||||
d.addBoth(self._cbReadFile, files, directory, glob)
|
||||
return d
|
||||
|
||||
def _ebNotADirectory(self, reason, path, glob):
|
||||
d = self.client.openDirectory(path)
|
||||
d.addCallback(self._cbOpenList, glob)
|
||||
return d
|
||||
|
||||
def _cbReadFile(self, files, l, directory, glob):
|
||||
if not isinstance(files, failure.Failure):
|
||||
if glob:
|
||||
l.extend([f for f in files if fnmatch.fnmatch(f[0], glob)])
|
||||
else:
|
||||
l.extend(files)
|
||||
d = directory.read()
|
||||
d.addBoth(self._cbReadFile, l, directory, glob)
|
||||
return d
|
||||
else:
|
||||
reason = files
|
||||
reason.trap(EOFError)
|
||||
directory.close()
|
||||
return l
|
||||
|
||||
def _abbrevSize(self, size):
|
||||
# from http://mail.python.org/pipermail/python-list/1999-December/018395.html
|
||||
_abbrevs = [
|
||||
(1<<50L, 'PB'),
|
||||
(1<<40L, 'TB'),
|
||||
(1<<30L, 'GB'),
|
||||
(1<<20L, 'MB'),
|
||||
(1<<10L, 'kB'),
|
||||
(1, 'B')
|
||||
]
|
||||
|
||||
for factor, suffix in _abbrevs:
|
||||
if size > factor:
|
||||
break
|
||||
return '%.1f' % (size/factor) + suffix
|
||||
|
||||
def _abbrevTime(self, t):
|
||||
if t > 3600: # 1 hour
|
||||
hours = int(t / 3600)
|
||||
t -= (3600 * hours)
|
||||
mins = int(t / 60)
|
||||
t -= (60 * mins)
|
||||
return "%i:%02i:%02i" % (hours, mins, t)
|
||||
else:
|
||||
mins = int(t/60)
|
||||
t -= (60 * mins)
|
||||
return "%02i:%02i" % (mins, t)
|
||||
|
||||
|
||||
def _printProgressBar(self, f, startTime):
|
||||
"""
|
||||
Update a console progress bar on this L{StdioClient}'s transport, based
|
||||
on the difference between the start time of the operation and the
|
||||
current time according to the reactor, and appropriate to the size of
|
||||
the console window.
|
||||
|
||||
@param f: a wrapper around the file which is being written or read
|
||||
@type f: L{FileWrapper}
|
||||
|
||||
@param startTime: The time at which the operation being tracked began.
|
||||
@type startTime: C{float}
|
||||
"""
|
||||
diff = self.reactor.seconds() - startTime
|
||||
total = f.total
|
||||
try:
|
||||
winSize = struct.unpack('4H',
|
||||
fcntl.ioctl(0, tty.TIOCGWINSZ, '12345679'))
|
||||
except IOError:
|
||||
winSize = [None, 80]
|
||||
if diff == 0.0:
|
||||
speed = 0.0
|
||||
else:
|
||||
speed = total / diff
|
||||
if speed:
|
||||
timeLeft = (f.size - total) / speed
|
||||
else:
|
||||
timeLeft = 0
|
||||
front = f.name
|
||||
back = '%3i%% %s %sps %s ' % ((total / f.size) * 100,
|
||||
self._abbrevSize(total),
|
||||
self._abbrevSize(speed),
|
||||
self._abbrevTime(timeLeft))
|
||||
spaces = (winSize[1] - (len(front) + len(back) + 1)) * ' '
|
||||
self.transport.write('\r%s%s%s' % (front, spaces, back))
|
||||
|
||||
|
||||
def _getFilename(self, line):
|
||||
line.lstrip()
|
||||
if not line:
|
||||
return None, ''
|
||||
if line[0] in '\'"':
|
||||
ret = []
|
||||
line = list(line)
|
||||
try:
|
||||
for i in range(1,len(line)):
|
||||
c = line[i]
|
||||
if c == line[0]:
|
||||
return ''.join(ret), ''.join(line[i+1:]).lstrip()
|
||||
elif c == '\\': # quoted character
|
||||
del line[i]
|
||||
if line[i] not in '\'"\\':
|
||||
raise IndexError, "bad quote: \\%s" % line[i]
|
||||
ret.append(line[i])
|
||||
else:
|
||||
ret.append(line[i])
|
||||
except IndexError:
|
||||
raise IndexError, "unterminated quote"
|
||||
ret = line.split(None, 1)
|
||||
if len(ret) == 1:
|
||||
return ret[0], ''
|
||||
else:
|
||||
return ret
|
||||
|
||||
StdioClient.__dict__['cmd_?'] = StdioClient.cmd_HELP
|
||||
|
||||
class SSHConnection(connection.SSHConnection):
|
||||
def serviceStarted(self):
|
||||
self.openChannel(SSHSession())
|
||||
|
||||
class SSHSession(channel.SSHChannel):
|
||||
|
||||
name = 'session'
|
||||
|
||||
def channelOpen(self, foo):
|
||||
log.msg('session %s open' % self.id)
|
||||
if self.conn.options['subsystem'].startswith('/'):
|
||||
request = 'exec'
|
||||
else:
|
||||
request = 'subsystem'
|
||||
d = self.conn.sendRequest(self, request, \
|
||||
common.NS(self.conn.options['subsystem']), wantReply=1)
|
||||
d.addCallback(self._cbSubsystem)
|
||||
d.addErrback(_ebExit)
|
||||
|
||||
def _cbSubsystem(self, result):
|
||||
self.client = filetransfer.FileTransferClient()
|
||||
self.client.makeConnection(self)
|
||||
self.dataReceived = self.client.dataReceived
|
||||
f = None
|
||||
if self.conn.options['batchfile']:
|
||||
fn = self.conn.options['batchfile']
|
||||
if fn != '-':
|
||||
f = file(fn)
|
||||
self.stdio = stdio.StandardIO(StdioClient(self.client, f))
|
||||
|
||||
def extReceived(self, t, data):
|
||||
if t==connection.EXTENDED_DATA_STDERR:
|
||||
log.msg('got %s stderr data' % len(data))
|
||||
sys.stderr.write(data)
|
||||
sys.stderr.flush()
|
||||
|
||||
def eofReceived(self):
|
||||
log.msg('got eof')
|
||||
self.stdio.loseWriteConnection()
|
||||
|
||||
def closeReceived(self):
|
||||
log.msg('remote side closed %s' % self)
|
||||
self.conn.sendClose(self)
|
||||
|
||||
def closed(self):
|
||||
try:
|
||||
reactor.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
def stopWriting(self):
|
||||
self.stdio.pauseProducing()
|
||||
|
||||
def startWriting(self):
|
||||
self.stdio.resumeProducing()
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_ckeygen -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation module for the `ckeygen` command.
|
||||
"""
|
||||
|
||||
import sys, os, getpass, socket
|
||||
if getpass.getpass == getpass.unix_getpass:
|
||||
try:
|
||||
import termios # hack around broken termios
|
||||
termios.tcgetattr, termios.tcsetattr
|
||||
except (ImportError, AttributeError):
|
||||
sys.modules['termios'] = None
|
||||
reload(getpass)
|
||||
|
||||
from twisted.conch.ssh import keys
|
||||
from twisted.python import filepath, log, usage, randbytes
|
||||
|
||||
|
||||
|
||||
class GeneralOptions(usage.Options):
|
||||
synopsis = """Usage: ckeygen [options]
|
||||
"""
|
||||
|
||||
longdesc = "ckeygen manipulates public/private keys in various ways."
|
||||
|
||||
optParameters = [['bits', 'b', 1024, 'Number of bits in the key to create.'],
|
||||
['filename', 'f', None, 'Filename of the key file.'],
|
||||
['type', 't', None, 'Specify type of key to create.'],
|
||||
['comment', 'C', None, 'Provide new comment.'],
|
||||
['newpass', 'N', None, 'Provide new passphrase.'],
|
||||
['pass', 'P', None, 'Provide old passphrase.']]
|
||||
|
||||
optFlags = [['fingerprint', 'l', 'Show fingerprint of key file.'],
|
||||
['changepass', 'p', 'Change passphrase of private key file.'],
|
||||
['quiet', 'q', 'Quiet.'],
|
||||
['no-passphrase', None, "Create the key with no passphrase."],
|
||||
['showpub', 'y', 'Read private key file and print public key.']]
|
||||
|
||||
compData = usage.Completions(
|
||||
optActions={"type": usage.CompleteList(["rsa", "dsa"])})
|
||||
|
||||
|
||||
|
||||
def run():
|
||||
options = GeneralOptions()
|
||||
try:
|
||||
options.parseOptions(sys.argv[1:])
|
||||
except usage.UsageError, u:
|
||||
print 'ERROR: %s' % u
|
||||
options.opt_help()
|
||||
sys.exit(1)
|
||||
log.discardLogs()
|
||||
log.deferr = handleError # HACK
|
||||
if options['type']:
|
||||
if options['type'] == 'rsa':
|
||||
generateRSAkey(options)
|
||||
elif options['type'] == 'dsa':
|
||||
generateDSAkey(options)
|
||||
else:
|
||||
sys.exit('Key type was %s, must be one of: rsa, dsa' % options['type'])
|
||||
elif options['fingerprint']:
|
||||
printFingerprint(options)
|
||||
elif options['changepass']:
|
||||
changePassPhrase(options)
|
||||
elif options['showpub']:
|
||||
displayPublicKey(options)
|
||||
else:
|
||||
options.opt_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
def handleError():
|
||||
from twisted.python import failure
|
||||
global exitStatus
|
||||
exitStatus = 2
|
||||
log.err(failure.Failure())
|
||||
reactor.stop()
|
||||
raise
|
||||
|
||||
|
||||
|
||||
def generateRSAkey(options):
|
||||
from Crypto.PublicKey import RSA
|
||||
print 'Generating public/private rsa key pair.'
|
||||
key = RSA.generate(int(options['bits']), randbytes.secureRandom)
|
||||
_saveKey(key, options)
|
||||
|
||||
|
||||
|
||||
def generateDSAkey(options):
|
||||
from Crypto.PublicKey import DSA
|
||||
print 'Generating public/private dsa key pair.'
|
||||
key = DSA.generate(int(options['bits']), randbytes.secureRandom)
|
||||
_saveKey(key, options)
|
||||
|
||||
|
||||
|
||||
def printFingerprint(options):
|
||||
if not options['filename']:
|
||||
filename = os.path.expanduser('~/.ssh/id_rsa')
|
||||
options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
|
||||
if os.path.exists(options['filename']+'.pub'):
|
||||
options['filename'] += '.pub'
|
||||
try:
|
||||
key = keys.Key.fromFile(options['filename'])
|
||||
obj = key.keyObject
|
||||
string = key.blob()
|
||||
print '%s %s %s' % (
|
||||
obj.size() + 1,
|
||||
key.fingerprint(),
|
||||
os.path.basename(options['filename']))
|
||||
except:
|
||||
sys.exit('bad key')
|
||||
|
||||
|
||||
|
||||
def changePassPhrase(options):
|
||||
if not options['filename']:
|
||||
filename = os.path.expanduser('~/.ssh/id_rsa')
|
||||
options['filename'] = raw_input(
|
||||
'Enter file in which the key is (%s): ' % filename)
|
||||
try:
|
||||
key = keys.Key.fromFile(options['filename']).keyObject
|
||||
except keys.EncryptedKeyError as e:
|
||||
# Raised if password not supplied for an encrypted key
|
||||
if not options.get('pass'):
|
||||
options['pass'] = getpass.getpass('Enter old passphrase: ')
|
||||
try:
|
||||
key = keys.Key.fromFile(
|
||||
options['filename'], passphrase=options['pass']).keyObject
|
||||
except keys.BadKeyError:
|
||||
sys.exit('Could not change passphrase: old passphrase error')
|
||||
except keys.EncryptedKeyError as e:
|
||||
sys.exit('Could not change passphrase: %s' % (e,))
|
||||
except keys.BadKeyError as e:
|
||||
sys.exit('Could not change passphrase: %s' % (e,))
|
||||
|
||||
if not options.get('newpass'):
|
||||
while 1:
|
||||
p1 = getpass.getpass(
|
||||
'Enter new passphrase (empty for no passphrase): ')
|
||||
p2 = getpass.getpass('Enter same passphrase again: ')
|
||||
if p1 == p2:
|
||||
break
|
||||
print 'Passphrases do not match. Try again.'
|
||||
options['newpass'] = p1
|
||||
|
||||
try:
|
||||
newkeydata = keys.Key(key).toString('openssh',
|
||||
extra=options['newpass'])
|
||||
except Exception as e:
|
||||
sys.exit('Could not change passphrase: %s' % (e,))
|
||||
|
||||
try:
|
||||
keys.Key.fromString(newkeydata, passphrase=options['newpass'])
|
||||
except (keys.EncryptedKeyError, keys.BadKeyError) as e:
|
||||
sys.exit('Could not change passphrase: %s' % (e,))
|
||||
|
||||
fd = open(options['filename'], 'w')
|
||||
fd.write(newkeydata)
|
||||
fd.close()
|
||||
|
||||
print 'Your identification has been saved with the new passphrase.'
|
||||
|
||||
|
||||
|
||||
def displayPublicKey(options):
|
||||
if not options['filename']:
|
||||
filename = os.path.expanduser('~/.ssh/id_rsa')
|
||||
options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
|
||||
try:
|
||||
key = keys.Key.fromFile(options['filename']).keyObject
|
||||
except keys.EncryptedKeyError:
|
||||
if not options.get('pass'):
|
||||
options['pass'] = getpass.getpass('Enter passphrase: ')
|
||||
key = keys.Key.fromFile(
|
||||
options['filename'], passphrase = options['pass']).keyObject
|
||||
print keys.Key(key).public().toString('openssh')
|
||||
|
||||
|
||||
|
||||
def _saveKey(key, options):
|
||||
if not options['filename']:
|
||||
kind = keys.objectType(key)
|
||||
kind = {'ssh-rsa':'rsa','ssh-dss':'dsa'}[kind]
|
||||
filename = os.path.expanduser('~/.ssh/id_%s'%kind)
|
||||
options['filename'] = raw_input('Enter file in which to save the key (%s): '%filename).strip() or filename
|
||||
if os.path.exists(options['filename']):
|
||||
print '%s already exists.' % options['filename']
|
||||
yn = raw_input('Overwrite (y/n)? ')
|
||||
if yn[0].lower() != 'y':
|
||||
sys.exit()
|
||||
if options.get('no-passphrase'):
|
||||
options['pass'] = b''
|
||||
elif not options['pass']:
|
||||
while 1:
|
||||
p1 = getpass.getpass('Enter passphrase (empty for no passphrase): ')
|
||||
p2 = getpass.getpass('Enter same passphrase again: ')
|
||||
if p1 == p2:
|
||||
break
|
||||
print 'Passphrases do not match. Try again.'
|
||||
options['pass'] = p1
|
||||
|
||||
keyObj = keys.Key(key)
|
||||
comment = '%s@%s' % (getpass.getuser(), socket.gethostname())
|
||||
|
||||
filepath.FilePath(options['filename']).setContent(
|
||||
keyObj.toString('openssh', options['pass']))
|
||||
os.chmod(options['filename'], 33152)
|
||||
|
||||
filepath.FilePath(options['filename'] + '.pub').setContent(
|
||||
keyObj.public().toString('openssh', comment))
|
||||
|
||||
print 'Your identification has been saved in %s' % options['filename']
|
||||
print 'Your public key has been saved in %s.pub' % options['filename']
|
||||
print 'The key fingerprint is:'
|
||||
print keyObj.fingerprint()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_conch -*-
|
||||
#
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
#
|
||||
# $Id: conch.py,v 1.65 2004/03/11 00:29:14 z3p Exp $
|
||||
|
||||
#""" Implementation module for the `conch` command.
|
||||
#"""
|
||||
from twisted.conch.client import connect, default, options
|
||||
from twisted.conch.error import ConchError
|
||||
from twisted.conch.ssh import connection, common
|
||||
from twisted.conch.ssh import session, forwarding, channel
|
||||
from twisted.internet import reactor, stdio, task
|
||||
from twisted.python import log, usage
|
||||
|
||||
import os, sys, getpass, struct, tty, fcntl, signal
|
||||
|
||||
class ClientOptions(options.ConchOptions):
|
||||
|
||||
synopsis = """Usage: conch [options] host [command]
|
||||
"""
|
||||
longdesc = ("conch is a SSHv2 client that allows logging into a remote "
|
||||
"machine and executing commands.")
|
||||
|
||||
optParameters = [['escape', 'e', '~'],
|
||||
['localforward', 'L', None, 'listen-port:host:port Forward local port to remote address'],
|
||||
['remoteforward', 'R', None, 'listen-port:host:port Forward remote port to local address'],
|
||||
]
|
||||
|
||||
optFlags = [['null', 'n', 'Redirect input from /dev/null.'],
|
||||
['fork', 'f', 'Fork to background after authentication.'],
|
||||
['tty', 't', 'Tty; allocate a tty even if command is given.'],
|
||||
['notty', 'T', 'Do not allocate a tty.'],
|
||||
['noshell', 'N', 'Do not execute a shell or command.'],
|
||||
['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
|
||||
]
|
||||
|
||||
compData = usage.Completions(
|
||||
mutuallyExclusive=[("tty", "notty")],
|
||||
optActions={
|
||||
"localforward": usage.Completer(descr="listen-port:host:port"),
|
||||
"remoteforward": usage.Completer(descr="listen-port:host:port")},
|
||||
extraActions=[usage.CompleteUserAtHost(),
|
||||
usage.Completer(descr="command"),
|
||||
usage.Completer(descr="argument", repeat=True)]
|
||||
)
|
||||
|
||||
localForwards = []
|
||||
remoteForwards = []
|
||||
|
||||
def opt_escape(self, esc):
|
||||
"Set escape character; ``none'' = disable"
|
||||
if esc == 'none':
|
||||
self['escape'] = None
|
||||
elif esc[0] == '^' and len(esc) == 2:
|
||||
self['escape'] = chr(ord(esc[1])-64)
|
||||
elif len(esc) == 1:
|
||||
self['escape'] = esc
|
||||
else:
|
||||
sys.exit("Bad escape character '%s'." % esc)
|
||||
|
||||
def opt_localforward(self, f):
|
||||
"Forward local port to remote address (lport:host:port)"
|
||||
localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
|
||||
localPort = int(localPort)
|
||||
remotePort = int(remotePort)
|
||||
self.localForwards.append((localPort, (remoteHost, remotePort)))
|
||||
|
||||
def opt_remoteforward(self, f):
|
||||
"""Forward remote port to local address (rport:host:port)"""
|
||||
remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
|
||||
remotePort = int(remotePort)
|
||||
connPort = int(connPort)
|
||||
self.remoteForwards.append((remotePort, (connHost, connPort)))
|
||||
|
||||
def parseArgs(self, host, *command):
|
||||
self['host'] = host
|
||||
self['command'] = ' '.join(command)
|
||||
|
||||
# Rest of code in "run"
|
||||
options = None
|
||||
conn = None
|
||||
exitStatus = 0
|
||||
old = None
|
||||
_inRawMode = 0
|
||||
_savedRawMode = None
|
||||
|
||||
def run():
|
||||
global options, old
|
||||
args = sys.argv[1:]
|
||||
if '-l' in args: # cvs is an idiot
|
||||
i = args.index('-l')
|
||||
args = args[i:i+2]+args
|
||||
del args[i+2:i+4]
|
||||
for arg in args[:]:
|
||||
try:
|
||||
i = args.index(arg)
|
||||
if arg[:2] == '-o' and args[i+1][0]!='-':
|
||||
args[i:i+2] = [] # suck on it scp
|
||||
except ValueError:
|
||||
pass
|
||||
options = ClientOptions()
|
||||
try:
|
||||
options.parseOptions(args)
|
||||
except usage.UsageError, u:
|
||||
print 'ERROR: %s' % u
|
||||
options.opt_help()
|
||||
sys.exit(1)
|
||||
if options['log']:
|
||||
if options['logfile']:
|
||||
if options['logfile'] == '-':
|
||||
f = sys.stdout
|
||||
else:
|
||||
f = file(options['logfile'], 'a+')
|
||||
else:
|
||||
f = sys.stderr
|
||||
realout = sys.stdout
|
||||
log.startLogging(f)
|
||||
sys.stdout = realout
|
||||
else:
|
||||
log.discardLogs()
|
||||
doConnect()
|
||||
fd = sys.stdin.fileno()
|
||||
try:
|
||||
old = tty.tcgetattr(fd)
|
||||
except:
|
||||
old = None
|
||||
try:
|
||||
oldUSR1 = signal.signal(signal.SIGUSR1, lambda *a: reactor.callLater(0, reConnect))
|
||||
except:
|
||||
oldUSR1 = None
|
||||
try:
|
||||
reactor.run()
|
||||
finally:
|
||||
if old:
|
||||
tty.tcsetattr(fd, tty.TCSANOW, old)
|
||||
if oldUSR1:
|
||||
signal.signal(signal.SIGUSR1, oldUSR1)
|
||||
if (options['command'] and options['tty']) or not options['notty']:
|
||||
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
||||
if sys.stdout.isatty() and not options['command']:
|
||||
print 'Connection to %s closed.' % options['host']
|
||||
sys.exit(exitStatus)
|
||||
|
||||
def handleError():
|
||||
from twisted.python import failure
|
||||
global exitStatus
|
||||
exitStatus = 2
|
||||
reactor.callLater(0.01, _stopReactor)
|
||||
log.err(failure.Failure())
|
||||
raise
|
||||
|
||||
def _stopReactor():
|
||||
try:
|
||||
reactor.stop()
|
||||
except: pass
|
||||
|
||||
def doConnect():
|
||||
# log.deferr = handleError # HACK
|
||||
if '@' in options['host']:
|
||||
options['user'], options['host'] = options['host'].split('@',1)
|
||||
if not options.identitys:
|
||||
options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
|
||||
host = options['host']
|
||||
if not options['user']:
|
||||
options['user'] = getpass.getuser()
|
||||
if not options['port']:
|
||||
options['port'] = 22
|
||||
else:
|
||||
options['port'] = int(options['port'])
|
||||
host = options['host']
|
||||
port = options['port']
|
||||
vhk = default.verifyHostKey
|
||||
uao = default.SSHUserAuthClient(options['user'], options, SSHConnection())
|
||||
connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
|
||||
|
||||
def _ebExit(f):
|
||||
global exitStatus
|
||||
if hasattr(f.value, 'value'):
|
||||
s = f.value.value
|
||||
else:
|
||||
s = str(f)
|
||||
exitStatus = "conch: exiting with error %s" % f
|
||||
reactor.callLater(0.1, _stopReactor)
|
||||
|
||||
def onConnect():
|
||||
# if keyAgent and options['agent']:
|
||||
# cc = protocol.ClientCreator(reactor, SSHAgentForwardingLocal, conn)
|
||||
# cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
|
||||
if hasattr(conn.transport, 'sendIgnore'):
|
||||
_KeepAlive(conn)
|
||||
if options.localForwards:
|
||||
for localPort, hostport in options.localForwards:
|
||||
s = reactor.listenTCP(localPort,
|
||||
forwarding.SSHListenForwardingFactory(conn,
|
||||
hostport,
|
||||
SSHListenClientForwardingChannel))
|
||||
conn.localForwards.append(s)
|
||||
if options.remoteForwards:
|
||||
for remotePort, hostport in options.remoteForwards:
|
||||
log.msg('asking for remote forwarding for %s:%s' %
|
||||
(remotePort, hostport))
|
||||
conn.requestRemoteForwarding(remotePort, hostport)
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', beforeShutdown)
|
||||
if not options['noshell'] or options['agent']:
|
||||
conn.openChannel(SSHSession())
|
||||
if options['fork']:
|
||||
if os.fork():
|
||||
os._exit(0)
|
||||
os.setsid()
|
||||
for i in range(3):
|
||||
try:
|
||||
os.close(i)
|
||||
except OSError, e:
|
||||
import errno
|
||||
if e.errno != errno.EBADF:
|
||||
raise
|
||||
|
||||
def reConnect():
|
||||
beforeShutdown()
|
||||
conn.transport.transport.loseConnection()
|
||||
|
||||
def beforeShutdown():
|
||||
remoteForwards = options.remoteForwards
|
||||
for remotePort, hostport in remoteForwards:
|
||||
log.msg('cancelling %s:%s' % (remotePort, hostport))
|
||||
conn.cancelRemoteForwarding(remotePort)
|
||||
|
||||
def stopConnection():
|
||||
if not options['reconnect']:
|
||||
reactor.callLater(0.1, _stopReactor)
|
||||
|
||||
class _KeepAlive:
|
||||
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
self.globalTimeout = None
|
||||
self.lc = task.LoopingCall(self.sendGlobal)
|
||||
self.lc.start(300)
|
||||
|
||||
def sendGlobal(self):
|
||||
d = self.conn.sendGlobalRequest("conch-keep-alive@twistedmatrix.com",
|
||||
"", wantReply = 1)
|
||||
d.addBoth(self._cbGlobal)
|
||||
self.globalTimeout = reactor.callLater(30, self._ebGlobal)
|
||||
|
||||
def _cbGlobal(self, res):
|
||||
if self.globalTimeout:
|
||||
self.globalTimeout.cancel()
|
||||
self.globalTimeout = None
|
||||
|
||||
def _ebGlobal(self):
|
||||
if self.globalTimeout:
|
||||
self.globalTimeout = None
|
||||
self.conn.transport.loseConnection()
|
||||
|
||||
class SSHConnection(connection.SSHConnection):
|
||||
def serviceStarted(self):
|
||||
global conn
|
||||
conn = self
|
||||
self.localForwards = []
|
||||
self.remoteForwards = {}
|
||||
if not isinstance(self, connection.SSHConnection):
|
||||
# make these fall through
|
||||
del self.__class__.requestRemoteForwarding
|
||||
del self.__class__.cancelRemoteForwarding
|
||||
onConnect()
|
||||
|
||||
def serviceStopped(self):
|
||||
lf = self.localForwards
|
||||
self.localForwards = []
|
||||
for s in lf:
|
||||
s.loseConnection()
|
||||
stopConnection()
|
||||
|
||||
def requestRemoteForwarding(self, remotePort, hostport):
|
||||
data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
|
||||
d = self.sendGlobalRequest('tcpip-forward', data,
|
||||
wantReply=1)
|
||||
log.msg('requesting remote forwarding %s:%s' %(remotePort, hostport))
|
||||
d.addCallback(self._cbRemoteForwarding, remotePort, hostport)
|
||||
d.addErrback(self._ebRemoteForwarding, remotePort, hostport)
|
||||
|
||||
def _cbRemoteForwarding(self, result, remotePort, hostport):
|
||||
log.msg('accepted remote forwarding %s:%s' % (remotePort, hostport))
|
||||
self.remoteForwards[remotePort] = hostport
|
||||
log.msg(repr(self.remoteForwards))
|
||||
|
||||
def _ebRemoteForwarding(self, f, remotePort, hostport):
|
||||
log.msg('remote forwarding %s:%s failed' % (remotePort, hostport))
|
||||
log.msg(f)
|
||||
|
||||
def cancelRemoteForwarding(self, remotePort):
|
||||
data = forwarding.packGlobal_tcpip_forward(('0.0.0.0', remotePort))
|
||||
self.sendGlobalRequest('cancel-tcpip-forward', data)
|
||||
log.msg('cancelling remote forwarding %s' % remotePort)
|
||||
try:
|
||||
del self.remoteForwards[remotePort]
|
||||
except:
|
||||
pass
|
||||
log.msg(repr(self.remoteForwards))
|
||||
|
||||
def channel_forwarded_tcpip(self, windowSize, maxPacket, data):
|
||||
log.msg('%s %s' % ('FTCP', repr(data)))
|
||||
remoteHP, origHP = forwarding.unpackOpen_forwarded_tcpip(data)
|
||||
log.msg(self.remoteForwards)
|
||||
log.msg(remoteHP)
|
||||
if self.remoteForwards.has_key(remoteHP[1]):
|
||||
connectHP = self.remoteForwards[remoteHP[1]]
|
||||
log.msg('connect forwarding %s' % (connectHP,))
|
||||
return SSHConnectForwardingChannel(connectHP,
|
||||
remoteWindow = windowSize,
|
||||
remoteMaxPacket = maxPacket,
|
||||
conn = self)
|
||||
else:
|
||||
raise ConchError(connection.OPEN_CONNECT_FAILED, "don't know about that port")
|
||||
|
||||
# def channel_auth_agent_openssh_com(self, windowSize, maxPacket, data):
|
||||
# if options['agent'] and keyAgent:
|
||||
# return agent.SSHAgentForwardingChannel(remoteWindow = windowSize,
|
||||
# remoteMaxPacket = maxPacket,
|
||||
# conn = self)
|
||||
# else:
|
||||
# return connection.OPEN_CONNECT_FAILED, "don't have an agent"
|
||||
|
||||
def channelClosed(self, channel):
|
||||
log.msg('connection closing %s' % channel)
|
||||
log.msg(self.channels)
|
||||
if len(self.channels) == 1: # just us left
|
||||
log.msg('stopping connection')
|
||||
stopConnection()
|
||||
else:
|
||||
# because of the unix thing
|
||||
self.__class__.__bases__[0].channelClosed(self, channel)
|
||||
|
||||
class SSHSession(channel.SSHChannel):
|
||||
|
||||
name = 'session'
|
||||
|
||||
def channelOpen(self, foo):
|
||||
log.msg('session %s open' % self.id)
|
||||
if options['agent']:
|
||||
d = self.conn.sendRequest(self, 'auth-agent-req@openssh.com', '', wantReply=1)
|
||||
d.addBoth(lambda x:log.msg(x))
|
||||
if options['noshell']: return
|
||||
if (options['command'] and options['tty']) or not options['notty']:
|
||||
_enterRawMode()
|
||||
c = session.SSHSessionClient()
|
||||
if options['escape'] and not options['notty']:
|
||||
self.escapeMode = 1
|
||||
c.dataReceived = self.handleInput
|
||||
else:
|
||||
c.dataReceived = self.write
|
||||
c.connectionLost = lambda x=None,s=self:s.sendEOF()
|
||||
self.stdio = stdio.StandardIO(c)
|
||||
fd = 0
|
||||
if options['subsystem']:
|
||||
self.conn.sendRequest(self, 'subsystem', \
|
||||
common.NS(options['command']))
|
||||
elif options['command']:
|
||||
if options['tty']:
|
||||
term = os.environ['TERM']
|
||||
winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
|
||||
winSize = struct.unpack('4H', winsz)
|
||||
ptyReqData = session.packRequest_pty_req(term, winSize, '')
|
||||
self.conn.sendRequest(self, 'pty-req', ptyReqData)
|
||||
signal.signal(signal.SIGWINCH, self._windowResized)
|
||||
self.conn.sendRequest(self, 'exec', \
|
||||
common.NS(options['command']))
|
||||
else:
|
||||
if not options['notty']:
|
||||
term = os.environ['TERM']
|
||||
winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
|
||||
winSize = struct.unpack('4H', winsz)
|
||||
ptyReqData = session.packRequest_pty_req(term, winSize, '')
|
||||
self.conn.sendRequest(self, 'pty-req', ptyReqData)
|
||||
signal.signal(signal.SIGWINCH, self._windowResized)
|
||||
self.conn.sendRequest(self, 'shell', '')
|
||||
#if hasattr(conn.transport, 'transport'):
|
||||
# conn.transport.transport.setTcpNoDelay(1)
|
||||
|
||||
def handleInput(self, char):
|
||||
#log.msg('handling %s' % repr(char))
|
||||
if char in ('\n', '\r'):
|
||||
self.escapeMode = 1
|
||||
self.write(char)
|
||||
elif self.escapeMode == 1 and char == options['escape']:
|
||||
self.escapeMode = 2
|
||||
elif self.escapeMode == 2:
|
||||
self.escapeMode = 1 # so we can chain escapes together
|
||||
if char == '.': # disconnect
|
||||
log.msg('disconnecting from escape')
|
||||
stopConnection()
|
||||
return
|
||||
elif char == '\x1a': # ^Z, suspend
|
||||
def _():
|
||||
_leaveRawMode()
|
||||
sys.stdout.flush()
|
||||
sys.stdin.flush()
|
||||
os.kill(os.getpid(), signal.SIGTSTP)
|
||||
_enterRawMode()
|
||||
reactor.callLater(0, _)
|
||||
return
|
||||
elif char == 'R': # rekey connection
|
||||
log.msg('rekeying connection')
|
||||
self.conn.transport.sendKexInit()
|
||||
return
|
||||
elif char == '#': # display connections
|
||||
self.stdio.write('\r\nThe following connections are open:\r\n')
|
||||
channels = self.conn.channels.keys()
|
||||
channels.sort()
|
||||
for channelId in channels:
|
||||
self.stdio.write(' #%i %s\r\n' % (channelId, str(self.conn.channels[channelId])))
|
||||
return
|
||||
self.write('~' + char)
|
||||
else:
|
||||
self.escapeMode = 0
|
||||
self.write(char)
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.stdio.write(data)
|
||||
|
||||
def extReceived(self, t, data):
|
||||
if t==connection.EXTENDED_DATA_STDERR:
|
||||
log.msg('got %s stderr data' % len(data))
|
||||
sys.stderr.write(data)
|
||||
|
||||
def eofReceived(self):
|
||||
log.msg('got eof')
|
||||
self.stdio.loseWriteConnection()
|
||||
|
||||
def closeReceived(self):
|
||||
log.msg('remote side closed %s' % self)
|
||||
self.conn.sendClose(self)
|
||||
|
||||
def closed(self):
|
||||
global old
|
||||
log.msg('closed %s' % self)
|
||||
log.msg(repr(self.conn.channels))
|
||||
|
||||
def request_exit_status(self, data):
|
||||
global exitStatus
|
||||
exitStatus = int(struct.unpack('>L', data)[0])
|
||||
log.msg('exit status: %s' % exitStatus)
|
||||
|
||||
def sendEOF(self):
|
||||
self.conn.sendEOF(self)
|
||||
|
||||
def stopWriting(self):
|
||||
self.stdio.pauseProducing()
|
||||
|
||||
def startWriting(self):
|
||||
self.stdio.resumeProducing()
|
||||
|
||||
def _windowResized(self, *args):
|
||||
winsz = fcntl.ioctl(0, tty.TIOCGWINSZ, '12345678')
|
||||
winSize = struct.unpack('4H', winsz)
|
||||
newSize = winSize[1], winSize[0], winSize[2], winSize[3]
|
||||
self.conn.sendRequest(self, 'window-change', struct.pack('!4L', *newSize))
|
||||
|
||||
|
||||
class SSHListenClientForwardingChannel(forwarding.SSHListenClientForwardingChannel): pass
|
||||
class SSHConnectForwardingChannel(forwarding.SSHConnectForwardingChannel): pass
|
||||
|
||||
def _leaveRawMode():
|
||||
global _inRawMode
|
||||
if not _inRawMode:
|
||||
return
|
||||
fd = sys.stdin.fileno()
|
||||
tty.tcsetattr(fd, tty.TCSANOW, _savedMode)
|
||||
_inRawMode = 0
|
||||
|
||||
def _enterRawMode():
|
||||
global _inRawMode, _savedMode
|
||||
if _inRawMode:
|
||||
return
|
||||
fd = sys.stdin.fileno()
|
||||
try:
|
||||
old = tty.tcgetattr(fd)
|
||||
new = old[:]
|
||||
except:
|
||||
log.msg('not a typewriter!')
|
||||
else:
|
||||
# iflage
|
||||
new[0] = new[0] | tty.IGNPAR
|
||||
new[0] = new[0] & ~(tty.ISTRIP | tty.INLCR | tty.IGNCR | tty.ICRNL |
|
||||
tty.IXON | tty.IXANY | tty.IXOFF)
|
||||
if hasattr(tty, 'IUCLC'):
|
||||
new[0] = new[0] & ~tty.IUCLC
|
||||
|
||||
# lflag
|
||||
new[3] = new[3] & ~(tty.ISIG | tty.ICANON | tty.ECHO | tty.ECHO |
|
||||
tty.ECHOE | tty.ECHOK | tty.ECHONL)
|
||||
if hasattr(tty, 'IEXTEN'):
|
||||
new[3] = new[3] & ~tty.IEXTEN
|
||||
|
||||
#oflag
|
||||
new[1] = new[1] & ~tty.OPOST
|
||||
|
||||
new[6][tty.VMIN] = 1
|
||||
new[6][tty.VTIME] = 0
|
||||
|
||||
_savedMode = old
|
||||
tty.tcsetattr(fd, tty.TCSANOW, new)
|
||||
#tty.setraw(fd)
|
||||
_inRawMode = 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
|
||||
|
|
@ -0,0 +1,572 @@
|
|||
# -*- test-case-name: twisted.conch.test.test_scripts -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation module for the `tkconch` command.
|
||||
"""
|
||||
|
||||
import Tkinter, tkFileDialog, tkFont, tkMessageBox, string
|
||||
from twisted.conch.ui import tkvt100
|
||||
from twisted.conch.ssh import transport, userauth, connection, common, keys
|
||||
from twisted.conch.ssh import session, forwarding, channel
|
||||
from twisted.conch.client.default import isInKnownHosts
|
||||
from twisted.internet import reactor, defer, protocol, tksupport
|
||||
from twisted.python import usage, log
|
||||
|
||||
import os, sys, getpass, struct, base64, signal
|
||||
|
||||
class TkConchMenu(Tkinter.Frame):
|
||||
def __init__(self, *args, **params):
|
||||
## Standard heading: initialization
|
||||
apply(Tkinter.Frame.__init__, (self,) + args, params)
|
||||
|
||||
self.master.title('TkConch')
|
||||
self.localRemoteVar = Tkinter.StringVar()
|
||||
self.localRemoteVar.set('local')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Hostname').grid(column=1, row=1, sticky='w')
|
||||
self.host = Tkinter.Entry(self)
|
||||
self.host.grid(column=2, columnspan=2, row=1, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Port').grid(column=1, row=2, sticky='w')
|
||||
self.port = Tkinter.Entry(self)
|
||||
self.port.grid(column=2, columnspan=2, row=2, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Username').grid(column=1, row=3, sticky='w')
|
||||
self.user = Tkinter.Entry(self)
|
||||
self.user.grid(column=2, columnspan=2, row=3, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Command').grid(column=1, row=4, sticky='w')
|
||||
self.command = Tkinter.Entry(self)
|
||||
self.command.grid(column=2, columnspan=2, row=4, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Identity').grid(column=1, row=5, sticky='w')
|
||||
self.identity = Tkinter.Entry(self)
|
||||
self.identity.grid(column=2, row=5, sticky='nesw')
|
||||
Tkinter.Button(self, command=self.getIdentityFile, text='Browse').grid(column=3, row=5, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, text='Port Forwarding').grid(column=1, row=6, sticky='w')
|
||||
self.forwards = Tkinter.Listbox(self, height=0, width=0)
|
||||
self.forwards.grid(column=2, columnspan=2, row=6, sticky='nesw')
|
||||
Tkinter.Button(self, text='Add', command=self.addForward).grid(column=1, row=7)
|
||||
Tkinter.Button(self, text='Remove', command=self.removeForward).grid(column=1, row=8)
|
||||
self.forwardPort = Tkinter.Entry(self)
|
||||
self.forwardPort.grid(column=2, row=7, sticky='nesw')
|
||||
Tkinter.Label(self, text='Port').grid(column=3, row=7, sticky='nesw')
|
||||
self.forwardHost = Tkinter.Entry(self)
|
||||
self.forwardHost.grid(column=2, row=8, sticky='nesw')
|
||||
Tkinter.Label(self, text='Host').grid(column=3, row=8, sticky='nesw')
|
||||
self.localForward = Tkinter.Radiobutton(self, text='Local', variable=self.localRemoteVar, value='local')
|
||||
self.localForward.grid(column=2, row=9)
|
||||
self.remoteForward = Tkinter.Radiobutton(self, text='Remote', variable=self.localRemoteVar, value='remote')
|
||||
self.remoteForward.grid(column=3, row=9)
|
||||
|
||||
Tkinter.Label(self, text='Advanced Options').grid(column=1, columnspan=3, row=10, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Cipher').grid(column=1, row=11, sticky='w')
|
||||
self.cipher = Tkinter.Entry(self, name='cipher')
|
||||
self.cipher.grid(column=2, columnspan=2, row=11, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='MAC').grid(column=1, row=12, sticky='w')
|
||||
self.mac = Tkinter.Entry(self, name='mac')
|
||||
self.mac.grid(column=2, columnspan=2, row=12, sticky='nesw')
|
||||
|
||||
Tkinter.Label(self, anchor='w', justify='left', text='Escape Char').grid(column=1, row=13, sticky='w')
|
||||
self.escape = Tkinter.Entry(self, name='escape')
|
||||
self.escape.grid(column=2, columnspan=2, row=13, sticky='nesw')
|
||||
Tkinter.Button(self, text='Connect!', command=self.doConnect).grid(column=1, columnspan=3, row=14, sticky='nesw')
|
||||
|
||||
# Resize behavior(s)
|
||||
self.grid_rowconfigure(6, weight=1, minsize=64)
|
||||
self.grid_columnconfigure(2, weight=1, minsize=2)
|
||||
|
||||
self.master.protocol("WM_DELETE_WINDOW", sys.exit)
|
||||
|
||||
|
||||
def getIdentityFile(self):
|
||||
r = tkFileDialog.askopenfilename()
|
||||
if r:
|
||||
self.identity.delete(0, Tkinter.END)
|
||||
self.identity.insert(Tkinter.END, r)
|
||||
|
||||
def addForward(self):
|
||||
port = self.forwardPort.get()
|
||||
self.forwardPort.delete(0, Tkinter.END)
|
||||
host = self.forwardHost.get()
|
||||
self.forwardHost.delete(0, Tkinter.END)
|
||||
if self.localRemoteVar.get() == 'local':
|
||||
self.forwards.insert(Tkinter.END, 'L:%s:%s' % (port, host))
|
||||
else:
|
||||
self.forwards.insert(Tkinter.END, 'R:%s:%s' % (port, host))
|
||||
|
||||
def removeForward(self):
|
||||
cur = self.forwards.curselection()
|
||||
if cur:
|
||||
self.forwards.remove(cur[0])
|
||||
|
||||
def doConnect(self):
|
||||
finished = 1
|
||||
options['host'] = self.host.get()
|
||||
options['port'] = self.port.get()
|
||||
options['user'] = self.user.get()
|
||||
options['command'] = self.command.get()
|
||||
cipher = self.cipher.get()
|
||||
mac = self.mac.get()
|
||||
escape = self.escape.get()
|
||||
if cipher:
|
||||
if cipher in SSHClientTransport.supportedCiphers:
|
||||
SSHClientTransport.supportedCiphers = [cipher]
|
||||
else:
|
||||
tkMessageBox.showerror('TkConch', 'Bad cipher.')
|
||||
finished = 0
|
||||
|
||||
if mac:
|
||||
if mac in SSHClientTransport.supportedMACs:
|
||||
SSHClientTransport.supportedMACs = [mac]
|
||||
elif finished:
|
||||
tkMessageBox.showerror('TkConch', 'Bad MAC.')
|
||||
finished = 0
|
||||
|
||||
if escape:
|
||||
if escape == 'none':
|
||||
options['escape'] = None
|
||||
elif escape[0] == '^' and len(escape) == 2:
|
||||
options['escape'] = chr(ord(escape[1])-64)
|
||||
elif len(escape) == 1:
|
||||
options['escape'] = escape
|
||||
elif finished:
|
||||
tkMessageBox.showerror('TkConch', "Bad escape character '%s'." % escape)
|
||||
finished = 0
|
||||
|
||||
if self.identity.get():
|
||||
options.identitys.append(self.identity.get())
|
||||
|
||||
for line in self.forwards.get(0,Tkinter.END):
|
||||
if line[0]=='L':
|
||||
options.opt_localforward(line[2:])
|
||||
else:
|
||||
options.opt_remoteforward(line[2:])
|
||||
|
||||
if '@' in options['host']:
|
||||
options['user'], options['host'] = options['host'].split('@',1)
|
||||
|
||||
if (not options['host'] or not options['user']) and finished:
|
||||
tkMessageBox.showerror('TkConch', 'Missing host or username.')
|
||||
finished = 0
|
||||
if finished:
|
||||
self.master.quit()
|
||||
self.master.destroy()
|
||||
if options['log']:
|
||||
realout = sys.stdout
|
||||
log.startLogging(sys.stderr)
|
||||
sys.stdout = realout
|
||||
else:
|
||||
log.discardLogs()
|
||||
log.deferr = handleError # HACK
|
||||
if not options.identitys:
|
||||
options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
|
||||
host = options['host']
|
||||
port = int(options['port'] or 22)
|
||||
log.msg((host,port))
|
||||
reactor.connectTCP(host, port, SSHClientFactory())
|
||||
frame.master.deiconify()
|
||||
frame.master.title('%s@%s - TkConch' % (options['user'], options['host']))
|
||||
else:
|
||||
self.focus()
|
||||
|
||||
class GeneralOptions(usage.Options):
|
||||
synopsis = """Usage: tkconch [options] host [command]
|
||||
"""
|
||||
|
||||
optParameters = [['user', 'l', None, 'Log in using this user name.'],
|
||||
['identity', 'i', '~/.ssh/identity', 'Identity for public key authentication'],
|
||||
['escape', 'e', '~', "Set escape character; ``none'' = disable"],
|
||||
['cipher', 'c', None, 'Select encryption algorithm.'],
|
||||
['macs', 'm', None, 'Specify MAC algorithms for protocol version 2.'],
|
||||
['port', 'p', None, 'Connect to this port. Server must be on the same port.'],
|
||||
['localforward', 'L', None, 'listen-port:host:port Forward local port to remote address'],
|
||||
['remoteforward', 'R', None, 'listen-port:host:port Forward remote port to local address'],
|
||||
]
|
||||
|
||||
optFlags = [['tty', 't', 'Tty; allocate a tty even if command is given.'],
|
||||
['notty', 'T', 'Do not allocate a tty.'],
|
||||
['version', 'V', 'Display version number only.'],
|
||||
['compress', 'C', 'Enable compression.'],
|
||||
['noshell', 'N', 'Do not execute a shell or command.'],
|
||||
['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem.'],
|
||||
['log', 'v', 'Log to stderr'],
|
||||
['ansilog', 'a', 'Print the receieved data to stdout']]
|
||||
|
||||
_ciphers = transport.SSHClientTransport.supportedCiphers
|
||||
_macs = transport.SSHClientTransport.supportedMACs
|
||||
|
||||
compData = usage.Completions(
|
||||
mutuallyExclusive=[("tty", "notty")],
|
||||
optActions={
|
||||
"cipher": usage.CompleteList(_ciphers),
|
||||
"macs": usage.CompleteList(_macs),
|
||||
"localforward": usage.Completer(descr="listen-port:host:port"),
|
||||
"remoteforward": usage.Completer(descr="listen-port:host:port")},
|
||||
extraActions=[usage.CompleteUserAtHost(),
|
||||
usage.Completer(descr="command"),
|
||||
usage.Completer(descr="argument", repeat=True)]
|
||||
)
|
||||
|
||||
identitys = []
|
||||
localForwards = []
|
||||
remoteForwards = []
|
||||
|
||||
def opt_identity(self, i):
|
||||
self.identitys.append(i)
|
||||
|
||||
def opt_localforward(self, f):
|
||||
localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
|
||||
localPort = int(localPort)
|
||||
remotePort = int(remotePort)
|
||||
self.localForwards.append((localPort, (remoteHost, remotePort)))
|
||||
|
||||
def opt_remoteforward(self, f):
|
||||
remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
|
||||
remotePort = int(remotePort)
|
||||
connPort = int(connPort)
|
||||
self.remoteForwards.append((remotePort, (connHost, connPort)))
|
||||
|
||||
def opt_compress(self):
|
||||
SSHClientTransport.supportedCompressions[0:1] = ['zlib']
|
||||
|
||||
def parseArgs(self, *args):
|
||||
if args:
|
||||
self['host'] = args[0]
|
||||
self['command'] = ' '.join(args[1:])
|
||||
else:
|
||||
self['host'] = ''
|
||||
self['command'] = ''
|
||||
|
||||
# Rest of code in "run"
|
||||
options = None
|
||||
menu = None
|
||||
exitStatus = 0
|
||||
frame = None
|
||||
|
||||
def deferredAskFrame(question, echo):
|
||||
if frame.callback:
|
||||
raise ValueError("can't ask 2 questions at once!")
|
||||
d = defer.Deferred()
|
||||
resp = []
|
||||
def gotChar(ch, resp=resp):
|
||||
if not ch: return
|
||||
if ch=='\x03': # C-c
|
||||
reactor.stop()
|
||||
if ch=='\r':
|
||||
frame.write('\r\n')
|
||||
stresp = ''.join(resp)
|
||||
del resp
|
||||
frame.callback = None
|
||||
d.callback(stresp)
|
||||
return
|
||||
elif 32 <= ord(ch) < 127:
|
||||
resp.append(ch)
|
||||
if echo:
|
||||
frame.write(ch)
|
||||
elif ord(ch) == 8 and resp: # BS
|
||||
if echo: frame.write('\x08 \x08')
|
||||
resp.pop()
|
||||
frame.callback = gotChar
|
||||
frame.write(question)
|
||||
frame.canvas.focus_force()
|
||||
return d
|
||||
|
||||
def run():
|
||||
global menu, options, frame
|
||||
args = sys.argv[1:]
|
||||
if '-l' in args: # cvs is an idiot
|
||||
i = args.index('-l')
|
||||
args = args[i:i+2]+args
|
||||
del args[i+2:i+4]
|
||||
for arg in args[:]:
|
||||
try:
|
||||
i = args.index(arg)
|
||||
if arg[:2] == '-o' and args[i+1][0]!='-':
|
||||
args[i:i+2] = [] # suck on it scp
|
||||
except ValueError:
|
||||
pass
|
||||
root = Tkinter.Tk()
|
||||
root.withdraw()
|
||||
top = Tkinter.Toplevel()
|
||||
menu = TkConchMenu(top)
|
||||
menu.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
|
||||
options = GeneralOptions()
|
||||
try:
|
||||
options.parseOptions(args)
|
||||
except usage.UsageError, u:
|
||||
print 'ERROR: %s' % u
|
||||
options.opt_help()
|
||||
sys.exit(1)
|
||||
for k,v in options.items():
|
||||
if v and hasattr(menu, k):
|
||||
getattr(menu,k).insert(Tkinter.END, v)
|
||||
for (p, (rh, rp)) in options.localForwards:
|
||||
menu.forwards.insert(Tkinter.END, 'L:%s:%s:%s' % (p, rh, rp))
|
||||
options.localForwards = []
|
||||
for (p, (rh, rp)) in options.remoteForwards:
|
||||
menu.forwards.insert(Tkinter.END, 'R:%s:%s:%s' % (p, rh, rp))
|
||||
options.remoteForwards = []
|
||||
frame = tkvt100.VT100Frame(root, callback=None)
|
||||
root.geometry('%dx%d'%(tkvt100.fontWidth*frame.width+3, tkvt100.fontHeight*frame.height+3))
|
||||
frame.pack(side = Tkinter.TOP)
|
||||
tksupport.install(root)
|
||||
root.withdraw()
|
||||
if (options['host'] and options['user']) or '@' in options['host']:
|
||||
menu.doConnect()
|
||||
else:
|
||||
top.mainloop()
|
||||
reactor.run()
|
||||
sys.exit(exitStatus)
|
||||
|
||||
def handleError():
|
||||
from twisted.python import failure
|
||||
global exitStatus
|
||||
exitStatus = 2
|
||||
log.err(failure.Failure())
|
||||
reactor.stop()
|
||||
raise
|
||||
|
||||
class SSHClientFactory(protocol.ClientFactory):
|
||||
noisy = 1
|
||||
|
||||
def stopFactory(self):
|
||||
reactor.stop()
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return SSHClientTransport()
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
tkMessageBox.showwarning('TkConch','Connection Failed, Reason:\n %s: %s' % (reason.type, reason.value))
|
||||
|
||||
class SSHClientTransport(transport.SSHClientTransport):
|
||||
|
||||
def receiveError(self, code, desc):
|
||||
global exitStatus
|
||||
exitStatus = 'conch:\tRemote side disconnected with error code %i\nconch:\treason: %s' % (code, desc)
|
||||
|
||||
def sendDisconnect(self, code, reason):
|
||||
global exitStatus
|
||||
exitStatus = 'conch:\tSending disconnect with error code %i\nconch:\treason: %s' % (code, reason)
|
||||
transport.SSHClientTransport.sendDisconnect(self, code, reason)
|
||||
|
||||
def receiveDebug(self, alwaysDisplay, message, lang):
|
||||
global options
|
||||
if alwaysDisplay or options['log']:
|
||||
log.msg('Received Debug Message: %s' % message)
|
||||
|
||||
def verifyHostKey(self, pubKey, fingerprint):
|
||||
#d = defer.Deferred()
|
||||
#d.addCallback(lambda x:defer.succeed(1))
|
||||
#d.callback(2)
|
||||
#return d
|
||||
goodKey = isInKnownHosts(options['host'], pubKey, {'known-hosts': None})
|
||||
if goodKey == 1: # good key
|
||||
return defer.succeed(1)
|
||||
elif goodKey == 2: # AAHHHHH changed
|
||||
return defer.fail(error.ConchError('bad host key'))
|
||||
else:
|
||||
if options['host'] == self.transport.getPeer()[1]:
|
||||
host = options['host']
|
||||
khHost = options['host']
|
||||
else:
|
||||
host = '%s (%s)' % (options['host'],
|
||||
self.transport.getPeer()[1])
|
||||
khHost = '%s,%s' % (options['host'],
|
||||
self.transport.getPeer()[1])
|
||||
keyType = common.getNS(pubKey)[0]
|
||||
ques = """The authenticity of host '%s' can't be established.\r
|
||||
%s key fingerprint is %s.""" % (host,
|
||||
{'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType],
|
||||
fingerprint)
|
||||
ques+='\r\nAre you sure you want to continue connecting (yes/no)? '
|
||||
return deferredAskFrame(ques, 1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
|
||||
|
||||
def _cbVerifyHostKey(self, ans, pubKey, khHost, keyType):
|
||||
if ans.lower() not in ('yes', 'no'):
|
||||
return deferredAskFrame("Please type 'yes' or 'no': ",1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
|
||||
if ans.lower() == 'no':
|
||||
frame.write('Host key verification failed.\r\n')
|
||||
raise error.ConchError('bad host key')
|
||||
try:
|
||||
frame.write("Warning: Permanently added '%s' (%s) to the list of known hosts.\r\n" % (khHost, {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType]))
|
||||
known_hosts = open(os.path.expanduser('~/.ssh/known_hosts'), 'a')
|
||||
encodedKey = base64.encodestring(pubKey).replace('\n', '')
|
||||
known_hosts.write('\n%s %s %s' % (khHost, keyType, encodedKey))
|
||||
known_hosts.close()
|
||||
except:
|
||||
log.deferr()
|
||||
raise error.ConchError
|
||||
|
||||
def connectionSecure(self):
|
||||
if options['user']:
|
||||
user = options['user']
|
||||
else:
|
||||
user = getpass.getuser()
|
||||
self.requestService(SSHUserAuthClient(user, SSHConnection()))
|
||||
|
||||
class SSHUserAuthClient(userauth.SSHUserAuthClient):
|
||||
usedFiles = []
|
||||
|
||||
def getPassword(self, prompt = None):
|
||||
if not prompt:
|
||||
prompt = "%s@%s's password: " % (self.user, options['host'])
|
||||
return deferredAskFrame(prompt,0)
|
||||
|
||||
def getPublicKey(self):
|
||||
files = [x for x in options.identitys if x not in self.usedFiles]
|
||||
if not files:
|
||||
return None
|
||||
file = files[0]
|
||||
log.msg(file)
|
||||
self.usedFiles.append(file)
|
||||
file = os.path.expanduser(file)
|
||||
file += '.pub'
|
||||
if not os.path.exists(file):
|
||||
return
|
||||
try:
|
||||
return keys.Key.fromFile(file).blob()
|
||||
except:
|
||||
return self.getPublicKey() # try again
|
||||
|
||||
def getPrivateKey(self):
|
||||
file = os.path.expanduser(self.usedFiles[-1])
|
||||
if not os.path.exists(file):
|
||||
return None
|
||||
try:
|
||||
return defer.succeed(keys.Key.fromFile(file).keyObject)
|
||||
except keys.BadKeyError, e:
|
||||
if e.args[0] == 'encrypted key with no password':
|
||||
prompt = "Enter passphrase for key '%s': " % \
|
||||
self.usedFiles[-1]
|
||||
return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, 0)
|
||||
def _cbGetPrivateKey(self, ans, count):
|
||||
file = os.path.expanduser(self.usedFiles[-1])
|
||||
try:
|
||||
return keys.Key.fromFile(file, password = ans).keyObject
|
||||
except keys.BadKeyError:
|
||||
if count == 2:
|
||||
raise
|
||||
prompt = "Enter passphrase for key '%s': " % \
|
||||
self.usedFiles[-1]
|
||||
return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey, count+1)
|
||||
|
||||
class SSHConnection(connection.SSHConnection):
|
||||
def serviceStarted(self):
|
||||
if not options['noshell']:
|
||||
self.openChannel(SSHSession())
|
||||
if options.localForwards:
|
||||
for localPort, hostport in options.localForwards:
|
||||
reactor.listenTCP(localPort,
|
||||
forwarding.SSHListenForwardingFactory(self,
|
||||
hostport,
|
||||
forwarding.SSHListenClientForwardingChannel))
|
||||
if options.remoteForwards:
|
||||
for remotePort, hostport in options.remoteForwards:
|
||||
log.msg('asking for remote forwarding for %s:%s' %
|
||||
(remotePort, hostport))
|
||||
data = forwarding.packGlobal_tcpip_forward(
|
||||
('0.0.0.0', remotePort))
|
||||
d = self.sendGlobalRequest('tcpip-forward', data)
|
||||
self.remoteForwards[remotePort] = hostport
|
||||
|
||||
class SSHSession(channel.SSHChannel):
|
||||
|
||||
name = 'session'
|
||||
|
||||
def channelOpen(self, foo):
|
||||
#global globalSession
|
||||
#globalSession = self
|
||||
# turn off local echo
|
||||
self.escapeMode = 1
|
||||
c = session.SSHSessionClient()
|
||||
if options['escape']:
|
||||
c.dataReceived = self.handleInput
|
||||
else:
|
||||
c.dataReceived = self.write
|
||||
c.connectionLost = self.sendEOF
|
||||
frame.callback = c.dataReceived
|
||||
frame.canvas.focus_force()
|
||||
if options['subsystem']:
|
||||
self.conn.sendRequest(self, 'subsystem', \
|
||||
common.NS(options['command']))
|
||||
elif options['command']:
|
||||
if options['tty']:
|
||||
term = os.environ.get('TERM', 'xterm')
|
||||
#winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
|
||||
winSize = (25,80,0,0) #struct.unpack('4H', winsz)
|
||||
ptyReqData = session.packRequest_pty_req(term, winSize, '')
|
||||
self.conn.sendRequest(self, 'pty-req', ptyReqData)
|
||||
self.conn.sendRequest(self, 'exec', \
|
||||
common.NS(options['command']))
|
||||
else:
|
||||
if not options['notty']:
|
||||
term = os.environ.get('TERM', 'xterm')
|
||||
#winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
|
||||
winSize = (25,80,0,0) #struct.unpack('4H', winsz)
|
||||
ptyReqData = session.packRequest_pty_req(term, winSize, '')
|
||||
self.conn.sendRequest(self, 'pty-req', ptyReqData)
|
||||
self.conn.sendRequest(self, 'shell', '')
|
||||
self.conn.transport.transport.setTcpNoDelay(1)
|
||||
|
||||
def handleInput(self, char):
|
||||
#log.msg('handling %s' % repr(char))
|
||||
if char in ('\n', '\r'):
|
||||
self.escapeMode = 1
|
||||
self.write(char)
|
||||
elif self.escapeMode == 1 and char == options['escape']:
|
||||
self.escapeMode = 2
|
||||
elif self.escapeMode == 2:
|
||||
self.escapeMode = 1 # so we can chain escapes together
|
||||
if char == '.': # disconnect
|
||||
log.msg('disconnecting from escape')
|
||||
reactor.stop()
|
||||
return
|
||||
elif char == '\x1a': # ^Z, suspend
|
||||
# following line courtesy of Erwin@freenode
|
||||
os.kill(os.getpid(), signal.SIGSTOP)
|
||||
return
|
||||
elif char == 'R': # rekey connection
|
||||
log.msg('rekeying connection')
|
||||
self.conn.transport.sendKexInit()
|
||||
return
|
||||
self.write('~' + char)
|
||||
else:
|
||||
self.escapeMode = 0
|
||||
self.write(char)
|
||||
|
||||
def dataReceived(self, data):
|
||||
if options['ansilog']:
|
||||
print repr(data)
|
||||
frame.write(data)
|
||||
|
||||
def extReceived(self, t, data):
|
||||
if t==connection.EXTENDED_DATA_STDERR:
|
||||
log.msg('got %s stderr data' % len(data))
|
||||
sys.stderr.write(data)
|
||||
sys.stderr.flush()
|
||||
|
||||
def eofReceived(self):
|
||||
log.msg('got eof')
|
||||
sys.stdin.close()
|
||||
|
||||
def closed(self):
|
||||
log.msg('closed %s' % self)
|
||||
if len(self.conn.channels) == 1: # just us left
|
||||
reactor.stop()
|
||||
|
||||
def request_exit_status(self, data):
|
||||
global exitStatus
|
||||
exitStatus = int(struct.unpack('>L', data)[0])
|
||||
log.msg('exit status: %s' % exitStatus)
|
||||
|
||||
def sendEOF(self):
|
||||
self.conn.sendEOF(self)
|
||||
|
||||
if __name__=="__main__":
|
||||
run()
|
||||
Loading…
Add table
Add a link
Reference in a new issue