diff --git a/butterfly/routes.py b/butterfly/routes.py index 0d1f1b4..def88fd 100644 --- a/butterfly/routes.py +++ b/butterfly/routes.py @@ -20,6 +20,8 @@ import pty import os import io import struct +import string +import random import fcntl import termios import tornado.web @@ -89,9 +91,16 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): terminals = set() def pty(self): - self.determine_user() + # Make a "unique" id in 4 bytes + self.uid = ''.join( + random.choice( + string.ascii_lowercase + string.ascii_uppercase + + string.digits) + for _ in range(4)) + self.pid, self.fd = pty.fork() if self.pid == 0: + self.determine_user() self.shell() else: self.communicate() @@ -101,10 +110,18 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): tornado.options.options.unsecure and tornado.options.options.login): # If callee is now known and we have unsecure connection - user = input('login: ') + user = '' + while user == '': + try: + user = input('login: ') + except (KeyboardInterrupt, EOFError): + self.log.debug("Errorin login input", exc_info=True) + pass + try: self.callee = utils.User(name=user) - except: + except Exception: + self.log.debug("Can't switch to user %s" % user, exc_info=True) self.callee = utils.User(name='nobody') elif (tornado.options.options.unsecure and not tornado.options.options.login): @@ -117,8 +134,10 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): def shell(self): try: os.chdir(self.path or self.callee.dir) - except: - pass + except Exception: + self.log.debug( + "Can't chdir to %s" % (self.path or self.callee.dir), + exc_info=True) env = os.environ # If local and local user is the same as login user @@ -135,6 +154,23 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): env["PATH"] = '%s:%s' % (os.path.abspath(os.path.join( os.path.dirname(__file__), 'bin')), env.get("PATH")) + try: + tty = os.ttyname(0).replace('/dev/', '') + except Exception: + self.log.debug("Can't get ttyname", exc_info=True) + tty = '' + + if self.caller != self.callee: + try: + os.chown(os.ttyname(0), self.callee.uid, -1) + except Exception: + self.log.debug("Can't chown ttyname", exc_info=True) + + utils.add_user_info( + self.uid, + tty, os.getpid(), + self.callee.name, self.request.headers['Host']) + if not tornado.options.options.unsecure or ( self.socket.local and self.caller == self.callee and @@ -154,9 +190,11 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): os.initgroups(self.callee.name, self.callee.gid) os.setgid(self.callee.gid) os.setuid(self.callee.uid) - except: - print('The server must be run as root ' - 'if you want to log as different user\n') + except Exception: + self.log.error( + 'The server must be run as root ' + 'if you want to log as different user\n', + exc_info=True) sys.exit(1) if tornado.options.options.cmd: @@ -198,10 +236,6 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): def communicate(self): fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NONBLOCK) - utils.add_user_info( - self.fd, self.pid, - self.callee.name, self.request.headers['Host']) - def utf8_error(e): self.log.error(e) @@ -249,6 +283,8 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): try: self.callee = utils.User(name=self.user) except LookupError: + self.log.debug( + "Can't switch to user %s" % self.user, exc_info=True) self.callee = None # If no user where given and we are local, keep the same user @@ -324,7 +360,7 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler): self.log.info('pid is 0') return - utils.rm_user_info(self.fd, self.pid, self.callee.name) + utils.rm_user_info(self.uid, self.pid) try: ioloop.remove_handler(self.fd) diff --git a/butterfly/utils.py b/butterfly/utils.py index 85714c5..ccf4a3c 100644 --- a/butterfly/utils.py +++ b/butterfly/utils.py @@ -46,7 +46,7 @@ def get_style(): os.path.dirname(__file__), 'sass') try: import sass - except: + except Exception: log.error('You must install libsass to use sass ' '(pip install libsass)') return @@ -124,9 +124,14 @@ class Socket(object): sn = socket.getsockname() self.local_addr = sn[0] self.local_port = sn[1] - pn = socket.getpeername() - self.remote_addr = pn[0] - self.remote_port = pn[1] + try: + pn = socket.getpeername() + self.remote_addr = pn[0] + self.remote_port = pn[1] + except Exception: + log.debug("Can't get peer name", exc_info=True) + self.remote_addr = '???' + self.remote_port = 0 self.user = None self.env = {} @@ -188,7 +193,7 @@ def get_procfs_socket_line(port): if line.split()[1] == '0100007F:%X' % port: # We got the socket return line.split() - except: + except Exception: log.debug('getting socket inet4 line fail', exc_info=True) try: @@ -200,7 +205,7 @@ def get_procfs_socket_line(port): '00000000000000000000000001000000:%X' % port): # We got the socket return line.split() - except: + except Exception: log.debug('getting socket inet6 line fail', exc_info=True) @@ -268,12 +273,12 @@ UTmp = namedtuple( 'sec', 'usec', 'addr0', 'addr1', 'addr2', 'addr3', 'unused']) -def utmp_line(type, pid, fd, user, host, ts): +def utmp_line(id, type, pid, fd, user, host, ts): return UTmp( type, # Type, 7 : user process pid, # pid - b('pts/%d' % fd), # line - b('/%d' % fd), # id + b(fd), # line + b(id), # id b(user), # user b(host), # host 0, # exit 0 @@ -289,12 +294,12 @@ def utmp_line(type, pid, fd, user, host, ts): ) -def add_user_info(fd, pid, user, host): +def add_user_info(id, fd, pid, user, host): # Freebsd format is not yet supported. # Please submit PR if sys.platform != 'linux': return - utmp = utmp_line(7, pid, fd, user, host, time.time()) + utmp = utmp_line(id, 7, pid, fd, user, host, time.time()) for kind, file in { 'utmp': get_utmp_file(), 'wtmp': get_wtmp_file()}.items(): @@ -305,7 +310,7 @@ def add_user_info(fd, pid, user, host): s = f.read(utmp_struct.size) while s: entry = UTmp(*utmp_struct.unpack(s)) - if kind == 'utmp' and entry.id.rstrip(b'\0') == utmp.id: + if kind == 'utmp' and entry.id == utmp.id: # Same id recycling f.seek(f.tell() - utmp_struct.size) f.write(utmp_struct.pack(*utmp)) @@ -314,13 +319,13 @@ def add_user_info(fd, pid, user, host): else: f.write(utmp_struct.pack(*utmp)) except Exception: - log.info('Unable to write utmp info to ' + file, exc_info=True) + log.debug('Unable to write utmp info to ' + file, exc_info=True) -def rm_user_info(fd, pid, user): +def rm_user_info(id, pid): if sys.platform != 'linux': return - utmp = utmp_line(8, pid, fd, user, '', time.time()) + utmp = utmp_line(id, 8, pid, '', '', '', time.time()) for kind, file in { 'utmp': get_utmp_file(), 'wtmp': get_wtmp_file()}.items(): @@ -331,17 +336,23 @@ def rm_user_info(fd, pid, user): s = f.read(utmp_struct.size) while s: entry = UTmp(*utmp_struct.unpack(s)) - if kind == 'utmp' and entry.id.rstrip(b'\0') == utmp.id: - # Same id closing - f.seek(f.tell() - utmp_struct.size) - f.write(utmp_struct.pack(*utmp)) - break + if entry.id == utmp.id: + if kind == 'utmp': + # Same id closing + f.seek(f.tell() - utmp_struct.size) + f.write(utmp_struct.pack(*utmp)) + break + else: + utmp = utmp_line( + id, 8, pid, entry.line, entry.user, '', + time.time()) + s = f.read(utmp_struct.size) else: f.write(utmp_struct.pack(*utmp)) except Exception: - log.info('Unable to update utmp info to ' + file, exc_info=True) + log.debug('Unable to update utmp info to ' + file, exc_info=True) class AnsiColors(object):