add proxy

pull/364/head
hongjunlong 2023-10-06 12:34:01 +08:00
parent 1cf19c7186
commit b60acbaf00
11 changed files with 982 additions and 808 deletions

4
.idea/misc.xml Normal file
View File

@ -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>

8
.idea/modules.xml Normal file
View File

@ -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>

View File

@ -0,0 +1,3 @@
9
README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d

View File

@ -0,0 +1,3 @@
9
README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

14
.idea/webssh.iml Normal file
View File

@ -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>

113
.idea/workspace.xml Normal file
View File

@ -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">{
&quot;associatedIndex&quot;: 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>

View File

@ -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