mirror of https://github.com/huashengdun/webssh
add proxy
parent
1cf19c7186
commit
b60acbaf00
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (webssh)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/webssh.iml" filepath="$PROJECT_DIR$/.idea/webssh.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
9
|
||||||
|
README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
9
|
||||||
|
README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="SonarLintModuleSettings">
|
||||||
|
<option name="uniqueId" value="3fc80e5d-f723-4353-8cca-79f0f3561461" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="4c3fa67e-67e3-4d45-a581-de095d94f3ad" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/webssh/handler.py" beforeDir="false" afterPath="$PROJECT_DIR$/webssh/handler.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/webssh/static/js/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/webssh/static/js/main.js" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 7
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="2W7ocgeQUfLJH8PAp9M4UcIppVE" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
<option name="showModules" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"WebServerToolWindowFactoryState": "false",
|
||||||
|
"git-widget-placeholder": "master",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager" selected="Python.main">
|
||||||
|
<configuration name="main" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="webssh" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/webssh" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/webssh/main.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="run" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="webssh" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="SDK_NAME" value="Python 3.9 (webssh)" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/run.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Python.main" />
|
||||||
|
<item itemvalue="Python.run" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="4c3fa67e-67e3-4d45-a581-de095d94f3ad" name="Changes" comment="" />
|
||||||
|
<created>1696093060393</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1696093060393</updated>
|
||||||
|
<workItem from="1696093062322" duration="3000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
|
<SUITE FILE_PATH="coverage/webssh$main.coverage" NAME="main Coverage Results" MODIFIED="1696500269853" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/webssh" />
|
||||||
|
<SUITE FILE_PATH="coverage/webssh$run.coverage" NAME="run Coverage Results" MODIFIED="1696093175682" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -6,6 +6,7 @@ import struct
|
||||||
import traceback
|
import traceback
|
||||||
import weakref
|
import weakref
|
||||||
import paramiko
|
import paramiko
|
||||||
|
import socks
|
||||||
import tornado.web
|
import tornado.web
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
@ -29,7 +30,6 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_PORT = 22
|
DEFAULT_PORT = 22
|
||||||
|
|
||||||
swallow_http_errors = True
|
swallow_http_errors = True
|
||||||
|
@ -101,7 +101,6 @@ class SSHClient(paramiko.SSHClient):
|
||||||
|
|
||||||
|
|
||||||
class PrivateKey(object):
|
class PrivateKey(object):
|
||||||
|
|
||||||
max_length = 16384 # rough number
|
max_length = 16384 # rough number
|
||||||
|
|
||||||
tag_to_name = {
|
tag_to_name = {
|
||||||
|
@ -143,7 +142,7 @@ class PrivateKey(object):
|
||||||
logging.debug('Reset offset to {}.'.format(offset))
|
logging.debug('Reset offset to {}.'.format(offset))
|
||||||
|
|
||||||
logging.debug('Try parsing it as {} type key'.format(name))
|
logging.debug('Try parsing it as {} type key'.format(name))
|
||||||
pkeycls = getattr(paramiko, name+'Key')
|
pkeycls = getattr(paramiko, name + 'Key')
|
||||||
pkey = None
|
pkey = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -179,12 +178,11 @@ class PrivateKey(object):
|
||||||
msg = 'Invalid key'
|
msg = 'Invalid key'
|
||||||
if self.password:
|
if self.password:
|
||||||
msg += ' or wrong passphrase "{}" for decrypting it.'.format(
|
msg += ' or wrong passphrase "{}" for decrypting it.'.format(
|
||||||
self.password)
|
self.password)
|
||||||
raise InvalidValueError(msg)
|
raise InvalidValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
class MixinHandler(object):
|
class MixinHandler(object):
|
||||||
|
|
||||||
custom_headers = {
|
custom_headers = {
|
||||||
'Server': 'TornadoServer'
|
'Server': 'TornadoServer'
|
||||||
}
|
}
|
||||||
|
@ -313,8 +311,7 @@ class NotFoundHandler(MixinHandler, tornado.web.ErrorHandler):
|
||||||
|
|
||||||
|
|
||||||
class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
|
executor = ThreadPoolExecutor(max_workers=cpu_count() * 5)
|
||||||
executor = ThreadPoolExecutor(max_workers=cpu_count()*5)
|
|
||||||
|
|
||||||
def initialize(self, loop, policy, host_keys_settings):
|
def initialize(self, loop, policy, host_keys_settings):
|
||||||
super(IndexHandler, self).initialize(loop)
|
super(IndexHandler, self).initialize(loop)
|
||||||
|
@ -383,9 +380,9 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
if self.ssh_client._system_host_keys.lookup(key) is None:
|
if self.ssh_client._system_host_keys.lookup(key) is None:
|
||||||
if self.ssh_client._host_keys.lookup(key) is None:
|
if self.ssh_client._host_keys.lookup(key) is None:
|
||||||
raise tornado.web.HTTPError(
|
raise tornado.web.HTTPError(
|
||||||
403, 'Connection to {}:{} is not allowed.'.format(
|
403, 'Connection to {}:{} is not allowed.'.format(
|
||||||
hostname, port)
|
hostname, port)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
hostname = self.get_hostname()
|
hostname = self.get_hostname()
|
||||||
|
@ -396,6 +393,19 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
passphrase = self.get_argument('passphrase', u'')
|
passphrase = self.get_argument('passphrase', u'')
|
||||||
totp = self.get_argument('totp', u'')
|
totp = self.get_argument('totp', u'')
|
||||||
|
|
||||||
|
# proxyType, proxyIp, proxyPort, proxyRdns = true, proxyUser, proxyPass
|
||||||
|
|
||||||
|
proxyType = self.get_argument('proxytype', None)
|
||||||
|
if proxyType is not None and proxyType != '':
|
||||||
|
proxyType = int(proxyType)
|
||||||
|
proxyIp = self.get_argument('proxyip', u'')
|
||||||
|
proxyPort = self.get_argument('proxyport', None)
|
||||||
|
if proxyPort is not None and proxyPort != '':
|
||||||
|
proxyPort = int(proxyPort)
|
||||||
|
proxyRdns = self.get_argument('proxyrdns', u'')
|
||||||
|
proxyUser = self.get_argument('proxyuser', u'')
|
||||||
|
proxyPass = self.get_argument('proxypass', u'')
|
||||||
|
|
||||||
if isinstance(self.policy, paramiko.RejectPolicy):
|
if isinstance(self.policy, paramiko.RejectPolicy):
|
||||||
self.lookup_hostname(hostname, port)
|
self.lookup_hostname(hostname, port)
|
||||||
|
|
||||||
|
@ -405,7 +415,8 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
pkey = None
|
pkey = None
|
||||||
|
|
||||||
self.ssh_client.totp = totp
|
self.ssh_client.totp = totp
|
||||||
args = (hostname, port, username, password, pkey)
|
args = (
|
||||||
|
hostname, port, username, password, pkey, proxyType, proxyIp, proxyPort, proxyRdns, proxyUser, proxyPass)
|
||||||
logging.debug(args)
|
logging.debug(args)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
@ -446,13 +457,33 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
logging.warning('Could not detect the default encoding.')
|
logging.warning('Could not detect the default encoding.')
|
||||||
return 'utf-8'
|
return 'utf-8'
|
||||||
|
|
||||||
|
def http_proxy_tunnel_connect(self, proxy, target, timeout=None):
|
||||||
|
logging.info('Connecting to proxy {}'.format(proxy))
|
||||||
|
logging.info('Connecting to target {}'.format(target))
|
||||||
|
sock = socks.socksocket()
|
||||||
|
sock.set_proxy(*proxy)
|
||||||
|
sock.connect(target)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
return sock
|
||||||
|
|
||||||
def ssh_connect(self, args):
|
def ssh_connect(self, args):
|
||||||
ssh = self.ssh_client
|
ssh = self.ssh_client
|
||||||
dst_addr = args[:2]
|
dst_addr = args[:2]
|
||||||
|
logging.info(args)
|
||||||
logging.info('Connecting to {}:{}'.format(*dst_addr))
|
logging.info('Connecting to {}:{}'.format(*dst_addr))
|
||||||
|
|
||||||
|
sock = None
|
||||||
|
if len(args) > 5:
|
||||||
|
if args[6] is not None and args[6] != '':
|
||||||
|
# 从args中获取代理信息
|
||||||
|
sock = self.http_proxy_tunnel_connect(
|
||||||
|
proxy=args[5:],
|
||||||
|
target=dst_addr,
|
||||||
|
timeout=5000
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh.connect(*args, timeout=options.timeout)
|
ssh.connect(*args[:5], timeout=options.timeout, sock=sock)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
raise ValueError('Unable to connect to {}:{}'.format(*dst_addr))
|
raise ValueError('Unable to connect to {}:{}'.format(*dst_addr))
|
||||||
except paramiko.BadAuthenticationType:
|
except paramiko.BadAuthenticationType:
|
||||||
|
@ -503,6 +534,7 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler):
|
||||||
|
|
||||||
self.check_origin()
|
self.check_origin()
|
||||||
|
|
||||||
|
logging.info('Connected from {}'.format(self.get_args()))
|
||||||
try:
|
try:
|
||||||
args = self.get_args()
|
args = self.get_args()
|
||||||
except InvalidValueError as exc:
|
except InvalidValueError as exc:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue