Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b39e9d6
Showing
14 changed files
with
1,541 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.py[co] | ||
*.egg | ||
*.egg-info | ||
dist | ||
build | ||
eggs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
recursive-include wssh/static/* | ||
recursive-include wssh/templates/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
#!/usr/bin/env python | ||
|
||
if __name__ == '__main__': | ||
from wssh import client | ||
|
||
import os | ||
import sys | ||
import argparse | ||
import getpass | ||
import urllib2 | ||
|
||
|
||
parser = argparse.ArgumentParser( | ||
description='wssh - SSH Over WebSockets Client') | ||
|
||
parser.add_argument('--host', '-H', | ||
help='WSSH server host (default: 127.0.0.1)', | ||
default='127.0.0.1') | ||
|
||
parser.add_argument('--port', '-P', | ||
help='WSSH server port (default: 5000)', | ||
type=int, | ||
default=5000) | ||
|
||
parser.add_argument('--password', '-p', | ||
nargs='?', | ||
const='', | ||
help='Password-based authentication. ' \ | ||
'If no password is provided you will be prompted for one') | ||
|
||
parser.add_argument('--key', '-k', | ||
nargs='?', | ||
const='', | ||
help='Private key authentication. ' \ | ||
'Selects a file from which the private key ' \ | ||
'for RSA or DSA authentication is read. ' \ | ||
'The default is ~/.ssh/id_rsa and ~/.ssh/id_dsa.') | ||
|
||
parser.add_argument('--key-passphrase', '-K', | ||
nargs='?', | ||
const='', | ||
help='Provide a passphrase for encrypted private key files.') | ||
|
||
parser.add_argument('destination', | ||
help='[user@]hostname') | ||
|
||
args = parser.parse_args() | ||
|
||
if '@' in args.destination: | ||
(username, hostname) = args.destination.split('@', 1) | ||
else: | ||
(username, hostname) = (getpass.getuser(), args.destination) | ||
|
||
if args.password == '': | ||
password = getpass.getpass('Password: ') | ||
else: | ||
password = args.password | ||
|
||
if args.key_passphrase == '': | ||
key_passphrase = getpass.getpass('Enter passphrase for private key: ') | ||
else: | ||
key_passphrase = args.key_passphrase | ||
|
||
|
||
key = None | ||
if args.key == '': | ||
key_files = ['~/.ssh/id_rsa', '~/.ssh/id_dsa'] | ||
for path in key_files: | ||
path = os.path.expanduser(path) | ||
if os.path.exists(path): | ||
key = file(path).read() | ||
break | ||
if key is None: | ||
print >> sys.stderr, 'Error: Unable to locate identity file {0}' \ | ||
.format(' or '.join(key_files)) | ||
sys.exit(1) | ||
elif args.key is not None: | ||
if not os.path.exists(args.key): | ||
print >> sys.stderr, 'Error: Identity file "{0}" does not exist' \ | ||
.format(args.key) | ||
sys.exit(1) | ||
key = file(args.key).read() | ||
|
||
params = { | ||
'password': password, | ||
'private_key': key, | ||
'key_passphrase': key_passphrase | ||
} | ||
|
||
# Filter empty parameters | ||
params = dict(filter(lambda (k, v): v is not None, params.iteritems())) | ||
|
||
endpoint = 'ws://{serv_host}:{serv_port}/wssh/{host}/{user}?{params}'.format( | ||
serv_host=args.host, | ||
serv_port=args.port, | ||
host=urllib2.quote(hostname), | ||
user=urllib2.quote(username), | ||
params='&'.join(['{0}={1}'.format(k, urllib2.quote(v)) | ||
for (k, v) in params.iteritems()])) | ||
|
||
try: | ||
client.invoke_shell(endpoint) | ||
except client.ConnectionError as e: | ||
print >>sys.stderr, 'wssh: {0}'.format(e.message or 'Connection error') | ||
else: | ||
print >>sys.stderr, 'Connection to {0} closed.'.format(hostname) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
#!/usr/bin/env python | ||
|
||
from gevent import monkey; monkey.patch_all() | ||
from flask import Flask, request, abort, render_template | ||
from werkzeug.exceptions import BadRequest | ||
import gevent | ||
import wssh | ||
|
||
|
||
app = Flask(__name__) | ||
|
||
@app.route('/') | ||
def index(): | ||
return render_template('index.html') | ||
|
||
@app.route('/wssh/<hostname>/<username>') | ||
def connect(hostname, username): | ||
app.logger.debug('{remote} -> {username}@{hostname}: {command}'.format( | ||
remote=request.remote_addr, | ||
username=username, | ||
hostname=hostname, | ||
command=request.args['run'] if 'run' in request.args else | ||
'[interactive shell]' | ||
)) | ||
|
||
# Abort if this is not a websocket request | ||
if not request.environ.get('wsgi.websocket'): | ||
app.logger.error('Abort: Request is not WebSocket upgradable') | ||
raise BadRequest() | ||
|
||
proxy = wssh.WSSHProxy(request.environ['wsgi.websocket']) | ||
try: | ||
proxy.open( | ||
hostname=hostname, | ||
username=username, | ||
password=request.args.get('password'), | ||
private_key=request.args.get('private_key'), | ||
key_passphrase=request.args.get('key_passphrase'), | ||
allow_agent=app.config.get('WSSH_ALLOW_SSH_AGENT', False)) | ||
except Exception as e: | ||
app.logger.exception('Error while connecting to {0}: {1}'.format( | ||
hostname, e.message)) | ||
request.environ['wsgi.websocket'].close() | ||
return str() | ||
if 'run' in request.args: | ||
proxy.execute(request.args) | ||
else: | ||
proxy.shell() | ||
|
||
# We have to manually close the websocket and return an empty response, | ||
# otherwise flask will complain about not returning a response and will | ||
# throw a 500 at our websocket client | ||
request.environ['wsgi.websocket'].close() | ||
return str() | ||
|
||
if __name__ == '__main__': | ||
import argparse | ||
from gevent.pywsgi import WSGIServer | ||
from geventwebsocket import WebSocketHandler | ||
from jinja2 import FileSystemLoader | ||
import os | ||
|
||
root_path = os.path.dirname(wssh.__file__) | ||
app.jinja_loader = FileSystemLoader(os.path.join(root_path, 'templates')) | ||
app.static_folder = os.path.join(root_path, 'static') | ||
|
||
parser = argparse.ArgumentParser( | ||
description='wsshd - SSH Over WebSockets Daemon') | ||
|
||
parser.add_argument('--port', '-p', | ||
default=5000, | ||
help='Port to bind (default: 5000)') | ||
|
||
parser.add_argument('--host', '-H', | ||
default='0.0.0.0', | ||
help='Host to listen to (default: 0.0.0.0)') | ||
|
||
parser.add_argument('--allow-agent', '-A', | ||
action='store_true', | ||
default=False, | ||
help='Allow the use of the local (where wsshd is running) ' \ | ||
'ssh-agent to authenticate. Dangerous.') | ||
|
||
args = parser.parse_args() | ||
|
||
app.config['WSSH_ALLOW_SSH_AGENT'] = args.allow_agent | ||
|
||
agent = 'wsshd/{0}'.format('0.1.0') | ||
|
||
print '{0} running on {1}:{2}'.format(agent, args.host, args.port) | ||
|
||
app.debug = True | ||
http_server = WSGIServer((args.host, args.port), app, | ||
log=None, | ||
handler_class=WebSocketHandler) | ||
try: | ||
http_server.serve_forever() | ||
except KeyboardInterrupt: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
https://github.com/liris/websocket-client/tarball/master#egg=websocket-client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
gevent | ||
gevent-websocket | ||
paramiko | ||
flask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from setuptools import setup | ||
|
||
|
||
setup( | ||
name='wssh', | ||
version='0.0.1', | ||
author='Andrea Luzzardi <aluzzardi@gmail.com>', | ||
packages=[ | ||
'wssh' | ||
], | ||
scripts=[ | ||
'bin/wssh', | ||
'bin/wsshd' | ||
], | ||
package_data={'': ['static/*', 'templates/*']}, | ||
include_package_data=True, | ||
zip_safe=False | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from server import WSSHProxy | ||
|
||
__version__ = '0.1.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
import sys | ||
import signal | ||
import errno | ||
|
||
import websocket | ||
import select | ||
|
||
import termios | ||
import tty | ||
import fcntl | ||
import struct | ||
|
||
import platform | ||
|
||
try: | ||
import simplejson as json | ||
except ImportError: | ||
import json | ||
|
||
|
||
class ConnectionError(Exception): | ||
pass | ||
|
||
|
||
def _pty_size(): | ||
rows, cols = 24, 80 | ||
# Can't do much for Windows | ||
if platform.system() == 'Windows': | ||
return rows, cols | ||
fmt = 'HH' | ||
buffer = struct.pack(fmt, 0, 0) | ||
result = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, | ||
buffer) | ||
rows, cols = struct.unpack(fmt, result) | ||
return rows, cols | ||
|
||
|
||
def _resize(ws): | ||
rows, cols = _pty_size() | ||
ws.send(json.dumps({'resize': {'width': cols, 'height': rows}})) | ||
|
||
|
||
def invoke_shell(endpoint): | ||
ssh = websocket.create_connection(endpoint) | ||
_resize(ssh) | ||
oldtty = termios.tcgetattr(sys.stdin) | ||
old_handler = signal.getsignal(signal.SIGWINCH) | ||
|
||
def on_term_resize(signum, frame): | ||
_resize(ssh) | ||
signal.signal(signal.SIGWINCH, on_term_resize) | ||
|
||
try: | ||
tty.setraw(sys.stdin.fileno()) | ||
tty.setcbreak(sys.stdin.fileno()) | ||
|
||
rows, cols = _pty_size() | ||
ssh.send(json.dumps({'resize': {'width': cols, 'height': rows}})) | ||
|
||
while True: | ||
try: | ||
r, w, e = select.select([ssh.sock, sys.stdin], [], []) | ||
if ssh.sock in r: | ||
data = ssh.recv() | ||
if not data: | ||
break | ||
message = json.loads(data) | ||
if 'error' in message: | ||
raise ConnectionError(message['error']) | ||
sys.stdout.write(message['data']) | ||
sys.stdout.flush() | ||
if sys.stdin in r: | ||
x = sys.stdin.read(1) | ||
if len(x) == 0: | ||
break | ||
ssh.send(json.dumps({'data': x})) | ||
except (select.error, IOError) as e: | ||
if e.args and e.args[0] == errno.EINTR: | ||
pass | ||
else: | ||
raise | ||
except websocket.WebSocketException: | ||
raise | ||
finally: | ||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) | ||
signal.signal(signal.SIGWINCH, old_handler) |
Oops, something went wrong.