mirror of
https://github.com/paradoxxxzero/butterfly.git
synced 2026-06-03 19:09:42 +00:00
Clean up, avoid some server info leak, prevent crash when procfs is unavailable
This commit is contained in:
@@ -22,15 +22,14 @@ import tornado.ioloop
|
||||
import tornado.httpserver
|
||||
import ssl
|
||||
|
||||
tornado.options.define("secret", default='secret', help="Secret")
|
||||
tornado.options.define("debug", default=False, help="Debug mode")
|
||||
tornado.options.define("host", default='127.0.0.1', help="Server host")
|
||||
tornado.options.define("port", default=57575, type=int, help="Server port")
|
||||
tornado.options.define("shell", help="Shell to execute at login")
|
||||
tornado.options.define("secure", default=False, \
|
||||
help="Choose whether or not to use SSL")
|
||||
tornado.options.define("reallysecure", default=False, \
|
||||
help="Require certificate authentication.")
|
||||
tornado.options.define("secure", default=False,
|
||||
help="Choose whether or not to use SSL")
|
||||
tornado.options.define("reallysecure", default=False,
|
||||
help="Require certificate authentication.")
|
||||
|
||||
tornado.options.parse_command_line()
|
||||
|
||||
@@ -55,15 +54,16 @@ elif tornado.options.options.secure:
|
||||
|
||||
ssl_opts = None
|
||||
if tornado.options.options.secure:
|
||||
ssl_opts = dict(certfile="butterfly.crt", keyfile="butterfly.key", \
|
||||
cert_reqs=reqs, ca_certs="butterflyca.crt")
|
||||
ssl_opts = dict(certfile="butterfly.crt", keyfile="butterfly.key",
|
||||
cert_reqs=reqs, ca_certs="butterflyca.crt")
|
||||
|
||||
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_opts)
|
||||
http_server.listen(
|
||||
tornado.options.options.port, address=tornado.options.options.host)
|
||||
|
||||
url = "http%s://%s:%d/*" % ( "s" if tornado.options.options.secure else "",
|
||||
tornado.options.options.host, tornado.options.options.port)
|
||||
url = "http%s://%s:%d/*" % ("s" if tornado.options.options.secure else "",
|
||||
tornado.options.options.host,
|
||||
tornado.options.options.port)
|
||||
|
||||
# This is for debugging purpose
|
||||
try:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
__version__ = '1.2.6'
|
||||
__version__ = '1.2.7'
|
||||
|
||||
|
||||
import os
|
||||
@@ -43,18 +43,11 @@ class Route(tornado.web.RequestHandler):
|
||||
def log(self):
|
||||
return log
|
||||
|
||||
if hasattr(tornado.options.options, 'debug'):
|
||||
opts = dict(
|
||||
debug=tornado.options.options.debug,
|
||||
cookie_secret=tornado.options.options.secret)
|
||||
else:
|
||||
opts = {}
|
||||
|
||||
|
||||
application = tornado.web.Application(
|
||||
static_path=os.path.join(os.path.dirname(__file__), "static"),
|
||||
template_path=os.path.join(os.path.dirname(__file__), "templates"),
|
||||
**opts
|
||||
debug=tornado.options.options.debug
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import tornado.process
|
||||
import tornado.ioloop
|
||||
import tornado.options
|
||||
import sys
|
||||
from butterfly import url, Route, utils
|
||||
from butterfly import url, Route, utils, __version__
|
||||
|
||||
ioloop = tornado.ioloop.IOLoop.instance()
|
||||
|
||||
@@ -47,41 +47,39 @@ def u(s):
|
||||
return s
|
||||
|
||||
|
||||
def motd(socket, caller, callee):
|
||||
def motd(socket):
|
||||
return (
|
||||
'''
|
||||
B ` '
|
||||
;,,, ` ' ,,,;
|
||||
`Y888888bo. : : .od888888Y'
|
||||
8888888888b. : : .d8888888888 AWelcome to RbutterflyB
|
||||
8888888888b. : : .d8888888888
|
||||
88888Y' `Y8b. ` ' .d8Y' `Y88888
|
||||
j88888 R.db.B Yb. ' ' .dY R.db.B 88888k AServer running as G%rB
|
||||
j88888 R.db.B Yb. ' ' .dY R.db.B 88888k
|
||||
`888 RY88YB `b ( ) d' RY88YB 888'
|
||||
888b R'"B ,', R"'B d888 AConnecting to:B
|
||||
j888888bd8gf"' ':' `"?g8bd888888k AHost: G%sB
|
||||
R'Y'B .8' d' 'b '8. R'Y'X AUser: G%rB
|
||||
R!B .8' RdbB d'; ;`b RdbB '8. R!B
|
||||
d88 R`'B 8 ; ; 8 R`'B 88b AFrom:B
|
||||
d888b .g8 ',' 8g. d888b AHost: G%sB
|
||||
:888888888Y' 'Y888888888: AUser: G%rB
|
||||
'! 8888888' `8888888 !'
|
||||
888b R'"B ,', R"'B d888
|
||||
j888888bd8gf"' ':' `"?g8bd888888k
|
||||
R'Y'B .8' d' 'b '8. R'Y'X
|
||||
R!B .8' RdbB d'; ;`b RdbB '8. R!
|
||||
d88 R`'B 8 ; ; 8 R`'B 88b Rbutterfly Zv %sB
|
||||
d888b .g8 ',' 8g. d888b
|
||||
:888888888Y' 'Y888888888: AConnecting to:B
|
||||
'! 8888888' `8888888 !' G%sB
|
||||
'8Y R`Y Y'B Y8'
|
||||
R Y Y
|
||||
! !X
|
||||
R Y Y AFrom:R
|
||||
! ! G%sX
|
||||
|
||||
'''
|
||||
.replace('B', '\x1b[34;1m')
|
||||
.replace('G', '\x1b[32;1m')
|
||||
.replace('R', '\x1b[37;1m')
|
||||
.replace('Z', '\x1b[33;1m')
|
||||
.replace('A', '\x1b[37;0m')
|
||||
.replace('X', '\x1b[0m')
|
||||
.replace('\n', '\r\n')
|
||||
% (
|
||||
server,
|
||||
'%s:%d' % (socket.remote_addr, socket.remote_port),
|
||||
callee,
|
||||
'%s:%d' % (socket.local_addr, socket.local_port),
|
||||
caller or '?'))
|
||||
% (__version__,
|
||||
'%s:%d' % (socket.local_addr, socket.local_port),
|
||||
'%s:%d' % (socket.remote_addr, socket.remote_port)))
|
||||
|
||||
|
||||
@url(r'/(?:user/(.+))?/?(?:wd/(.+))?')
|
||||
@@ -105,36 +103,35 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
self.communicate()
|
||||
|
||||
def shell(self):
|
||||
while self.callee is None:
|
||||
if self.callee is None:
|
||||
user = input('login: ')
|
||||
try:
|
||||
self.callee = utils.User(name=user)
|
||||
except:
|
||||
print('User %s not found' % user)
|
||||
self.callee = utils.User(name='nologin')
|
||||
|
||||
try:
|
||||
os.chdir(self.path or self.callee.dir)
|
||||
except:
|
||||
pass
|
||||
|
||||
shell = tornado.options.options.shell or self.callee.shell
|
||||
env = os.environ
|
||||
env.update(self.socket.env)
|
||||
env["TERM"] = "xterm-256color"
|
||||
env["COLORTERM"] = "butterfly"
|
||||
env["HOME"] = self.callee.dir
|
||||
env["LOCATION"] = "http%s://%s:%d/" % \
|
||||
("s" if tornado.options.options.secure else "", \
|
||||
tornado.options.options.host, tornado.options.options.port)
|
||||
env["LOCATION"] = "http%s://%s:%d/" % (
|
||||
"s" if tornado.options.options.secure else "",
|
||||
tornado.options.options.host, tornado.options.options.port)
|
||||
env["PATH"] = '%s:%s' % (os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), '..', 'bin')), env.get("PATH"))
|
||||
args = [shell]
|
||||
|
||||
if self.socket.local:
|
||||
# All users are the same -> launch shell
|
||||
if self.caller == self.callee and server == self.callee:
|
||||
args = [tornado.options.options.shell or self.callee.shell]
|
||||
args.append('-i')
|
||||
os.execvpe(shell, args, env)
|
||||
os.execvpe(args[0], args, env)
|
||||
# This process has been replaced
|
||||
return
|
||||
|
||||
@@ -151,12 +148,19 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
sys.exit(1)
|
||||
os.setuid(daemon.uid)
|
||||
|
||||
args.append('-p')
|
||||
if tornado.options.options.shell:
|
||||
args.append('-s')
|
||||
args.append(tornado.options.options.shell)
|
||||
if os.path.exists('/usr/bin/su'):
|
||||
args = ['/usr/bin/su']
|
||||
else:
|
||||
args = ['/bin/su']
|
||||
|
||||
# This would be better to run
|
||||
if sys.platform == 'linux':
|
||||
args.append('-p')
|
||||
if tornado.options.options.shell:
|
||||
args.append('-s')
|
||||
args.append(tornado.options.options.shell)
|
||||
args.append(self.callee.name)
|
||||
os.execvpe('/bin/su', args, env)
|
||||
os.execvpe(args[0], args, env)
|
||||
|
||||
def communicate(self):
|
||||
self.log.debug('Adding handler')
|
||||
@@ -181,8 +185,8 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
self.fd, self.shell_handler, ioloop.READ | ioloop.ERROR)
|
||||
|
||||
def open(self, user, path):
|
||||
if self.request.headers['Origin'] != 'http%s://%s' % \
|
||||
("s" if tornado.options.options.secure else "",
|
||||
if self.request.headers['Origin'] != 'http%s://%s' % (
|
||||
"s" if tornado.options.options.secure else "",
|
||||
self.request.headers['Host']):
|
||||
self.log.warning(
|
||||
'Unauthorized connection attempt: from : %s to: %s' % (
|
||||
@@ -190,7 +194,9 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
self.request.headers['Host']))
|
||||
self.close()
|
||||
return
|
||||
|
||||
self.socket = utils.Socket(self.ws_connection.stream.socket)
|
||||
self.errors = 0
|
||||
self.set_nodelay(True)
|
||||
self.log.info('Websocket opened %r' % self.socket)
|
||||
self.path = path
|
||||
@@ -198,7 +204,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
self.caller = self.callee = None
|
||||
if tornado.options.options.secure:
|
||||
cert = self.request.get_ssl_certificate()
|
||||
if cert != None:
|
||||
if cert is not None:
|
||||
for field in cert['subject']:
|
||||
if field[0][0] == 'commonName':
|
||||
self.user = self.callee = field[0][1]
|
||||
@@ -208,12 +214,11 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
else:
|
||||
# We don't know uid is on the other machine
|
||||
pass
|
||||
|
||||
|
||||
if self.user:
|
||||
try:
|
||||
self.callee = utils.User(name=self.user)
|
||||
except LookupError:
|
||||
print('User %s not found' % self.user)
|
||||
self.callee = None
|
||||
|
||||
# If no user where given and we are local, keep the same user
|
||||
@@ -221,7 +226,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
# ie: the one openning a terminal in borwser
|
||||
if not self.callee and not self.user and self.socket.local:
|
||||
self.callee = self.caller
|
||||
self.write_message(motd(self.socket, self.caller, self.callee))
|
||||
self.write_message(motd(self.socket))
|
||||
self.pty()
|
||||
|
||||
def on_message(self, message):
|
||||
@@ -240,6 +245,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
|
||||
def shell_handler(self, fd, events):
|
||||
if events & ioloop.READ:
|
||||
self.errors = 0
|
||||
try:
|
||||
read = self.reader.read()
|
||||
except IOError:
|
||||
@@ -251,12 +257,13 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
self.write_message(read.decode('utf-8', 'replace'))
|
||||
|
||||
if events & ioloop.ERROR:
|
||||
self.log.info('Closing due to ioloop fd handler error')
|
||||
ioloop.remove_handler(self.fd)
|
||||
|
||||
# Terminated
|
||||
self.on_close()
|
||||
self.close()
|
||||
self.errors += 1
|
||||
if self.errors >= 10:
|
||||
self.log.info(
|
||||
'Too many errors, the shell must have been closed')
|
||||
# Terminated
|
||||
self.on_close()
|
||||
self.close()
|
||||
|
||||
def on_close(self):
|
||||
if getattr(self, 'pid', 0) == 0:
|
||||
@@ -275,4 +282,10 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
|
||||
os.waitpid(self.pid, 0)
|
||||
except OSError:
|
||||
self.log.warning('waitpid fail', exc_info=True)
|
||||
|
||||
try:
|
||||
ioloop.remove_handler(self.fd)
|
||||
except Exception:
|
||||
self.log.warning('handler removal fail', exc_info=True)
|
||||
|
||||
self.log.info('Websocket closed')
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
cols = rows = null
|
||||
quit = false
|
||||
open_ts = 0
|
||||
|
||||
$ = document.querySelectorAll.bind(document)
|
||||
|
||||
@@ -38,6 +39,7 @@ ws = new WebSocket ws_url
|
||||
ws.addEventListener 'open', ->
|
||||
console.log "WebSocket open", arguments
|
||||
ws.send 'R' + term.cols + ',' + term.rows
|
||||
open_ts = (new Date()).getTime()
|
||||
|
||||
ws.addEventListener 'error', ->
|
||||
console.log "WebSocket error", arguments
|
||||
@@ -49,9 +51,16 @@ ws.addEventListener 'message', (e) ->
|
||||
|
||||
ws.addEventListener 'close', ->
|
||||
console.log "WebSocket closed", arguments
|
||||
setTimeout ->
|
||||
term.write 'Closed'
|
||||
# Allow quick reload
|
||||
term.skipNextKey = true
|
||||
term.element.classList.add('skip')
|
||||
, 1
|
||||
quit = true
|
||||
open('','_self').close()
|
||||
|
||||
# Don't autoclose if websocket didn't last 1 minute
|
||||
if (new Date()).getTime() - open_ts > 60 * 1000
|
||||
open('','_self').close()
|
||||
|
||||
term = new Terminal $('#wrapper')[0], send, ctl
|
||||
addEventListener 'beforeunload', ->
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -75,7 +75,7 @@ def get_socket_line(port):
|
||||
# We got the socket
|
||||
return line.split()
|
||||
except:
|
||||
log.error('getting socket inet4 line fail', exc_info=True)
|
||||
log.debug('getting socket inet4 line fail', exc_info=True)
|
||||
|
||||
try:
|
||||
with open('/proc/net/tcp6') as k:
|
||||
@@ -87,7 +87,7 @@ def get_socket_line(port):
|
||||
# We got the socket
|
||||
return line.split()
|
||||
except:
|
||||
log.error('getting socket inet6 line fail', exc_info=True)
|
||||
log.debug('getting socket inet6 line fail', exc_info=True)
|
||||
|
||||
|
||||
def get_env(inode):
|
||||
@@ -122,7 +122,11 @@ class Socket(object):
|
||||
pn = socket.getpeername()
|
||||
self.remote_addr = pn[0]
|
||||
self.remote_port = pn[1]
|
||||
line = get_socket_line(self.remote_port)
|
||||
try:
|
||||
line = get_socket_line(self.remote_port)
|
||||
except Exception:
|
||||
line = None
|
||||
|
||||
if line:
|
||||
self.uid = int(line[7])
|
||||
self.inode = line[9]
|
||||
@@ -131,10 +135,10 @@ class Socket(object):
|
||||
self.inode = None
|
||||
|
||||
self.env = {}
|
||||
if self.local:
|
||||
if self.local and self.inode:
|
||||
try:
|
||||
self.env = get_env(self.inode)
|
||||
except:
|
||||
except Exception:
|
||||
log.debug('Unable to get env', exc_info=True)
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user