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
2444c6fd
Unverified
Commit
2444c6fd
authored
Dec 25, 2018
by
老广
Committed by
GitHub
Dec 25, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #151 from jumpserver/dev
Dev
parents
f98e6815
1eb34b69
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
299 additions
and
279 deletions
+299
-279
config.py
coco/config.py
+2
-2
connection.py
coco/connection.py
+19
-15
app.py
coco/httpd/app.py
+5
-2
__init__.py
coco/httpd/elfinder/__init__.py
+2
-0
base.py
coco/httpd/elfinder/volumes/base.py
+2
-0
sftp.py
coco/httpd/elfinder/volumes/sftp.py
+3
-1
ws.py
coco/httpd/ws.py
+2
-2
interactive.py
coco/interactive.py
+62
-93
interface.py
coco/interface.py
+10
-10
logger.py
coco/logger.py
+2
-1
models.py
coco/models.py
+34
-17
proxy.py
coco/proxy.py
+8
-6
recorder.py
coco/recorder.py
+16
-7
session.py
coco/session.py
+10
-9
sftp.py
coco/sftp.py
+9
-7
sshd.py
coco/sshd.py
+41
-41
struct.py
coco/struct.py
+3
-3
utils.py
coco/utils.py
+10
-19
cocod
cocod
+11
-6
coco.mo
locale/en/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/en/LC_MESSAGES/coco.po
+24
-19
coco.mo
locale/zh_CN/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/zh_CN/LC_MESSAGES/coco.po
+24
-19
No files found.
coco/config.py
View file @
2444c6fd
...
...
@@ -94,7 +94,7 @@ class Config(dict):
def
__init__
(
self
,
root_path
,
defaults
=
None
):
self
.
defaults
=
defaults
or
{}
self
.
root_path
=
root_path
super
()
.
__init__
({})
super
(
Config
,
self
)
.
__init__
({})
def
from_envvar
(
self
,
variable_name
,
silent
=
False
):
"""Loads a configuration from an environment variable pointing to
...
...
@@ -272,7 +272,7 @@ class Config(dict):
def
__getitem__
(
self
,
item
):
try
:
value
=
super
()
.
__getitem__
(
item
)
value
=
super
(
Config
,
self
)
.
__getitem__
(
item
)
except
KeyError
:
value
=
None
if
value
is
not
None
:
...
...
coco/connection.py
View file @
2444c6fd
...
...
@@ -4,9 +4,13 @@
import
os
import
re
import
socket
import
selectors
import
telnetlib
try
:
import
selectors
except
ImportError
:
import
selectors2
as
selectors
import
paramiko
from
paramiko.ssh_exception
import
SSHException
...
...
@@ -54,7 +58,7 @@ class SSHConnection:
asset
.
ip
,
port
=
asset
.
port
,
username
=
system_user
.
username
,
password
=
system_user
.
password
,
pkey
=
system_user
.
private_key
,
timeout
=
config
[
'SSH_TIMEOUT'
],
compress
=
Tru
e
,
auth_timeout
=
config
[
'SSH_TIMEOUT'
],
compress
=
Fals
e
,
auth_timeout
=
config
[
'SSH_TIMEOUT'
],
look_for_keys
=
False
,
sock
=
sock
)
except
paramiko
.
AuthenticationException
:
...
...
@@ -62,7 +66,7 @@ class SSHConnection:
ssh
.
connect
(
asset
.
ip
,
port
=
asset
.
port
,
username
=
system_user
.
username
,
password
=
system_user
.
password
,
timeout
=
config
[
'SSH_TIMEOUT'
],
compress
=
Tru
e
,
auth_timeout
=
config
[
'SSH_TIMEOUT'
],
compress
=
Fals
e
,
auth_timeout
=
config
[
'SSH_TIMEOUT'
],
look_for_keys
=
False
,
sock
=
sock
,
allow_agent
=
False
,
)
transport
=
ssh
.
get_transport
()
...
...
@@ -86,7 +90,7 @@ class SSHConnection:
password_short
,
key_fingerprint
,
))
return
None
,
None
,
error
+
'
\n
'
+
str
(
e
)
except
(
socket
.
error
,
TimeoutError
)
as
e
:
except
(
socket
.
error
,
socket
.
timeout
)
as
e
:
return
None
,
None
,
error
+
'
\n
'
+
str
(
e
)
return
ssh
,
sock
,
None
...
...
@@ -197,7 +201,7 @@ class TelnetConnection:
)
def
get_socket
(
self
):
logger
.
info
(
'Get telnet server socket. {}'
.
format
(
self
.
client
.
user
))
logger
.
debug
(
'Get telnet server socket. {}'
.
format
(
self
.
client
.
user
))
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
self
.
sock
.
settimeout
(
10
)
self
.
sock
.
connect
((
self
.
asset
.
ip
,
self
.
asset
.
port
))
...
...
@@ -211,7 +215,7 @@ class TelnetConnection:
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
)
logger
.
debug
(
b
'[Telnet server send]: '
+
data
)
if
not
data
:
self
.
sock
.
close
()
...
...
@@ -247,7 +251,7 @@ class TelnetConnection:
:param data: option negotiate data
:return:
"""
logger
.
info
(
b
'[Server options negotiate]: '
+
data
)
logger
.
debug
(
b
'[Server options negotiate]: '
+
data
)
data_list
=
data
.
split
(
telnetlib
.
IAC
)
new_data_list
=
[]
for
x
in
data_list
:
...
...
@@ -272,11 +276,11 @@ class TelnetConnection:
else
:
new_data_list
.
append
(
x
)
new_data
=
telnetlib
.
IAC
.
join
(
new_data_list
)
logger
.
info
(
b
'[Client options negotiate]: '
+
new_data
)
logger
.
debug
(
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
))
logger
.
debug
(
'[Telnet login auth]: ({})'
.
format
(
self
.
client
.
user
))
try
:
data
=
raw_data
.
decode
(
'utf-8'
)
...
...
@@ -284,23 +288,23 @@ class TelnetConnection:
try
:
data
=
raw_data
.
decode
(
'gbk'
)
except
UnicodeDecodeError
:
logger
.
info
(
b
'[Decode error]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
logger
.
debug
(
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
'<<'
)
logger
.
debug
(
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
'<<'
)
logger
.
debug
(
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
'<<'
)
logger
.
debug
(
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
'<<'
)
logger
.
debug
(
b
'[Login Success prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
True
else
:
logger
.
info
(
b
'[No match]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
logger
.
debug
(
b
'[No match]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
None
coco/httpd/app.py
View file @
2444c6fd
# -*- coding: utf-8 -*-
#
import
os
from
flask_socketio
import
SocketIO
from
flask
import
Flask
...
...
@@ -17,8 +18,10 @@ socket_io = SocketIO()
socket_io
.
on_namespace
(
ProxyNamespace
(
'/ssh'
))
socket_io
.
on_namespace
(
ElfinderNamespace
(
'/elfinder'
))
# init_kwargs = {'async_mode': 'threading'}
init_kwargs
=
{
'async_mode'
:
'eventlet'
}
if
os
.
environ
.
get
(
'USE_EVENTLET'
,
'1'
)
==
'1'
:
init_kwargs
=
{
'async_mode'
:
'eventlet'
}
else
:
init_kwargs
=
{
'async_mode'
:
'threading'
}
socket_io
.
init_app
(
app
,
**
init_kwargs
),
socket_io
.
on_error_default
(
lambda
x
:
logger
.
exception
(
x
))
...
...
coco/httpd/elfinder/__init__.py
0 → 100644
View file @
2444c6fd
# -*- coding: utf-8 -*-
#
coco/httpd/elfinder/volumes/base.py
View file @
2444c6fd
# -*- coding: utf-8 -*-
#
import
base64
import
os
import
hashlib
...
...
coco/httpd/elfinder/volumes/sftp.py
View file @
2444c6fd
# -*- coding: utf-8 -*-
#
import
stat
import
threading
...
...
@@ -14,9 +16,9 @@ class SFTPVolume(BaseVolume):
def
__init__
(
self
,
sftp
):
self
.
sftp
=
sftp
self
.
root_name
=
'Home'
super
()
.
__init__
()
self
.
_stat_cache
=
{}
self
.
lock
=
threading
.
Lock
()
super
(
SFTPVolume
,
self
)
.
__init__
()
def
close
(
self
):
self
.
sftp
.
close
()
...
...
coco/httpd/ws.py
View file @
2444c6fd
...
...
@@ -28,7 +28,7 @@ class ProxyNamespace(BaseNamespace):
...
}
"""
super
()
.
__init__
(
*
args
,
**
kwargs
)
super
(
BaseNamespace
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
win_size
=
None
def
new_connection
(
self
):
...
...
@@ -46,7 +46,7 @@ class ProxyNamespace(BaseNamespace):
def
on_connect
(
self
):
logger
.
debug
(
"On connect event trigger"
)
self
.
get_current_user
()
super
()
.
on_connect
()
super
(
ProxyNamespace
,
self
)
.
on_connect
()
self
.
new_connection
()
def
on_host
(
self
,
message
):
...
...
coco/interactive.py
View file @
2444c6fd
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
from
__future__
import
unicode_literals
import
socket
import
threading
...
...
@@ -28,20 +29,20 @@ PROXY = 'proxy'
class
InteractiveServer
:
_sentinel
=
object
()
_user_assets_cached
=
{}
def
__init__
(
self
,
client
):
self
.
client
=
client
self
.
closed
=
False
self
.
_results
=
None
self
.
nodes
=
None
self
.
offset
=
0
self
.
limit
=
100
self
.
assets
=
[]
self
.
finish
=
False
self
.
assets
=
None
self
.
get_user_assets_finished
=
False
self
.
page
=
1
self
.
total_asset
s
=
0
# 用户被授权的所有资产
self
.
total_asset
_count
=
0
# 用户被授权的所有资产数量
self
.
total_count
=
0
# 分页展示中的资产总数量
self
.
node_tree
=
None
# 授权节点树
self
.
load_user_assets_from_cache
()
self
.
get_user_assets_async
()
self
.
get_user_nodes_async
()
...
...
@@ -82,7 +83,7 @@ class InteractiveServer:
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
self
.
display_logo
()
header
=
_
(
"
\n
{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system {end}{R}{R}"
)
menu
s
=
[
menu
=
[
_
(
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"
),
_
(
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"
),
_
(
"{T}3) Enter {green}p{end} to display the host you have permission.{R}"
),
...
...
@@ -97,8 +98,8 @@ class InteractiveServer:
title
=
"
\033
[1;32m"
,
user
=
self
.
client
.
user
,
end
=
"
\033
[0m"
,
T
=
'
\t
'
,
R
=
'
\r\n\r
'
))
for
menu
in
menus
:
self
.
client
.
send
(
menu
.
format
(
for
item
in
menu
:
self
.
client
.
send
(
item
.
format
(
green
=
"
\033
[32m"
,
end
=
"
\033
[0m"
,
T
=
'
\t
'
,
R
=
'
\r\n\r
'
))
...
...
@@ -143,7 +144,7 @@ class InteractiveServer:
def
search_and_display_assets
(
self
,
q
):
assets
=
self
.
search_assets
(
q
)
self
.
display_assets
(
assets
)
self
.
display_assets
_paging
(
assets
)
def
search_and_proxy_assets
(
self
,
opt
):
assets
=
self
.
search_assets
(
opt
)
...
...
@@ -158,25 +159,13 @@ class InteractiveServer:
return
self
.
proxy
(
asset
)
else
:
self
.
display_assets
(
assets
)
self
.
display_assets
_paging
(
assets
)
def
refresh_assets_nodes
(
self
):
self
.
get_user_assets_async
()
self
.
get_user_nodes_async
()
def
search_assets
(
self
,
q
):
if
self
.
finish
:
assets
=
self
.
search_assets_from_local
(
q
)
else
:
assets
=
self
.
search_assets_from_core
(
q
)
return
assets
def
search_assets_from_core
(
self
,
q
):
assets
=
app_service
.
get_search_user_granted_assets
(
self
.
client
.
user
,
q
)
assets
=
self
.
filter_system_users
(
assets
)
return
assets
def
search_assets_from_local
(
self
,
q
):
result
=
[]
# 所有的
...
...
@@ -204,20 +193,17 @@ class InteractiveServer:
# Display assets
#
def
display_assets
(
self
,
assets
=
None
):
if
assets
is
None
:
while
not
self
.
assets
and
not
self
.
finish
:
time
.
sleep
(
0.2
)
assets
=
self
.
assets
self
.
display_assets_paging
(
assets
)
def
display_assets
(
self
):
while
self
.
assets
is
None
and
not
self
.
get_user_assets_finished
:
time
.
sleep
(
0.5
)
if
self
.
assets
:
self
.
display_assets_paging
(
self
.
assets
)
def
display_assets_paging
(
self
,
assets
):
if
len
(
assets
)
==
0
:
self
.
client
.
send
(
wr
(
_
(
"No Assets"
),
before
=
0
))
return
self
.
total_count
=
self
.
total_assets
if
assets
is
self
.
assets
else
len
(
assets
)
self
.
total_count
=
len
(
assets
)
action
=
None
gen
=
self
.
_page_generator
(
assets
)
...
...
@@ -238,14 +224,8 @@ class InteractiveServer:
start
,
page
=
0
,
1
while
True
:
_assets
=
assets
[
start
:
start
+
self
.
page_size
]
# 等待加载
if
(
assets
is
self
.
assets
)
and
(
not
self
.
finish
)
and
(
not
self
.
need_paging
):
time
.
sleep
(
1
)
continue
# 最后一页
elif
_assets
and
(
page
==
self
.
total_pages
)
and
(
assets
is
not
self
.
assets
or
(
assets
is
self
.
assets
and
self
.
finish
)):
if
page
==
self
.
total_pages
:
return
page
,
_assets
# 执行动作
else
:
...
...
@@ -273,33 +253,8 @@ class InteractiveServer:
def
display_a_page_assets
(
self
,
page
,
assets
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
self
.
page
=
page
self
.
results
=
assets
self
.
display_results
()
def
display_page_bottom_prompt
(
self
):
self
.
client
.
send
(
wr
(
_
(
'Tips: Enter the asset ID and log directly into the asset.'
),
before
=
1
))
prompt_page_up
=
_
(
"Page up: P/p"
)
prompt_page_down
=
_
(
"Page down: Enter|N/n"
)
prompt_back
=
_
(
"BACK: b/q"
)
prompts
=
[
prompt_page_up
,
prompt_page_down
,
prompt_back
]
prompt
=
'
\t
'
.
join
(
prompts
)
self
.
client
.
send
(
wr
(
prompt
,
before
=
1
))
def
get_user_action
(
self
):
opt
=
net_input
(
self
.
client
,
prompt
=
':'
)
if
opt
in
(
'p'
,
'P'
):
return
PAGE_UP
elif
opt
in
(
'b'
,
'q'
):
return
BACK
elif
opt
.
isdigit
()
and
self
.
results
and
0
<
int
(
opt
)
<=
len
(
self
.
results
):
self
.
proxy
(
self
.
results
[
int
(
opt
)
-
1
])
return
BACK
else
:
return
PAGE_DOWN
def
display_results
(
self
):
sort_by
=
config
[
"ASSET_LIST_SORT_BY"
]
self
.
results
=
sort_assets
(
self
.
resul
ts
,
sort_by
)
self
.
results
=
sort_assets
(
asse
ts
,
sort_by
)
fake_data
=
[
_
(
"ID"
),
_
(
"Hostname"
),
_
(
"IP"
),
_
(
"LoginAs"
)]
id_length
=
max
(
len
(
str
(
len
(
self
.
results
))),
4
)
hostname_length
=
item_max_length
(
self
.
results
,
15
,
...
...
@@ -323,44 +278,59 @@ class InteractiveServer:
]
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
title
(
_
(
"Page: {}, Count: {}, Total Page: {}, Total Count: {}"
)
.
format
(
self
.
page
,
len
(
self
.
results
),
self
.
total_pages
,
self
.
total_count
)),
before
=
1
)
self
.
client
.
send
(
wr
(
title
(
_
(
"Page: {}, Count: {}, Total Page: {}, Total Count: {}"
)
.
format
(
self
.
page
,
len
(
self
.
results
),
self
.
total_pages
,
self
.
total_count
)),
before
=
1
)
)
def
display_page_bottom_prompt
(
self
):
msg
=
wr
(
_
(
'Tips: Enter the asset ID and log directly into the asset.'
),
before
=
1
)
self
.
client
.
send
(
msg
)
prompt_page_up
=
_
(
"Page up: P/p"
)
prompt_page_down
=
_
(
"Page down: Enter|N/n"
)
prompt_back
=
_
(
"BACK: b/q"
)
prompts
=
[
prompt_page_up
,
prompt_page_down
,
prompt_back
]
prompt
=
'
\t
'
.
join
(
prompts
)
self
.
client
.
send
(
wr
(
prompt
,
before
=
1
))
def
get_user_action
(
self
):
opt
=
net_input
(
self
.
client
,
prompt
=
':'
)
if
opt
in
(
'p'
,
'P'
):
return
PAGE_UP
elif
opt
in
(
'b'
,
'q'
):
return
BACK
elif
opt
.
isdigit
()
and
self
.
results
and
0
<
int
(
opt
)
<=
len
(
self
.
results
):
self
.
proxy
(
self
.
results
[
int
(
opt
)
-
1
])
return
BACK
else
:
return
PAGE_DOWN
#
# Get assets
#
def
load_user_assets_from_cache
(
self
):
assets
=
self
.
__class__
.
_user_assets_cached
.
get
(
self
.
client
.
user
.
id
)
self
.
assets
=
assets
if
assets
:
self
.
total_asset_count
=
len
(
assets
)
def
set_user_assets_cache
(
self
,
assets
):
self
.
__class__
.
_user_assets_cached
[
self
.
client
.
user
.
id
]
=
assets
def
get_user_assets_async
(
self
):
if
self
.
need_paging
:
thread
=
threading
.
Thread
(
target
=
self
.
get_user_assets_paging
)
else
:
thread
=
threading
.
Thread
(
target
=
self
.
get_user_assets_direct
)
thread
=
threading
.
Thread
(
target
=
self
.
get_user_assets
)
thread
.
start
()
def
get_user_assets
_direct
(
self
):
def
get_user_assets
(
self
):
assets
=
app_service
.
get_user_assets
(
self
.
client
.
user
)
assets
=
self
.
filter_system_users
(
assets
)
self
.
assets
=
assets
self
.
total_assets
=
len
(
assets
)
self
.
finish
=
True
def
get_user_assets_paging
(
self
):
while
not
self
.
closed
:
assets
,
total
=
app_service
.
get_user_assets_paging
(
self
.
client
.
user
,
offset
=
self
.
offset
,
limit
=
self
.
limit
)
if
not
assets
:
logger
.
info
(
'Get user assets paging async finished.'
)
self
.
finish
=
True
break
logger
.
info
(
'Get user assets paging async: {}'
.
format
(
len
(
assets
)))
assets
=
self
.
filter_system_users
(
assets
)
self
.
total_assets
=
total
self
.
assets
.
extend
(
assets
)
self
.
offset
+=
self
.
limit
self
.
set_user_assets_cache
(
assets
)
self
.
load_user_assets_from_cache
()
self
.
get_user_assets_finished
=
True
#
# Nodes
#
...
...
@@ -415,7 +385,7 @@ class InteractiveServer:
return
assets
=
self
.
nodes
[
_id
-
1
]
.
assets_granted
self
.
display_assets
(
assets
)
self
.
display_assets
_paging
(
assets
)
#
# System users
...
...
@@ -487,7 +457,6 @@ class InteractiveServer:
def
close
(
self
):
logger
.
debug
(
"Interactive server server close: {}"
.
format
(
self
))
self
.
closed
=
True
# current_app.remove_client(self.client)
def
interact_async
(
self
):
# 目前没用
...
...
coco/interface.py
View file @
2444c6fd
...
...
@@ -30,14 +30,14 @@ class SSHInterface(paramiko.ServerInterface):
self
.
user
=
None
def
check_auth_interactive
(
self
,
username
,
submethods
):
logger
.
info
(
"Check auth interactive:
%
s
%
s"
%
(
username
,
submethods
))
logger
.
debug
(
"Check auth interactive:
%
s
%
s"
%
(
username
,
submethods
))
instructions
=
'Please enter 6 digits.'
interactive
=
paramiko
.
server
.
InteractiveQuery
(
instructions
=
instructions
)
interactive
.
add_prompt
(
prompt
=
'[MFA auth]: '
)
return
interactive
def
check_auth_interactive_response
(
self
,
responses
):
logger
.
info
(
"Check auth interactive response:
%
s "
%
responses
)
logger
.
debug
(
"Check auth interactive response:
%
s "
%
responses
)
# TODO:MFA Auth
otp_code
=
responses
[
0
]
if
not
otp_code
or
not
len
(
otp_code
)
==
6
or
not
otp_code
.
isdigit
():
...
...
@@ -90,7 +90,7 @@ class SSHInterface(paramiko.ServerInterface):
logger
.
debug
(
"Public key auth <
%
s> failed, try to password"
%
username
)
return
paramiko
.
AUTH_FAILED
else
:
logger
.
debug
(
"Public key auth <
%
s> success"
%
username
)
logger
.
info
(
"Public key auth <
%
s> success"
%
username
)
if
self
.
otp_auth
:
return
paramiko
.
AUTH_PARTIALLY_SUCCESSFUL
return
paramiko
.
AUTH_SUCCESSFUL
...
...
@@ -149,14 +149,14 @@ class SSHInterface(paramiko.ServerInterface):
return
0
def
check_port_forward_request
(
self
,
address
,
port
):
logger
.
info
(
logger
.
debug
(
"Check channel port forward request:
%
s
%
s"
%
(
address
,
port
)
)
self
.
event
.
set
()
return
False
def
check_channel_request
(
self
,
kind
,
chan_id
):
logger
.
info
(
"Check channel request:
%
s
%
d"
%
(
kind
,
chan_id
))
logger
.
debug
(
"Check channel request:
%
s
%
d"
%
(
kind
,
chan_id
))
client
=
self
.
connection
.
new_client
(
chan_id
)
client
.
request
.
kind
=
kind
return
paramiko
.
OPEN_SUCCEEDED
...
...
@@ -188,7 +188,7 @@ class SSHInterface(paramiko.ServerInterface):
def
check_channel_pty_request
(
self
,
channel
,
term
,
width
,
height
,
pixelwidth
,
pixelheight
,
modes
):
logger
.
info
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s"
%
logger
.
debug
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s"
%
(
term
,
width
,
height
,
pixelwidth
,
pixelheight
))
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
type
=
'pty'
...
...
@@ -201,18 +201,18 @@ class SSHInterface(paramiko.ServerInterface):
return
True
def
check_channel_shell_request
(
self
,
channel
):
logger
.
info
(
"Check channel shell request:
%
s"
%
channel
.
get_id
())
logger
.
debug
(
"Check channel shell request:
%
s"
%
channel
.
get_id
())
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
meta
[
'shell'
]
=
True
return
True
def
check_channel_subsystem_request
(
self
,
channel
,
name
):
logger
.
info
(
"Check channel subsystem request:
%
s"
%
name
)
logger
.
debug
(
"Check channel subsystem request:
%
s"
%
name
)
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
type
=
'subsystem'
client
.
request
.
meta
[
'subsystem'
]
=
name
self
.
event
.
set
()
return
super
()
.
check_channel_subsystem_request
(
channel
,
name
)
return
super
(
SSHInterface
,
self
)
.
check_channel_subsystem_request
(
channel
,
name
)
def
check_channel_window_change_request
(
self
,
channel
,
width
,
height
,
pixelwidth
,
pixelheight
):
...
...
@@ -228,7 +228,7 @@ class SSHInterface(paramiko.ServerInterface):
def
check_channel_x11_request
(
self
,
channel
,
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
):
logger
.
info
(
"Check channel x11 request
%
s
%
s
%
s
%
s"
%
logger
.
debug
(
"Check channel x11 request
%
s
%
s
%
s
%
s"
%
(
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
))
client
=
self
.
connection
.
get_client
(
channel
)
...
...
coco/logger.py
View file @
2444c6fd
...
...
@@ -51,12 +51,13 @@ def create_logger():
},
loggers
=
{
'coco'
:
main_setting
,
'paramiko'
:
main_setting
,
'jms'
:
main_setting
,
# 'socket.io': main_setting,
# 'engineio': main_setting,
}
)
if
level
.
lower
()
==
'debug'
:
config
[
'loggers'
][
'paramiko'
]
=
main_setting
dictConfig
(
config
)
logger
=
logging
.
getLogger
()
return
logger
...
...
coco/models.py
View file @
2444c6fd
...
...
@@ -8,14 +8,14 @@ from .service import app_service
from
.struct
import
SizedList
,
SelectEvent
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_warning
as
warning
,
\
ugettext
as
_
from
.
import
char
from
.
import
utils
from
.
import
char
,
utils
from
.
compat
import
str
BUF_SIZE
=
4096
logger
=
utils
.
get_logger
(
__file__
)
class
Connection
:
class
Connection
(
object
)
:
connections
=
{}
clients_num
=
0
...
...
@@ -80,6 +80,8 @@ class Connection:
@classmethod
def
remove_connection
(
cls
,
cid
):
connection
=
cls
.
get_connection
(
cid
)
if
not
connection
:
return
connection
.
close
()
del
cls
.
connections
[
cid
]
...
...
@@ -88,7 +90,7 @@ class Connection:
return
cls
.
connections
.
get
(
cid
)
class
Request
:
class
Request
(
object
)
:
def
__init__
(
self
):
self
.
type
=
None
self
.
x11
=
None
...
...
@@ -96,7 +98,7 @@ class Request:
self
.
meta
=
{
'env'
:
{}}
class
Client
:
class
Client
(
object
)
:
"""
Client is the request client. Nothing more to say
...
...
@@ -129,12 +131,17 @@ class Client:
self
.
close
()
return
@property
def
closed
(
self
):
return
self
.
chan
.
closed
def
recv
(
self
,
size
):
return
self
.
chan
.
recv
(
size
)
def
close
(
self
):
logger
.
info
(
"Client {} close"
.
format
(
self
))
return
self
.
chan
.
close
()
self
.
chan
.
close
()
return
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
chan
,
item
)
...
...
@@ -143,12 +150,12 @@ class Client:
return
"<
%
s from
%
s:
%
s>"
%
(
self
.
user
,
self
.
addr
[
0
],
self
.
addr
[
1
])
class
ServerFilter
:
class
ServerFilter
(
object
)
:
def
run
(
self
,
data
):
pass
class
BaseServer
:
class
BaseServer
(
object
)
:
"""
Base Server
Achieve command record
...
...
@@ -238,22 +245,31 @@ class BaseServer:
return
data
if
not
self
.
_input
:
return
data
if
self
.
_cmd_filter_rules
is
None
:
msg
=
_
(
"Warning: Failed to load filter rule, "
"please press Ctrl + D to exit retry."
)
data
=
self
.
command_forbidden
(
msg
)
return
data
for
rule
in
self
.
_cmd_filter_rules
:
action
,
cmd
=
rule
.
match
(
self
.
_input
)
if
action
==
rule
.
ALLOW
:
break
elif
action
==
rule
.
DENY
:
data
=
char
.
CLEAR_LINE_CHAR
+
b
'
\r
'
msg
=
_
(
"Command `{}` is forbidden ........"
)
.
format
(
cmd
)
msg
=
wr
(
warning
(
msg
.
encode
()),
before
=
1
,
after
=
1
)
self
.
output_data
.
append
(
msg
)
self
.
session
.
send_to_clients
(
msg
)
self
.
session
.
put_command
(
self
.
_input
,
msg
.
decode
())
self
.
session
.
put_replay
(
msg
)
self
.
input_data
.
clean
()
self
.
command_forbidden
(
msg
)
break
return
data
def
command_forbidden
(
self
,
msg
):
data
=
char
.
CLEAR_LINE_CHAR
+
b
'
\r
'
msg
=
wr
(
warning
(
msg
.
encode
()),
before
=
1
,
after
=
1
)
self
.
output_data
.
append
(
msg
)
self
.
session
.
send_to_clients
(
msg
)
self
.
session
.
put_command
(
self
.
_input
,
msg
.
decode
())
self
.
session
.
put_replay
(
msg
)
self
.
input_data
.
clean
()
return
data
def
r_replay_filter
(
self
,
data
):
if
not
self
.
_zmodem_state
:
self
.
session
.
put_replay
(
data
)
...
...
@@ -382,13 +398,14 @@ class Server(BaseServer):
super
(
Server
,
self
)
.
__init__
(
chan
=
chan
)
def
close
(
self
):
super
()
.
close
()
super
(
Server
,
self
)
.
close
()
self
.
chan
.
transport
.
close
()
logger
.
debug
(
"Backend server closed"
)
if
self
.
sock
:
self
.
sock
.
transport
.
close
()
class
WSProxy
:
class
WSProxy
(
object
)
:
def
__init__
(
self
,
ws
,
client_id
):
self
.
ws
=
ws
self
.
client_id
=
client_id
...
...
coco/proxy.py
View file @
2444c6fd
...
...
@@ -52,7 +52,7 @@ class ProxyServer:
return
False
return
True
def
manual_s
et_system_user_username_if_need
(
self
):
def
g
et_system_user_username_if_need
(
self
):
if
self
.
system_user
.
login_mode
==
MANUAL_LOGIN
and
\
not
self
.
system_user
.
username
:
username
=
net_input
(
self
.
client
,
prompt
=
'username: '
,
before
=
1
)
...
...
@@ -63,15 +63,17 @@ class ProxyServer:
def
proxy
(
self
):
if
not
self
.
check_protocol
():
return
self
.
manual_s
et_system_user_username_if_need
()
self
.
g
et_system_user_username_if_need
()
self
.
get_system_user_auth_or_manual_set
()
self
.
server
=
self
.
get_server_conn
()
if
self
.
server
is
None
:
return
session
=
Session
.
new_session
(
self
.
client
,
self
.
server
)
session
.
bridge
()
Session
.
remove_session
(
session
.
id
)
self
.
server
.
close
()
try
:
session
.
bridge
()
finally
:
Session
.
remove_session
(
session
.
id
)
self
.
server
.
close
()
def
validate_permission
(
self
):
"""
...
...
@@ -83,7 +85,7 @@ class ProxyServer:
)
def
get_server_conn
(
self
):
logger
.
info
(
"Connect to {}
"
.
format
(
self
.
asset
.
hostname
))
logger
.
info
(
"Connect to {}
:{} ..."
.
format
(
self
.
asset
.
hostname
,
self
.
asset
.
port
))
self
.
send_connecting_message
()
if
not
self
.
validate_permission
():
self
.
client
.
send
(
warning
(
_
(
'No permission'
)))
...
...
coco/recorder.py
View file @
2444c6fd
...
...
@@ -21,12 +21,12 @@ logger = get_logger(__file__)
BUF_SIZE
=
1024
class
ReplayRecorder
(
metaclass
=
abc
.
ABCMeta
):
class
ReplayRecorder
(
object
):
time_start
=
None
storage
=
None
def
__init__
(
self
):
super
()
.
__init__
()
super
(
ReplayRecorder
,
self
)
.
__init__
()
self
.
file
=
None
self
.
file_path
=
None
self
.
get_storage
()
...
...
@@ -82,7 +82,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
self
.
upload_replay
(
session_id
,
times
-
1
)
else
:
msg
=
'Success push replay file: {}'
.
format
(
session_id
)
logger
.
info
(
msg
)
logger
.
debug
(
msg
)
self
.
finish_replay
(
3
,
session_id
)
os
.
unlink
(
self
.
file_path
)
return
True
...
...
@@ -101,23 +101,32 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
if
app_service
.
finish_replay
(
session_id
):
logger
.
info
(
"Success finish session {}'s replay "
.
format
(
session_id
)
"Success finish
ed
session {}'s replay "
.
format
(
session_id
)
)
return
True
else
:
msg
=
"Failed finish session {}'s replay, try {} times"
msg
=
"Failed finish
ed
session {}'s replay, try {} times"
logger
.
error
(
msg
.
format
(
session_id
,
times
))
return
self
.
finish_replay
(
times
-
1
,
session_id
)
class
CommandRecorder
(
metaclass
=
Singleton
):
class
CommandRecorder
(
object
):
batch_size
=
10
timeout
=
5
no
=
0
storage
=
None
_cache
=
[]
def
__new__
(
cls
,
*
args
,
**
kwargs
):
if
cls
.
_cache
:
return
cls
.
_cache
[
0
]
else
:
self
=
super
(
CommandRecorder
,
cls
)
.
__new__
(
cls
,
*
args
,
**
kwargs
)
cls
.
_cache
.
append
(
self
)
return
self
def
__init__
(
self
):
super
()
.
__init__
()
super
(
CommandRecorder
,
self
)
.
__init__
()
self
.
queue
=
MemoryQueue
()
self
.
stop_evt
=
threading
.
Event
()
self
.
push_to_server_async
()
...
...
coco/session.py
View file @
2444c6fd
...
...
@@ -3,9 +3,13 @@
#
import
uuid
import
datetime
import
selectors
import
time
try
:
import
selectors
except
ImportError
:
import
selectors2
as
selectors
from
.utils
import
get_logger
,
wrap_with_warning
as
warn
,
\
wrap_with_line_feed
as
wr
,
ugettext
as
_
,
ignore_error
from
.service
import
app_service
...
...
@@ -68,14 +72,14 @@ class Session:
:param silent: If true not send welcome message
:return:
"""
logger
.
info
(
"Session add watcher: {} -> {} "
.
format
(
self
.
id
,
watcher
))
logger
.
debug
(
"Session add watcher: {} -> {} "
.
format
(
self
.
id
,
watcher
))
if
not
silent
:
watcher
.
send
(
"Welcome to watch session {}
\r\n
"
.
format
(
self
.
id
)
.
encode
())
self
.
sel
.
register
(
watcher
,
selectors
.
EVENT_READ
)
self
.
_watchers
.
append
(
watcher
)
def
remove_watcher
(
self
,
watcher
):
logger
.
info
(
"Session
%
s remove watcher
%
s"
%
(
self
.
id
,
watcher
))
logger
.
debug
(
"Session
%
s remove watcher
%
s"
%
(
self
.
id
,
watcher
))
self
.
sel
.
unregister
(
watcher
)
self
.
_watchers
.
remove
(
watcher
)
...
...
@@ -86,7 +90,7 @@ class Session:
:param silent: If true not send welcome message
:return:
"""
logger
.
info
(
"Session
%
s add share
%
s"
%
(
self
.
id
,
sharer
))
logger
.
debug
(
"Session
%
s add share
%
s"
%
(
self
.
id
,
sharer
))
if
not
silent
:
sharer
.
send
(
"Welcome to join session: {}
\r\n
"
.
format
(
self
.
id
)
.
encode
(
"utf-8"
))
...
...
@@ -190,7 +194,7 @@ class Session:
break
elif
sock
==
self
.
client
.
change_size_evt
:
self
.
resize_win_size
()
logger
.
info
(
"Session stop event set: {}"
.
format
(
self
.
id
))
logger
.
debug
(
"Session stop event set: {}"
.
format
(
self
.
id
))
def
resize_win_size
(
self
):
width
,
height
=
self
.
client
.
request
.
meta
[
'width'
],
\
...
...
@@ -201,7 +205,7 @@ class Session:
@ignore_error
def
close
(
self
):
if
self
.
closed
:
logger
.
info
(
"Session has been closed: {} "
.
format
(
self
.
id
))
logger
.
debug
(
"Session has been closed: {} "
.
format
(
self
.
id
))
return
logger
.
info
(
"Close the session: {} "
.
format
(
self
.
id
))
self
.
is_finished
=
True
...
...
@@ -228,6 +232,3 @@ class Session:
def
__repr__
(
self
):
return
self
.
id
# def __del__(self):
# print("GC: Session object has been GC")
coco/sftp.py
View file @
2444c6fd
...
...
@@ -62,10 +62,11 @@ class SFTPServer(paramiko.SFTPServerInterface):
}
"""
super
()
.
__init__
(
server
,
**
kwargs
)
super
(
SFTPServer
,
self
)
.
__init__
(
server
,
**
kwargs
)
self
.
server
=
server
self
.
_sftp
=
{}
self
.
hosts
=
self
.
get_permed_hosts
()
self
.
is_finished
=
False
def
get_permed_hosts
(
self
):
hosts
=
{}
...
...
@@ -88,7 +89,8 @@ class SFTPServer(paramiko.SFTPServerInterface):
return
hosts
def
session_ended
(
self
):
super
()
.
session_ended
()
self
.
is_finished
=
True
super
(
SFTPServer
,
self
)
.
session_ended
()
for
_
,
v
in
self
.
_sftp
.
items
():
sftp
=
v
[
'client'
]
proxy
=
v
.
get
(
'proxy'
)
...
...
@@ -144,7 +146,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
if
len
(
data
)
==
1
and
not
data
[
0
]:
return
request
host
,
*
path
=
data
host
,
path
=
data
[
0
],
data
[
1
:]
request
[
"host"
]
=
host
unique
,
su
=
self
.
host_has_unique_su
(
host
)
if
unique
:
...
...
@@ -388,7 +390,7 @@ class InternalSFTPClient(SFTPServer):
def
__init__
(
self
,
connection
):
fake_server
=
FakeServer
()
fake_server
.
connection
=
connection
super
()
.
__init__
(
fake_server
)
super
(
InternalSFTPClient
,
self
)
.
__init__
(
fake_server
)
def
listdir_attr
(
self
,
path
):
return
self
.
list_folder
.
__wrapped__
(
self
,
path
)
...
...
@@ -408,15 +410,15 @@ class InternalSFTPClient(SFTPServer):
self
.
create_ftp_log
(
path
,
operate
,
success
)
def
stat
(
self
,
path
):
attr
=
super
()
.
stat
.
__wrapped__
(
self
,
path
)
attr
=
super
(
InternalSFTPClient
,
self
)
.
stat
.
__wrapped__
(
self
,
path
)
return
attr
def
lstat
(
self
,
path
):
attr
=
super
()
.
lstat
.
__wrapped__
(
self
,
path
)
attr
=
super
(
InternalSFTPClient
,
self
)
.
lstat
.
__wrapped__
(
self
,
path
)
return
attr
def
rmdir
(
self
,
path
):
return
super
()
.
rmdir
.
__wrapped__
(
self
,
path
)
return
super
(
InternalSFTPClient
,
self
)
.
rmdir
.
__wrapped__
(
self
,
path
)
def
get_channel
(
self
):
return
FakeChannel
.
new
()
...
...
coco/sshd.py
View file @
2444c6fd
...
...
@@ -5,6 +5,7 @@
import
os
import
socket
import
threading
import
time
import
paramiko
...
...
@@ -25,7 +26,6 @@ class SSHServer:
self
.
stop_evt
=
threading
.
Event
()
self
.
workers
=
[]
self
.
pipe
=
None
self
.
connections
=
[]
@property
def
host_key
(
self
):
...
...
@@ -57,11 +57,6 @@ class SSHServer:
except
IndexError
as
e
:
logger
.
error
(
"Start SSH server error: {}"
.
format
(
e
))
def
new_connection
(
self
,
addr
,
sock
):
connection
=
Connection
.
new_connection
(
addr
=
addr
,
sock
=
sock
)
self
.
connections
.
append
(
connection
)
return
connection
def
handle_connection
(
self
,
sock
,
addr
):
logger
.
debug
(
"Handle new connection from: {}"
.
format
(
addr
))
transport
=
paramiko
.
Transport
(
sock
,
gss_kex
=
False
)
...
...
@@ -74,55 +69,60 @@ class SSHServer:
transport
.
set_subsystem_handler
(
'sftp'
,
paramiko
.
SFTPServer
,
SFTPServer
)
connection
=
self
.
new_connection
(
addr
,
sock
=
sock
)
connection
=
Connection
.
new_connection
(
addr
=
addr
,
sock
=
sock
)
server
=
SSHInterface
(
connection
)
try
:
transport
.
start_server
(
server
=
server
)
except
(
paramiko
.
SSHException
,
socket
.
timeout
):
while
transport
.
is_active
():
chan
=
transport
.
accept
()
server
.
event
.
wait
(
5
)
if
chan
is
None
:
continue
if
not
server
.
event
.
is_set
():
logger
.
warning
(
"Client not request invalid, exiting"
)
sock
.
close
()
continue
else
:
server
.
event
.
clear
()
client
=
connection
.
clients
.
get
(
chan
.
get_id
())
client
.
chan
=
chan
t
=
threading
.
Thread
(
target
=
self
.
dispatch
,
args
=
(
client
,))
t
.
daemon
=
True
t
.
start
()
transport
.
close
()
except
(
paramiko
.
SSHException
,
sock
.
timeout
):
logger
.
warning
(
"SSH negotiation failed"
)
return
except
EOFError
as
e
:
logger
.
warning
(
"Handle EOF Error: {}"
.
format
(
e
))
return
while
transport
.
is_active
():
chan
=
transport
.
accept
()
server
.
event
.
wait
(
5
)
if
chan
is
None
:
continue
if
not
server
.
event
.
is_set
():
logger
.
warning
(
"Client not request invalid, exiting"
)
sock
.
close
()
return
else
:
server
.
event
.
clear
()
client
=
connection
.
clients
.
get
(
chan
.
get_id
())
client
.
chan
=
chan
t
=
threading
.
Thread
(
target
=
self
.
dispatch
,
args
=
(
client
,))
t
.
daemon
=
True
t
.
start
()
Connection
.
remove_connection
(
connection
.
id
)
finally
:
Connection
.
remove_connection
(
connection
.
id
)
sock
.
close
()
@staticmethod
def
dispatch
(
client
):
supported
=
{
'pty'
,
'x11'
,
'forward-agent'
}
chan_type
=
client
.
request
.
type
kind
=
client
.
request
.
kind
if
kind
==
'session'
and
chan_type
in
supported
:
logger
.
info
(
"Request type `{}:{}`, dispatch to interactive mode"
.
format
(
kind
,
chan_type
))
try
:
InteractiveServer
(
client
)
.
interact
()
except
Exception
as
e
:
logger
.
error
(
"Unexpected error occur: {}"
.
format
(
e
))
try
:
if
kind
==
'session'
and
chan_type
in
supported
:
logger
.
info
(
"Dispatch client to interactive mode"
)
try
:
InteractiveServer
(
client
)
.
interact
()
except
IndexError
as
e
:
logger
.
error
(
"Unexpected error occur: {}"
.
format
(
e
))
elif
chan_type
==
'subsystem'
:
while
not
client
.
closed
:
time
.
sleep
(
5
)
logger
.
debug
(
"SFTP session finished"
)
else
:
msg
=
"Request type `{}:{}` not support now"
.
format
(
kind
,
chan_type
)
logger
.
error
(
msg
)
client
.
send
(
msg
)
finally
:
connection
=
Connection
.
get_connection
(
client
.
connection_id
)
connection
.
remove_client
(
client
.
id
)
elif
chan_type
==
'subsystem'
:
pass
else
:
msg
=
"Request type `{}:{}` not support now"
.
format
(
kind
,
chan_type
)
logger
.
info
(
msg
)
client
.
send
(
msg
)
def
shutdown
(
self
):
self
.
stop_evt
.
set
()
...
...
coco/struct.py
View file @
2444c6fd
...
...
@@ -21,7 +21,7 @@ class MultiQueueMixin:
self
.
put
(
i
)
class
MemoryQueue
(
MultiQueueMixin
,
queue
.
Queue
):
class
MemoryQueue
(
MultiQueueMixin
,
queue
.
Queue
,
object
):
pass
...
...
@@ -29,11 +29,11 @@ class SizedList(list):
def
__init__
(
self
,
maxsize
=
0
):
self
.
maxsize
=
maxsize
self
.
size
=
0
super
()
.
__init__
()
super
(
list
,
self
)
.
__init__
()
def
append
(
self
,
b
):
if
self
.
maxsize
==
0
or
self
.
size
<
self
.
maxsize
:
super
()
.
append
(
b
)
super
(
SizedList
,
self
)
.
append
(
b
)
self
.
size
+=
len
(
b
)
def
clean
(
self
):
...
...
coco/utils.py
View file @
2444c6fd
...
...
@@ -12,7 +12,6 @@ from io import StringIO
from
binascii
import
hexlify
from
werkzeug.local
import
Local
,
LocalProxy
from
functools
import
partial
,
wraps
import
builtins
import
paramiko
import
pyte
...
...
@@ -294,7 +293,7 @@ def get_logger(file_name):
return
logging
.
getLogger
(
'coco.'
+
file_name
)
def
net_input
(
client
,
prompt
=
'Opt> '
,
sensitive
=
False
,
before
=
0
,
after
=
0
,
only_one_char
=
False
):
def
net_input
(
client
,
prompt
=
'Opt> '
,
sensitive
=
False
,
before
=
0
,
after
=
0
):
"""实现了一个ssh input, 提示用户输入, 获取并返回
:return user input string
...
...
@@ -303,12 +302,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
parser
=
TtyIOParser
()
client
.
send
(
wrap_with_line_feed
(
prompt
,
before
=
before
,
after
=
after
))
if
only_one_char
:
data
=
client
.
recv
(
1
)
return
data
.
decode
()
while
True
:
data
=
client
.
recv
(
1
0
)
data
=
client
.
recv
(
1
)
if
len
(
data
)
==
0
:
break
# Client input backspace
...
...
@@ -336,18 +331,8 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_o
client
.
send
(
b
''
)
continue
# handle shell expect
multi_char_with_enter
=
False
if
len
(
data
)
>
1
and
data
[
-
1
]
in
char
.
ENTER_CHAR_ORDER
:
if
sensitive
:
client
.
send
(
len
(
data
)
*
'*'
)
else
:
client
.
send
(
data
)
input_data
.
append
(
data
[:
-
1
])
multi_char_with_enter
=
True
# If user types ENTER we should get user input
if
data
in
char
.
ENTER_CHAR
or
multi_char_with_enter
:
if
data
in
char
.
ENTER_CHAR
:
client
.
send
(
wrap_with_line_feed
(
b
''
,
after
=
2
))
option
=
parser
.
parse_input
(
input_data
)
del
input_data
[:]
...
...
@@ -435,11 +420,17 @@ def get_current_lang(attr):
def
_gettext
(
lang
):
import
builtins
if
lang
==
'en'
:
trans_en
.
install
()
else
:
trans_zh
.
install
()
return
builtins
.
__dict__
[
'_'
]
try
:
return
builtins
.
__dict__
[
'_'
]
except
KeyError
:
def
_f
(
x
):
return
x
return
_f
def
_find
(
attr
):
...
...
cocod
View file @
2444c6fd
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
import
os
import
eventlet
from
eventlet.debug
import
hub_prevent_multiple_readers
eventlet
.
monkey_patch
()
hub_prevent_multiple_readers
(
False
)
if
os
.
environ
.
get
(
'USE_EVENTLET'
,
'1'
)
==
'1'
:
import
eventlet
from
eventlet.debug
import
hub_prevent_multiple_readers
eventlet
.
monkey_patch
()
hub_prevent_multiple_readers
(
False
)
print
(
"Use eventlet dispatch"
)
else
:
print
(
"Use local threading"
)
import
os
import
sys
import
argparse
import
time
...
...
@@ -15,7 +19,8 @@ import signal
dirs
=
(
'logs'
,
'keys'
)
for
d
in
dirs
:
os
.
makedirs
(
d
,
exist_ok
=
True
)
if
not
os
.
path
.
isdir
(
d
):
os
.
makedirs
(
d
)
from
coco
import
Coco
...
...
locale/en/LC_MESSAGES/coco.mo
View file @
2444c6fd
No preview for this file type
locale/en/LC_MESSAGES/coco.po
View file @
2444c6fd
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-
18 20:03
+0800\n"
"POT-Creation-Date: 2018-12-
21 16:48
+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n"
...
...
@@ -84,75 +84,80 @@ msgstr ""
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr ""
#: coco/interactive.py:21
2
#: coco/interactive.py:21
7
msgid "No Assets"
msgstr ""
#: coco/interactive.py:2
75
#: coco/interactive.py:2
80
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr ""
#: coco/interactive.py:2
76
#: coco/interactive.py:2
81
msgid "Page up: P/p"
msgstr ""
#: coco/interactive.py:2
77
#: coco/interactive.py:2
82
msgid "Page down: Enter|N/n"
msgstr ""
#: coco/interactive.py:2
78
#: coco/interactive.py:2
83
msgid "BACK: b/q"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "ID"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "Hostname"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "IP"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:31
3
#: coco/interactive.py:31
7
msgid "Comment"
msgstr ""
#: coco/interactive.py:32
2
#: coco/interactive.py:32
6
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr ""
#: coco/interactive.py:39
4
#: coco/interactive.py:39
8
msgid "No Nodes"
msgstr ""
#: coco/interactive.py:
398
#: coco/interactive.py:
402
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr ""
#: coco/interactive.py:40
0
#: coco/interactive.py:40
4
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr ""
#: coco/interactive.py:4
08
#: coco/interactive.py:4
12
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:4
38
#: coco/interactive.py:4
42
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:46
1
#: coco/interactive.py:46
5
msgid "No system user"
msgstr ""
#: coco/models.py:247
#: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr ""
#: coco/models.py:251
msgid "Command `{}` is forbidden ........"
msgstr ""
...
...
locale/zh_CN/LC_MESSAGES/coco.mo
View file @
2444c6fd
No preview for this file type
locale/zh_CN/LC_MESSAGES/coco.po
View file @
2444c6fd
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-12-
18 20:04
+0800\n"
"POT-Creation-Date: 2018-12-
21 16:48
+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n"
...
...
@@ -91,75 +91,80 @@ msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:21
2
#: coco/interactive.py:21
7
msgid "No Assets"
msgstr "没有资产"
#: coco/interactive.py:2
75
#: coco/interactive.py:2
80
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:2
76
#: coco/interactive.py:2
81
msgid "Page up: P/p"
msgstr "上一页: P/p"
#: coco/interactive.py:2
77
#: coco/interactive.py:2
82
msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n"
#: coco/interactive.py:2
78
#: coco/interactive.py:2
83
msgid "BACK: b/q"
msgstr "返回: B/b"
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "ID"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "IP"
msgstr ""
#: coco/interactive.py:
299
#: coco/interactive.py:
303
msgid "LoginAs"
msgstr "登录用户"
#: coco/interactive.py:31
3
#: coco/interactive.py:31
7
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:32
2
#: coco/interactive.py:32
6
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:39
4
#: coco/interactive.py:39
8
msgid "No Nodes"
msgstr "没有节点"
#: coco/interactive.py:
398
#: coco/interactive.py:
402
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "节点: [ ID.名称(资产数量) ]"
#: coco/interactive.py:40
0
#: coco/interactive.py:40
4
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1"
#: coco/interactive.py:4
08
#: coco/interactive.py:4
12
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:4
38
#: coco/interactive.py:4
42
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:46
1
#: coco/interactive.py:46
5
msgid "No system user"
msgstr "没有系统用户"
#: coco/models.py:247
#: coco/models.py:242
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr "警告: 加载过滤规则失败,请按 Ctrl + D 退出重试."
#: coco/models.py:251
msgid "Command `{}` is forbidden ........"
msgstr "命令 `{}` 是被禁止的 ..."
...
...
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