From a5d9e58ac61dde82c2776aaba2c6712312f71bde Mon Sep 17 00:00:00 2001 From: yanghuan Date: Tue, 5 Jan 2016 15:04:56 +0800 Subject: [PATCH 1/3] support telnet and gbk charset --- bin/wssh | 8 ++- bin/wsshd | 6 +- wssh/server.py | 143 +++++++++++++++++++++++++------------- wssh/setup.py | 18 +++++ wssh/static/wssh.js | 2 +- wssh/templates/index.html | 60 ++++++++++++---- 6 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 wssh/setup.py diff --git a/bin/wssh b/bin/wssh index 55a7f61..fadddd5 100755 --- a/bin/wssh +++ b/bin/wssh @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python if __name__ == '__main__': from wssh import client @@ -51,6 +51,11 @@ if __name__ == '__main__': parser.add_argument('command', nargs='*', help='optional command to be executed') + + parser.add_argument('logintype', + nargs='*', + default='ssh', + help='logintype, support ssh/telnet') args = parser.parse_args() @@ -91,6 +96,7 @@ if __name__ == '__main__': params = { 'password': password, 'port': str(args.ssh_port), + 'logintype': logintype, 'private_key': key, 'key_passphrase': key_passphrase, 'run': ' '.join(args.command) if args.command else None, diff --git a/bin/wsshd b/bin/wsshd index 97fbd09..16f2320 100755 --- a/bin/wsshd +++ b/bin/wsshd @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python from gevent import monkey monkey.patch_all() @@ -32,7 +32,7 @@ def connect(hostname, username): app.logger.error('Abort: Request is not WebSocket upgradable') raise BadRequest() - bridge = wssh.WSSHBridge(request.environ['wsgi.websocket']) + bridge = wssh.WSSHBridge(request.environ['wsgi.websocket'], logintype=request.args.get('logintype')) try: bridge.open( hostname=hostname, @@ -40,7 +40,7 @@ def connect(hostname, username): password=request.args.get('password'), port=int(request.args.get('port')), private_key=request.args.get('private_key'), - key_passphrase=request.args.get('key_passphrase'), + 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( diff --git a/wssh/server.py b/wssh/server.py index 17a4965..9f87107 100644 --- a/wssh/server.py +++ b/wssh/server.py @@ -17,29 +17,29 @@ from paramiko.rsakey import RSAKey from paramiko.ssh_exception import SSHException +import telnetlib import socket - -try: - import simplejson as json -except ImportError: - import json +import json from StringIO import StringIO - class WSSHBridge(object): """ WebSocket to SSH Bridge Server """ - def __init__(self, websocket): + def __init__(self, websocket, logintype='ssh'): """ Initialize a WSSH Bridge The websocket must be the one created by gevent-websocket """ self._websocket = websocket - self._ssh = paramiko.SSHClient() - self._ssh.set_missing_host_key_policy( - paramiko.AutoAddPolicy()) + self._logintype = logintype self._tasks = [] + if self._logintype == 'telnet': + self._ssh = telnetlib + else: + self._ssh = paramiko.SSHClient() + self._ssh.set_missing_host_key_policy( + paramiko.AutoAddPolicy()) def _load_private_key(self, private_key, passphrase=None): """ Load a SSH private key (DSA or RSA) from a string @@ -70,6 +70,8 @@ def open(self, hostname, port=22, username=None, password=None, private_key=None, key_passphrase=None, allow_agent=False, timeout=None): """ Open a connection to a remote SSH server + or + Open a connection to a remote telnet server In order to connect, either one of these credentials must be supplied: @@ -83,27 +85,46 @@ def open(self, hostname, port=22, username=None, password=None, Authenticate using the *local* SSH agent. This is the one running alongside wsshd on the server side. """ - try: - pkey = None - if private_key: - pkey = self._load_private_key(private_key, key_passphrase) - self._ssh.connect( - hostname=hostname, - port=port, - username=username, - password=password, - pkey=pkey, - timeout=timeout, - allow_agent=allow_agent, - look_for_keys=False) - except socket.gaierror as e: - self._websocket.send(json.dumps({'error': - 'Could not resolve hostname {0}: {1}'.format( - hostname, e.args[1])})) - raise - except Exception as e: - self._websocket.send(json.dumps({'error': e.message or str(e)})) - raise + if self._logintype == 'telnet': + try: + self._ssh = telnetlib.Telnet(hostname,port) + self._ssh.read_until(': ') + #self._ssh.read_some() + self._ssh.write(username.encode('ascii') + b'\n') + self._ssh.read_until(': ') + #self._ssh.read_some() + self._ssh.write(password.encode('ascii') + b'\n') + ### telnetlib, process_rawq, line-478,self.sock.sendall(IAC + DONT + opt)->self.sock.sendall(IAC + DO + opt) + except socket.gaierror as e: + self._websocket.send(json.dumps({'error': + 'Could not resolve hostname {0}: {1}'.format( + hostname, e.args[1])})) + raise + except Exception as e: + self._websocket.send(json.dumps({'error': e.message or str(e)})) + raise + else: + try: + pkey = None + if private_key: + pkey = self._load_private_key(private_key, key_passphrase) + self._ssh.connect( + hostname=hostname, + port=port, + username=username, + password=password, + pkey=pkey, + timeout=timeout, + allow_agent=allow_agent, + look_for_keys=False) + except socket.gaierror as e: + self._websocket.send(json.dumps({'error': + 'Could not resolve hostname {0}: {1}'.format( + hostname, e.args[1])})) + raise + except Exception as e: + self._websocket.send(json.dumps({'error': e.message or str(e)})) + raise def _forward_inbound(self, channel): """ Forward inbound traffic (websockets -> ssh) """ @@ -113,12 +134,16 @@ def _forward_inbound(self, channel): if not data: return data = json.loads(str(data)) - if 'resize' in data: - channel.resize_pty( - data['resize'].get('width', 80), - data['resize'].get('height', 24)) - if 'data' in data: - channel.send(data['data']) + if self._logintype == 'telnet': + if 'data' in data: + channel.write(data['data'].encode('ascii')) + else: + if 'resize' in data: + channel.resize_pty( + data['resize'].get('width', 80), + data['resize'].get('height', 24)) + if 'data' in data: + channel.send(data['data']) finally: self.close() @@ -127,17 +152,26 @@ def _forward_outbound(self, channel): try: while True: wait_read(channel.fileno()) - data = channel.recv(1024) + if self._logintype == 'telnet': + data = channel.read_very_eager() + else: + data = channel.recv(1024) if not len(data): return - self._websocket.send(json.dumps({'data': data})) + try: + data = data.decode("gbk", 'ignore').encode("utf-8") + self._websocket.send(json.dumps({'data': data})) + except BaseException,e: + print "data=%r, err=%r" %(data,e) + pass finally: self.close() def _bridge(self, channel): """ Full-duplex bridge between a websocket and a SSH channel """ - channel.setblocking(False) - channel.settimeout(0.0) + if self._logintype != 'telnet': + channel.setblocking(False) + channel.settimeout(0.0) self._tasks = [ gevent.spawn(self._forward_inbound, channel), gevent.spawn(self._forward_outbound, channel) @@ -159,12 +193,20 @@ def execute(self, command, term='xterm'): You must connect to a SSH server using ssh_connect() prior to starting the session. """ - transport = self._ssh.get_transport() - channel = transport.open_session() - channel.get_pty(term) - channel.exec_command(command) - self._bridge(channel) - channel.close() + if self._logintype == 'telnet': + self._ssh.write(command.encode('ascii')+b'\n') + data = self._ssh.read_eager() + self._websocket.send(json.dumps({'data': data})) + channel = self._ssh + self._bridge(channel) + channel.close() + else: + transport = self._ssh.get_transport() + channel = transport.open_session() + channel.get_pty(term) + channel.exec_command(command) + self._bridge(channel) + channel.close() def shell(self, term='xterm'): """ Start an interactive shell session @@ -175,6 +217,9 @@ def shell(self, term='xterm'): You must connect to a SSH server using ssh_connect() prior to starting the session. """ - channel = self._ssh.invoke_shell(term) + if self._logintype == 'telnet': + channel = self._ssh + else: + channel = self._ssh.invoke_shell(term) self._bridge(channel) - channel.close() + channel.close() \ No newline at end of file diff --git a/wssh/setup.py b/wssh/setup.py new file mode 100644 index 0000000..148557d --- /dev/null +++ b/wssh/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup + + +setup( + name='wssh', + version='0.1.0', + author='Andrea Luzzardi ', + packages=[ + 'wssh' + ], + scripts=[ + 'bin/wssh', + 'bin/wsshd' + ], + package_data={'': ['static/*', 'templates/*']}, + include_package_data=True, + zip_safe=False +) diff --git a/wssh/static/wssh.js b/wssh/static/wssh.js index 076557d..43b4620 100644 --- a/wssh/static/wssh.js +++ b/wssh/static/wssh.js @@ -50,7 +50,7 @@ WSSHClient.prototype._generateEndpoint = function(options) { encodeURIComponent(options.username); if (options.authentication_method == 'password') { endpoint += '?password=' + encodeURIComponent(options.password) + - '&port=' + encodeURIComponent(options.port); + '&port=' + encodeURIComponent(options.port) + '&logintype=' + encodeURIComponent(options.logintype); } else if (options.authentication_method == 'private_key') { endpoint += '?private_key=' + encodeURIComponent(options.private_key) + '&port=' + encodeURIComponent(options.port); diff --git a/wssh/templates/index.html b/wssh/templates/index.html index f585e6e..a5a9953 100644 --- a/wssh/templates/index.html +++ b/wssh/templates/index.html @@ -174,19 +174,44 @@ $('#ssh').hide(); $('#private_key_authentication', '#connect').hide(); - $('input:radio[value=private_key]', '#connect').click( - function() { - $('#password_authentication').hide(); - $('#private_key_authentication').show(); - } - ); + // $('input:radio[value=private_key]', '#connect').click( + // function() { + // $('#password_authentication').hide(); + // $('#private_key_authentication').show(); + // } + // ); + + // $('input:radio[value=password]', '#connect').click( + // function() { + // $('#password_authentication').show(); + // $('#private_key_authentication').hide(); + // } + // ); + $('.error').removeClass('error'); + var username = getUrlParam("username"); + var hostname = getUrlParam("hostname"); + var password = getUrlParam("password"); + var port = getUrlParam("port"); + var logintype = getUrlParam("logintype"); + if (port > 0 && port < 65535) { + + }else { + port = 22; + } + + var options = { + username: username, + hostname: hostname, + password: password, + command: "", + port: port, + logintype: logintype, + authentication_method: "password" + }; + $('#connect').hide(); + $('#ssh').show(); + openTerminal(options); - $('input:radio[value=password]', '#connect').click( - function() { - $('#password_authentication').show(); - $('#private_key_authentication').hide(); - } - ); $('#connect').submit(function(ev) { ev.preventDefault(); @@ -249,7 +274,18 @@ $('#ssh').show(); openTerminal(options); }); + function getUrlParam(name) { + var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象 + var r = window.location.search.substr(1).match(reg); //匹配目标参数 + if (r != null) return unescape(r[2]); return null; //返回参数值 + } + + + + }); + + From 1124897199d18d513ab8627067abc225302b52d5 Mon Sep 17 00:00:00 2001 From: yanghuan Date: Mon, 25 Jul 2016 11:52:22 +0800 Subject: [PATCH 2/3] support telnet and set width --- bin/wsshd | 4 +- wssh/server.py | 112 ++++++++++++++++++++++---------------- wssh/static/wssh.js | 3 + wssh/templates/index.html | 71 ++++++++---------------- 4 files changed, 93 insertions(+), 97 deletions(-) diff --git a/bin/wsshd b/bin/wsshd index 16f2320..e7b7ae2 100755 --- a/bin/wsshd +++ b/bin/wsshd @@ -32,7 +32,7 @@ def connect(hostname, username): app.logger.error('Abort: Request is not WebSocket upgradable') raise BadRequest() - bridge = wssh.WSSHBridge(request.environ['wsgi.websocket'], logintype=request.args.get('logintype')) + bridge = wssh.WSSHBridge(request.environ['wsgi.websocket'], logintype=request.args.get('logintype'),width=request.args.get('widths')) try: bridge.open( hostname=hostname, @@ -40,7 +40,7 @@ def connect(hostname, username): password=request.args.get('password'), port=int(request.args.get('port')), private_key=request.args.get('private_key'), - key_passphrase=request.args.get('key_passphrase'), + 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( diff --git a/wssh/server.py b/wssh/server.py index 9f87107..23427f6 100644 --- a/wssh/server.py +++ b/wssh/server.py @@ -17,28 +17,41 @@ from paramiko.rsakey import RSAKey from paramiko.ssh_exception import SSHException +import struct import telnetlib import socket import json +import os +import re +import time from StringIO import StringIO class WSSHBridge(object): - """ WebSocket to SSH Bridge Server """ + """ WebSocket to SSH Bridge Server - def __init__(self, websocket, logintype='ssh'): + support telnet protocol + """ + + def __init__(self, websocket, logintype='ssh', width=80): """ Initialize a WSSH Bridge The websocket must be the one created by gevent-websocket """ self._websocket = websocket self._logintype = logintype + self._buffsize = 10240 + self._width = width + if self._width and self._width.isdigit(): + self._width = int(self._width) + else: + self._width = 80 self._tasks = [] if self._logintype == 'telnet': - self._ssh = telnetlib + self._cli = telnetlib else: - self._ssh = paramiko.SSHClient() - self._ssh.set_missing_host_key_policy( + self._cli = paramiko.SSHClient() + self._cli.set_missing_host_key_policy( paramiko.AutoAddPolicy()) def _load_private_key(self, private_key, passphrase=None): @@ -87,14 +100,11 @@ def open(self, hostname, port=22, username=None, password=None, """ if self._logintype == 'telnet': try: - self._ssh = telnetlib.Telnet(hostname,port) - self._ssh.read_until(': ') - #self._ssh.read_some() - self._ssh.write(username.encode('ascii') + b'\n') - self._ssh.read_until(': ') - #self._ssh.read_some() - self._ssh.write(password.encode('ascii') + b'\n') - ### telnetlib, process_rawq, line-478,self.sock.sendall(IAC + DONT + opt)->self.sock.sendall(IAC + DO + opt) + self._cli = telnetlib.Telnet(hostname,port) + self._cli.sock.sendall(telnetlib.IAC + telnetlib.WILL + telnetlib.NAWS) + self._cli.sock.sendall(telnetlib.IAC + telnetlib.SB + telnetlib.NAWS + struct.pack(">H", self._width) + struct.pack(">H", 24) + telnetlib.IAC + telnetlib.SE) + self.invoke_telnetlogin(self._cli, username, password) + ### telnetlib, process_rawq, line-478,mod,self.sock.sendall(IAC + DONT + opt)->self.sock.sendall(IAC + DO + opt) except socket.gaierror as e: self._websocket.send(json.dumps({'error': 'Could not resolve hostname {0}: {1}'.format( @@ -108,7 +118,7 @@ def open(self, hostname, port=22, username=None, password=None, pkey = None if private_key: pkey = self._load_private_key(private_key, key_passphrase) - self._ssh.connect( + self._cli.connect( hostname=hostname, port=port, username=username, @@ -134,13 +144,14 @@ def _forward_inbound(self, channel): if not data: return data = json.loads(str(data)) + if self._logintype == 'telnet': if 'data' in data: channel.write(data['data'].encode('ascii')) else: if 'resize' in data: channel.resize_pty( - data['resize'].get('width', 80), + data['resize'].get('width', self._width), data['resize'].get('height', 24)) if 'data' in data: channel.send(data['data']) @@ -155,15 +166,11 @@ def _forward_outbound(self, channel): if self._logintype == 'telnet': data = channel.read_very_eager() else: - data = channel.recv(1024) + data = channel.recv(self._buffsize) if not len(data): return - try: - data = data.decode("gbk", 'ignore').encode("utf-8") - self._websocket.send(json.dumps({'data': data})) - except BaseException,e: - print "data=%r, err=%r" %(data,e) - pass + + self._websocket.send(json.dumps({'data': data})) finally: self.close() @@ -182,44 +189,57 @@ def close(self): """ Terminate a bridge session """ gevent.killall(self._tasks, block=True) self._tasks = [] - self._ssh.close() + self._cli.close() - def execute(self, command, term='xterm'): - """ Execute a command on the remote server + def shell(self, term='xterm'): + """ Start an interactive shell session - This method will forward traffic from the websocket to the SSH server - and the other way around. + This method invokes a shell on the remote SSH server and proxies + traffic to/from both peers. You must connect to a SSH server using ssh_connect() prior to starting the session. """ if self._logintype == 'telnet': - self._ssh.write(command.encode('ascii')+b'\n') - data = self._ssh.read_eager() - self._websocket.send(json.dumps({'data': data})) - channel = self._ssh - self._bridge(channel) - channel.close() + channel = self._cli else: - transport = self._ssh.get_transport() - channel = transport.open_session() - channel.get_pty(term) - channel.exec_command(command) - self._bridge(channel) - channel.close() + channel = self._cli.invoke_shell(term, width=self._width) + self._bridge(channel) + channel.close() - def shell(self, term='xterm'): - """ Start an interactive shell session + def execute(self, command, term='xterm'): + """ Execute a command on the remote server - This method invokes a shell on the remote SSH server and proxies - traffic to/from both peers. + This method will forward traffic from the websocket to the SSH server + and the other way around. You must connect to a SSH server using ssh_connect() prior to starting the session. """ if self._logintype == 'telnet': - channel = self._ssh + channel = self._cli else: - channel = self._ssh.invoke_shell(term) + channel = self._cli.invoke_shell(term, width=self._width) + + if command: + self.executecmd(channel, command) + self._bridge(channel) - channel.close() \ No newline at end of file + channel.close() + + def invoke_telnetlogin(self, channel, username=None, password=None, buff=''): + if username: + while not re.search("(name|ogin):\s*$", buff, re.M|re.I): + buff += channel.read_eager() + channel.write(username.encode('ascii') + b'\n') + buff = '' + if password: + while not re.search("assword:\s*$", buff, re.M|re.I): + buff += channel.read_eager() + channel.write(password.encode('ascii') + b'\n') + + def executecmd(self, channel, command): + if self._logintype == 'telnet': + channel.write(command.encode('ascii') + b'\n') + else: + channel.send(command.encode('ascii') + b'\n') diff --git a/wssh/static/wssh.js b/wssh/static/wssh.js index 43b4620..56c7958 100644 --- a/wssh/static/wssh.js +++ b/wssh/static/wssh.js @@ -62,6 +62,9 @@ WSSHClient.prototype._generateEndpoint = function(options) { endpoint += '&run=' + encodeURIComponent( options.command); } + if (options.widths != "") { + endpoint += '&widths=' + encodeURIComponent(options.widths); + } return endpoint; }; diff --git a/wssh/templates/index.html b/wssh/templates/index.html index a5a9953..be25591 100644 --- a/wssh/templates/index.html +++ b/wssh/templates/index.html @@ -38,6 +38,10 @@ id="hostname" class="input-large" placeholder="localhost" /> + port From 48fa52692a7500fe5f2a77872873a9178ab4b427 Mon Sep 17 00:00:00 2001 From: yanghuan Date: Tue, 26 Jul 2016 17:21:12 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=98=93=E7=94=A8=E6=80=A7=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wssh/templates/index.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wssh/templates/index.html b/wssh/templates/index.html index be25591..7a3c5d4 100644 --- a/wssh/templates/index.html +++ b/wssh/templates/index.html @@ -194,6 +194,15 @@ } ); + $("#logintype").change(function(){ + var logintype = $('#logintype').val(); + if (logintype == 'ssh') { + $("#portnumber").attr("value",'22'); + } else { + $("#portnumber").attr("value",'23'); + } + }); + $('#connect').submit(function(ev) { ev.preventDefault();