Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
C
coco
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
coco
Commits
7295a354
Unverified
Commit
7295a354
authored
Jul 02, 2018
by
老广
Committed by
GitHub
Jul 02, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #66 from jumpserver/feature_telnet
[Feature] 添加功能,支持 telnet server.
parents
1df7614a
581b66ea
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
340 additions
and
7 deletions
+340
-7
connection.py
coco/connection.py
+137
-1
models.py
coco/models.py
+171
-0
proxy.py
coco/proxy.py
+32
-6
No files found.
coco/connection.py
View file @
7295a354
...
...
@@ -2,16 +2,22 @@
#
import
os
import
re
import
socket
import
selectors
import
telnetlib
import
paramiko
from
paramiko.ssh_exception
import
SSHException
from
.ctx
import
app_service
from
.utils
import
get_logger
,
get_private_key_fingerprint
from
.utils
import
get_logger
,
get_private_key_fingerprint
,
net_input
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
1024
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
class
SSHConnection
:
...
...
@@ -148,3 +154,133 @@ class SSHConnection:
logger
.
error
(
e
)
continue
return
sock
class
TelnetConnection
:
def
__init__
(
self
,
asset
,
system_user
,
client
):
self
.
client
=
client
self
.
asset
=
asset
self
.
system_user
=
system_user
self
.
sock
=
None
self
.
sel
=
selectors
.
DefaultSelector
()
self
.
incorrect_pattern
=
re
.
compile
(
r'incorrect|failed|失败'
,
re
.
I
)
self
.
username_pattern
=
re
.
compile
(
r'login:\s*$|username:\s*$|用户名:\s*$|账\s*号:\s*$'
,
re
.
I
)
self
.
password_pattern
=
re
.
compile
(
r'password:\s*$|passwd:\s*$|密\s*码:\s*$'
,
re
.
I
)
self
.
success_pattern
=
re
.
compile
(
r'Last\s*login|success|成功'
,
re
.
I
)
def
get_socket
(
self
):
logger
.
info
(
'Get telnet server socket. {}'
.
format
(
self
.
client
.
user
))
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
self
.
sock
.
connect
((
self
.
asset
.
ip
,
self
.
asset
.
port
))
# Send SGA and ECHO options to Telnet Server
self
.
sock
.
send
(
telnetlib
.
IAC
+
telnetlib
.
DO
+
telnetlib
.
SGA
)
self
.
sock
.
send
(
telnetlib
.
IAC
+
telnetlib
.
DO
+
telnetlib
.
ECHO
)
self
.
sel
.
register
(
self
.
sock
,
selectors
.
EVENT_READ
)
while
True
:
events
=
self
.
sel
.
select
()
for
sock
in
[
key
.
fileobj
for
key
,
_
in
events
]:
data
=
sock
.
recv
(
BUF_SIZE
)
if
sock
==
self
.
sock
:
logger
.
info
(
b
'[Telnet server send]: '
+
data
)
if
not
data
:
self
.
sock
.
close
()
msg
=
'The server <{}> closes the connection.'
.
format
(
self
.
asset
.
hostname
)
logger
.
info
(
msg
)
return
None
,
msg
if
data
.
startswith
(
telnetlib
.
IAC
):
self
.
option_negotiate
(
data
)
else
:
result
=
self
.
login_auth
(
data
)
if
result
:
msg
=
'Successful asset connection.<{}>/<{}>/<{}>.'
.
format
(
self
.
client
.
user
,
self
.
system_user
.
username
,
self
.
asset
.
hostname
)
logger
.
info
(
msg
)
self
.
client
.
send
(
b
'
\r\n
'
+
data
)
return
self
.
sock
,
None
elif
result
is
False
:
self
.
sock
.
close
()
msg
=
'Authentication failed.
\r\n
'
logger
.
info
(
msg
)
return
None
,
msg
elif
result
is
None
:
continue
def
option_negotiate
(
self
,
data
):
"""
Telnet server option negotiate before connection
:param data: option negotiate data
:return:
"""
logger
.
info
(
b
'[Server options negotiate]: '
+
data
)
data_list
=
data
.
split
(
telnetlib
.
IAC
)
new_data_list
=
[]
for
x
in
data_list
:
if
x
==
telnetlib
.
DO
+
telnetlib
.
ECHO
:
new_data_list
.
append
(
telnetlib
.
WONT
+
telnetlib
.
ECHO
)
elif
x
==
telnetlib
.
WILL
+
telnetlib
.
ECHO
:
new_data_list
.
append
(
telnetlib
.
DO
+
telnetlib
.
ECHO
)
elif
x
==
telnetlib
.
WILL
+
telnetlib
.
SGA
:
new_data_list
.
append
(
telnetlib
.
DO
+
telnetlib
.
SGA
)
elif
x
==
telnetlib
.
DO
+
telnetlib
.
TTYPE
:
new_data_list
.
append
(
telnetlib
.
WILL
+
telnetlib
.
TTYPE
)
elif
x
==
telnetlib
.
SB
+
telnetlib
.
TTYPE
+
b
'
\x01
'
:
new_data_list
.
append
(
telnetlib
.
SB
+
telnetlib
.
TTYPE
+
b
'
\x00
'
+
b
'XTERM-256COLOR'
)
elif
telnetlib
.
DO
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
DO
,
telnetlib
.
WONT
))
elif
telnetlib
.
WILL
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
WILL
,
telnetlib
.
DONT
))
elif
telnetlib
.
WONT
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
WONT
,
telnetlib
.
DONT
))
elif
telnetlib
.
DONT
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
DONT
,
telnetlib
.
WONT
))
else
:
new_data_list
.
append
(
x
)
new_data
=
telnetlib
.
IAC
.
join
(
new_data_list
)
logger
.
info
(
b
'[Client options negotiate]: '
+
new_data
)
self
.
sock
.
send
(
new_data
)
def
login_auth
(
self
,
raw_data
):
logger
.
info
(
'[Telnet login auth]: ({})'
.
format
(
self
.
client
.
user
))
try
:
data
=
raw_data
.
decode
(
'utf-8'
)
except
UnicodeDecodeError
:
try
:
data
=
raw_data
.
decode
(
'gbk'
)
except
UnicodeDecodeError
:
logger
.
info
(
b
'[Decode error]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
None
if
self
.
incorrect_pattern
.
search
(
data
):
logger
.
info
(
b
'[Login incorrect prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
False
elif
self
.
username_pattern
.
search
(
data
):
logger
.
info
(
b
'[Username prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
self
.
sock
.
send
(
self
.
system_user
.
username
.
encode
(
'utf-8'
)
+
b
'
\r\n
'
)
return
None
elif
self
.
password_pattern
.
search
(
data
):
logger
.
info
(
b
'[Password prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
self
.
sock
.
send
(
self
.
system_user
.
password
.
encode
(
'utf-8'
)
+
b
'
\r\n
'
)
return
None
elif
self
.
success_pattern
.
search
(
data
):
logger
.
info
(
b
'[Login Success prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
True
else
:
logger
.
info
(
b
'[No match]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
None
coco/models.py
View file @
7295a354
...
...
@@ -86,8 +86,178 @@ class Client:
# print("GC: Client object has been gc")
class
BaseServer
:
"""
Base Server
Achieve command record
sub-class: Server, Telnet Server
"""
def
__init__
(
self
):
self
.
send_bytes
=
0
self
.
recv_bytes
=
0
self
.
stop_evt
=
threading
.
Event
()
self
.
input_data
=
SizedList
(
maxsize
=
1024
)
self
.
output_data
=
SizedList
(
maxsize
=
1024
)
self
.
_in_input_state
=
True
self
.
_input_initial
=
False
self
.
_in_vim_state
=
False
self
.
_input
=
""
self
.
_output
=
""
self
.
_session_ref
=
None
def
set_session
(
self
,
session
):
self
.
_session_ref
=
weakref
.
ref
(
session
)
@property
def
session
(
self
):
if
self
.
_session_ref
:
return
self
.
_session_ref
()
else
:
return
None
def
parse
(
self
,
b
):
if
isinstance
(
b
,
str
):
b
=
b
.
encode
(
"utf-8"
)
if
not
self
.
_input_initial
:
self
.
_input_initial
=
True
if
self
.
_have_enter_char
(
b
):
self
.
_in_input_state
=
False
self
.
_input
=
self
.
_parse_input
()
else
:
if
not
self
.
_in_input_state
:
self
.
_output
=
self
.
_parse_output
()
logger
.
debug
(
"
\n
{}
\n
Input: {}
\n
Output: {}
\n
{}"
.
format
(
"#"
*
30
+
" Command "
+
"#"
*
30
,
self
.
_input
,
self
.
_output
,
"#"
*
30
+
" End "
+
"#"
*
30
,
))
if
self
.
_input
:
self
.
session
.
put_command
(
self
.
_input
,
self
.
_output
)
self
.
input_data
.
clean
()
self
.
output_data
.
clean
()
self
.
_in_input_state
=
True
@staticmethod
def
_have_enter_char
(
s
):
for
c
in
char
.
ENTER_CHAR
:
if
c
in
s
:
return
True
return
False
def
_parse_output
(
self
):
if
not
self
.
output_data
:
return
''
parser
=
utils
.
TtyIOParser
()
return
parser
.
parse_output
(
self
.
output_data
)
def
_parse_input
(
self
):
if
not
self
.
input_data
or
self
.
input_data
[
0
]
==
char
.
RZ_PROTOCOL_CHAR
:
return
parser
=
utils
.
TtyIOParser
()
return
parser
.
parse_input
(
self
.
input_data
)
class
TelnetServer
(
BaseServer
):
"""
Telnet server
"""
def
__init__
(
self
,
sock
,
asset
,
system_user
):
super
(
TelnetServer
,
self
)
.
__init__
()
self
.
sock
=
sock
self
.
asset
=
asset
self
.
system_user
=
system_user
def
fileno
(
self
):
return
self
.
sock
.
fileno
()
def
send
(
self
,
b
):
self
.
parse
(
b
)
return
self
.
sock
.
send
(
b
)
def
recv
(
self
,
size
):
data
=
self
.
sock
.
recv
(
size
)
self
.
session
.
put_replay
(
data
)
if
self
.
_input_initial
:
if
self
.
_in_input_state
:
self
.
input_data
.
append
(
data
)
else
:
self
.
output_data
.
append
(
data
)
return
data
def
close
(
self
):
logger
.
info
(
"Closed server {}"
.
format
(
self
))
self
.
parse
(
b
''
)
self
.
stop_evt
.
set
()
self
.
sock
.
close
()
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
sock
,
item
)
def
__str__
(
self
):
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
class
Server
(
BaseServer
):
"""
SSH Server
Server object like client, a wrapper object, a connection to the asset,
Because we don't want to using python dynamic feature, such asset
have the chan and system_user attr.
"""
# Todo: Server name is not very suitable
def
__init__
(
self
,
chan
,
sock
,
asset
,
system_user
):
super
(
Server
,
self
)
.
__init__
()
self
.
chan
=
chan
self
.
sock
=
sock
self
.
asset
=
asset
self
.
system_user
=
system_user
def
fileno
(
self
):
return
self
.
chan
.
fileno
()
def
send
(
self
,
b
):
self
.
parse
(
b
)
return
self
.
chan
.
send
(
b
)
def
recv
(
self
,
size
):
data
=
self
.
chan
.
recv
(
size
)
self
.
session
.
put_replay
(
data
)
if
self
.
_input_initial
:
if
self
.
_in_input_state
:
self
.
input_data
.
append
(
data
)
else
:
self
.
output_data
.
append
(
data
)
return
data
def
close
(
self
):
logger
.
info
(
"Closed server {}"
.
format
(
self
))
self
.
parse
(
b
''
)
self
.
stop_evt
.
set
()
self
.
chan
.
close
()
self
.
chan
.
transport
.
close
()
if
self
.
sock
:
self
.
sock
.
transport
.
close
()
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
chan
,
item
)
def
__str__
(
self
):
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
# def __del__(self):
# print("GC: Server object has been gc")
'''
class Server:
"""
SSH Server
Server object like client, a wrapper object, a connection to the asset,
Because we don't want to using python dynamic feature, such asset
have the chan and system_user attr.
...
...
@@ -199,6 +369,7 @@ class Server:
# def __del__(self):
# print("GC: Server object has been gc")
'''
class
WSProxy
:
...
...
coco/proxy.py
View file @
7295a354
...
...
@@ -8,8 +8,8 @@ import time
from
paramiko.ssh_exception
import
SSHException
from
.session
import
Session
from
.models
import
Server
from
.connection
import
SSHConnection
from
.models
import
Server
,
TelnetServer
from
.connection
import
SSHConnection
,
TelnetConnection
from
.ctx
import
current_app
,
app_service
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_warning
as
warning
,
\
get_logger
,
net_input
...
...
@@ -18,6 +18,8 @@ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
4096
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
class
ProxyServer
:
...
...
@@ -34,13 +36,24 @@ class ProxyServer:
"""
password
,
private_key
=
\
app_service
.
get_system_user_auth_info
(
system_user
)
if
not
password
and
not
private_key
:
if
system_user
.
login_mode
==
MANUAL_LOGIN
or
(
not
password
and
not
private_key
)
:
prompt
=
"{}'s password: "
.
format
(
system_user
.
username
)
password
=
net_input
(
self
.
client
,
prompt
=
prompt
,
sensitive
=
True
)
system_user
.
password
=
password
system_user
.
private_key
=
private_key
def
proxy
(
self
,
asset
,
system_user
):
if
asset
.
protocol
!=
system_user
.
protocol
:
msg
=
'System user <{}> and asset <{}> protocol are inconsistent.'
.
format
(
system_user
.
name
,
asset
.
hostname
)
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
return
if
system_user
.
login_mode
==
MANUAL_LOGIN
or
not
system_user
.
username
:
system_user_name
=
net_input
(
self
.
client
,
prompt
=
'username: '
,
before
=
1
)
system_user
.
username
=
system_user_name
self
.
get_system_user_auth
(
system_user
)
self
.
send_connecting_message
(
asset
,
system_user
)
self
.
server
=
self
.
get_server_conn
(
asset
,
system_user
)
...
...
@@ -74,15 +87,26 @@ class ProxyServer:
if
not
self
.
validate_permission
(
asset
,
system_user
):
self
.
client
.
send
(
warning
(
'No permission'
))
return
None
if
True
:
if
system_user
.
protocol
==
asset
.
protocol
==
'telnet'
:
server
=
self
.
get_telnet_server_conn
(
asset
,
system_user
)
elif
system_user
.
protocol
==
asset
.
protocol
==
'ssh'
:
server
=
self
.
get_ssh_server_conn
(
asset
,
system_user
)
else
:
server
=
self
.
get_ssh_server_conn
(
asset
,
system_user
)
server
=
None
return
server
# Todo: Support telnet
def
get_telnet_server_conn
(
self
,
asset
,
system_user
):
pass
telnet
=
TelnetConnection
(
asset
,
system_user
,
self
.
client
)
sock
,
msg
=
telnet
.
get_socket
()
if
not
sock
:
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
server
=
None
else
:
server
=
TelnetServer
(
sock
,
asset
,
system_user
)
# self.client.send(b'\r\n')
self
.
connecting
=
False
return
server
def
get_ssh_server_conn
(
self
,
asset
,
system_user
):
request
=
self
.
client
.
request
...
...
@@ -116,6 +140,8 @@ class ProxyServer:
break
def
watch_win_size_change_async
(
self
):
if
not
isinstance
(
self
.
server
,
Server
):
return
thread
=
threading
.
Thread
(
target
=
self
.
watch_win_size_change
)
thread
.
daemon
=
True
thread
.
start
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment