def open(self, user, path): if self.request.headers['Origin'] != 'http://%s' % ( self.request.headers['Host']): self.log.warning( 'Unauthorized connection attempt: from : %s to: %s' % (self.request.headers['Origin'], self.request.headers['Host'])) self.close() return self.socket = utils.Socket(self.ws_connection.stream.socket) self.set_nodelay(True) self.log.info('Websocket opened %r' % self.socket) self.path = path self.user = user.decode('utf-8') if user else None self.caller = self.callee = None if self.socket.local: self.caller = utils.User(uid=self.socket.uid) 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 # as the one who opened the socket # 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.pty()
def determine_user(self): if not tornado.options.options.unsecure: # Secure mode we must have already a callee assert self.callee is not None return # If we should login, login if tornado.options.options.login: user = '' while user == '': try: user = input('login: '******'t switch to user %s" % user, exc_info=True) self.callee = utils.User(name='nobody') return # if login is not required, we will use the same user as # butterfly is executed self.callee = self.callee or utils.User()
def shell(self): if self.callee is None: user = input('login: '******'nobody') try: os.chdir(self.path or self.callee.dir) except: pass 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["PATH"] = '%s:%s' % (os.path.abspath( os.path.join(os.path.dirname(__file__), '..', 'bin')), env.get("PATH")) 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(args[0], args, env) # This process has been replaced return if server.root: if self.callee != self.caller: # Force password prompt by dropping rights # to the daemon user os.setuid(daemon.uid) else: # We are not local so we should always get a password prompt if server.root: if self.callee == daemon: # No logging from daemon sys.exit(1) os.setuid(daemon.uid) if os.path.exists('/usr/bin/su'): args = ['/usr/bin/su'] else: args = ['/bin/su'] 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(args[0], args, env)
def create_terminal(self): socket = utils.Socket(self.ws_connection.stream.socket) user = self.request.query_arguments.get('user', [b''])[0].decode('utf-8') path = self.request.query_arguments.get('path', [b''])[0].decode('utf-8') secure_user = None if tornado.options.options.username is not None: user = utils.User(name=user) elif not tornado.options.options.unsecure: user = utils.parse_cert( self.ws_connection.stream.socket.getpeercert()) assert user, 'No user in certificate' try: user = utils.User(name=user) except LookupError: raise Exception('Invalid user in certificate') # Certificate authed user secure_user = user elif socket.local and socket.user == utils.User() and not user: # Local to local returning browser user secure_user = socket.user elif user: try: user = utils.User(name=user) except LookupError: raise Exception('Invalid user') if secure_user: user = secure_user if self.session in self.sessions and self.session in ( self.sessions_secure_users): if user.name != self.sessions_secure_users[self.session]: # Restrict to authorized users raise tornado.web.HTTPError(403) else: self.sessions_secure_users[self.session] = user.name self.sessions[self.session].append(self) terminal = Terminal.sessions.get(self.session) # Handling terminal session if terminal: TermWebSocket.last.write_message(terminal.history) # And returning, we don't want another terminal return # New session, opening terminal terminal = Terminal(user, path, self.session, socket, self.request.full_url().replace('/ctl/', '/'), self.render_string, TermWebSocket.broadcast) terminal.pty() self.log.info('Openning session %s for secure user %r' % (self.session, user))
def open(self, user, path): self.fd = None self.closed = False if self.request.headers['Origin'] not in ( 'http://%s' % self.request.headers['Host'], 'https://%s' % self.request.headers['Host']): self.log.warning( 'Unauthorized connection attempt: from : %s to: %s' % (self.request.headers['Origin'], self.request.headers['Host'])) self.close() return self.socket = utils.Socket(self.ws_connection.stream.socket) self.set_nodelay(True) self.log.info('Websocket opened %r' % self.socket) self.path = path self.user = user if user else None self.caller = self.callee = None # If local we have the user connecting if self.socket.local and self.socket.user is not None: self.caller = self.socket.user if tornado.options.options.unsecure: if self.user: 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 # as the one who opened the socket # ie: the one openning a terminal in borwser if not self.callee and not self.user and self.socket.local: self.callee = self.caller else: user = utils.parse_cert(self.stream.socket.getpeercert()) assert user, 'No user in certificate' self.user = user try: self.callee = utils.User(name=self.user) except LookupError: raise Exception('Invalid user in certificate') TermWebSocket.terminals.add(self) if tornado.options.options.motd != '': motd = (self.render_string( tornado.options.options.motd, butterfly=self, version=__version__, opts=tornado.options.options, colors=utils.ansi_colors).decode('utf-8').replace( '\r', '').replace('\n', '\r\n')) self.write_message(motd) self.pty()
def open(self, user, path): if self.request.headers['Origin'] != 'http%s://%s' % ( "s" if not tornado.options.options.unsecure else "", self.request.headers['Host']): self.log.warning( 'Unauthorized connection attempt: from : %s to: %s' % (self.request.headers['Origin'], self.request.headers['Host'])) self.close() return self.socket = utils.Socket(self.ws_connection.stream.socket) self.set_nodelay(True) self.log.info('Websocket opened %r' % self.socket) self.path = path self.user = user.decode('utf-8') if user else None self.caller = self.callee = None # If local we have the user connecting if self.socket.local and self.socket.user is not None: self.caller = self.socket.user if tornado.options.options.unsecure: if self.user: try: self.callee = utils.User(name=self.user) except LookupError: self.callee = None # If no user where given and we are local, keep the same user # as the one who opened the socket # ie: the one openning a terminal in borwser if not self.callee and not self.user and self.socket.local: self.callee = self.caller else: user = utils.parse_cert(self.request.get_ssl_certificate()) assert user, 'No user in certificate' self.user = user try: self.callee = utils.User(name=self.user) except LookupError: raise Exception('Invalid user in certificate') self.write_message(motd(self.socket)) self.pty()
def determine_user(self): if self.callee is None and (tornado.options.options.unsecure and tornado.options.options.login): # If callee is now known and we have unsecure connection user = '' while user == '': try: user = input('login: '******'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): # if login is not required, we will use the same user as # butterfly is executed self.callee = utils.User() assert self.callee is not None
def __init__(self, user, path, session, socket, host, render_string, send): self.host = host self.session = session self.send = send self.fd = None self.closed = False self.socket = socket log.info('Terminal opening with session: %s and socket %r' % ( self.session, self.socket)) self.path = path self.user = user if user else None self.caller = self.callee = None # If local we have the user connecting if self.socket.local and self.socket.user is not None: self.caller = self.socket.user if tornado.options.options.unsecure: if self.user: try: self.callee = utils.User(name=self.user) except LookupError: 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 as the one who opened the socket ie: the one # openning a terminal in browser if not self.callee and not self.user and self.socket.local: self.user = self.callee = self.caller else: # Authed user self.callee = self.user if tornado.options.options.motd != '': motd = (render_string( tornado.options.options.motd, butterfly=self, version=__version__, opts=tornado.options.options, colors=utils.ansi_colors) .decode('utf-8') .replace('\r', '') .replace('\n', '\r\n')) self.send('S' + motd) log.info('Forking pty for user %r' % self.user)
server_cert.sign(ca_pk, 'sha512') write(cert % host, crypto.dump_certificate(crypto.FILETYPE_PEM, server_cert)) write(cert_key % host, crypto.dump_privatekey(crypto.FILETYPE_PEM, server_pk)) os.chmod(cert_key % host, stat.S_IRUSR | stat.S_IWUSR) # 0o600 perms print('\nNow you can run --generate-user-pkcs=user ' 'to generate user certificate.') sys.exit(0) if (options.generate_current_user_pkcs or options.generate_user_pkcs): from butterfly import utils try: current_user = utils.User() except Exception: current_user = None from OpenSSL import crypto if not all(map(os.path.exists, [ca, ca_key])): print('Please generate certificates using --generate-certs before') sys.exit(1) if options.generate_current_user_pkcs: user = current_user.name else: user = options.generate_user_pkcs if user != current_user.name and current_user.uid != 0: print('Cannot create certificate for another user with '
import struct import sys from logging import getLogger import tornado.ioloop import tornado.options import tornado.process import tornado.web import tornado.websocket import termios from butterfly import __version__, utils log = getLogger('butterfly') ioloop = tornado.ioloop.IOLoop.instance() server = utils.User() daemon = utils.User(name='daemon') # Python 2 backward compatibility try: input = raw_input except NameError: pass class Terminal(object): sessions = {} def __init__(self, user, path, session, socket, uri, render_string, broadcast): self.sessions[session] = self
def shell(self): if not tornado.options.options.prompt_login: self.callee = utils.User(self.caller) if self.callee is None: user = input('login: '******'nobody') try: os.chdir(self.path or tornado.options.options.wd or self.callee.dir) except: pass 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 not tornado.options.options.unsecure 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")) env.pop("VIRTUAL_ENV", None) # If the server is running from virtualenv env.pop("PS1", None) # then remove the prefix (virtenv) and show the regular one [user@comp ~] if tornado.options.options.load_script: args = tornado.options.options.load_script.split(" ") elif tornado.options.options.shell: args = [tornado.options.options.shell] else: args = [self.callee.shell, "-i"] if self.socket.local or not tornado.options.options.prompt_login: # All users are the same -> launch shell if (self.caller == self.callee and server == self.callee) or not tornado.options.options.prompt_login: os.execvpe(args[0], args, env) # This process has been replaced return if server.root: if self.callee != self.caller: # Force password prompt by dropping rights # to the daemon user os.setuid(daemon.uid) else: # We are not local so we should always get a password prompt if server.root: if self.callee == daemon: # No logging from daemon sys.exit(1) os.setuid(daemon.uid) if os.path.exists('/usr/bin/su'): args = ['/usr/bin/su'] else: args = ['/bin/su'] if sys.platform == 'linux': args.append('-p') if tornado.options.options.load_script: args.append('-c') args.append(tornado.options.options.load_script) elif tornado.options.options.shell: args.append('-s') args.append(tornado.options.options.shell) args.append(self.callee.name) os.execvpe(args[0], args, env)
def shell(self): if self.callee is None and (tornado.options.options.unsecure and tornado.options.options.login): # If callee is now known and we have unsecure connection user = input('login: '******'nobody') elif (tornado.options.options.unsecure and not tornado.options.options.login): # if login is not required, we will use the same user as # butterfly is executed self.callee = utils.User() assert self.callee is not None try: os.chdir(self.path or self.callee.dir) except: pass env = os.environ # If local and local user is the same as login user # We set the env of the user from the browser # Usefull when running as root if self.caller == self.callee: 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 not tornado.options.options.unsecure 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")) if not tornado.options.options.unsecure or ( self.socket.local and self.caller == self.callee and server == self.callee) or not tornado.options.options.login: # User has been auth with ssl or is the same user as server # or login is explicitly turned off if (not tornado.options.options.unsecure and tornado.options.options.login and not (self.socket.local and self.caller == self.callee and server == self.callee)): # User is authed by ssl, setting groups try: 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') sys.exit(1) if tornado.options.options.cmd: args = tornado.options.options.cmd.split(' ') else: args = [tornado.options.options.shell or self.callee.shell] args.append('-i') os.execvpe(args[0], args, env) # This process has been replaced # Unsecure connection with su if server.root: if self.socket.local: if self.callee != self.caller: # Force password prompt by dropping rights # to the daemon user os.setuid(daemon.uid) else: # We are not local so we should always get a password prompt if self.callee == daemon: # No logging from daemon sys.exit(1) os.setuid(daemon.uid) if os.path.exists('/usr/bin/su'): args = ['/usr/bin/su'] else: args = ['/bin/su'] 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(args[0], args, env)
def open(self, user, path, session): self.session = session self.closed = False self.secure_user = None # Prevent cross domain if self.request.headers['Origin'] not in ( 'http://%s' % self.request.headers['Host'], 'https://%s' % self.request.headers['Host']): self.log.warning( 'Unauthorized connection attempt: from : %s to: %s' % (self.request.headers['Origin'], self.request.headers['Host'])) self.close() return TermWebSocket.sockets.append(self) self.log.info('Websocket opened %r' % self) self.set_nodelay(True) socket = utils.Socket(self.ws_connection.stream.socket) opts = tornado.options.options if not opts.unsecure: user = utils.parse_cert( self.ws_connection.stream.socket.getpeercert()) assert user, 'No user in certificate' try: user = utils.User(name=user) except LookupError: raise Exception('Invalid user in certificate') # Certificate authed user self.secure_user = user elif socket.local and socket.user == utils.User(): # Local to local returning browser user self.secure_user = socket.user # Handling terminal session if session: if session in self.user_sessions: # Session already here, registering websocket self.user_sessions[session].append(self) self.write_message('S' + TermWebSocket.history[session]) # And returning, we don't want another terminal return else: # New session, opening terminal self.user_sessions[session] = [self] TermWebSocket.history[session] = '' terminal = Terminal(user, path, session, socket, self.request.headers['Host'], self.render_string, self.write) terminal.pty() if session: if not self.secure_user: self.log.error( 'No terminal session without secure authenticated user' 'or local user.') self._terminal = terminal self.session = None else: self.log.info('Openning session %s for secure user %r' % (session, self.secure_user)) self.user_terminals[session] = terminal else: self._terminal = terminal
def create_terminal(self): socket = utils.Socket(self.ws_connection.stream.socket) user = self.request.query_arguments.get('user', [b''])[0].decode('utf-8') path = self.request.query_arguments.get('path', [b''])[0].decode('utf-8') secure_user = None self.CMD_SETUP['path'] = str(path) if not tornado.options.options.unsecure: user = utils.parse_cert( self.ws_connection.stream.socket.getpeercert()) assert user, 'No user in certificate' try: user = utils.User(name=user) except LookupError: raise Exception('Invalid user in certificate') # Certificate authed user secure_user = user elif socket.local and socket.user == utils.User() and not user: # Local to local returning browser user secure_user = socket.user elif user: try: user = utils.User(name=user) except LookupError: raise Exception('Invalid user') if secure_user: user = secure_user if self.session in self.sessions and self.session in ( self.sessions_secure_users): if user.name != self.sessions_secure_users[self.session]: # Restrict to authorized users raise tornado.web.HTTPError(403) else: self.sessions_secure_users[self.session] = user.name self.sessions[self.session].append(self) terminal = Terminal.sessions.get(self.session) # Handling terminal session if terminal: TermWebSocket.last.write_message(terminal.history) # And returning, we don't want another terminal return self.CMD_SETUP['path'] = str(path) self.CMD_SETUP['uri'] = str(self.request.full_url().replace( '/ctl/', '/')) self.CMD_SETUP['user'] = str(user) self.CMD_SETUP['urlparse'] = urlparse(self.CMD_SETUP['uri'])[4] self.CMD_SETUP['parse_qs'] = parse_qs(self.CMD_SETUP['urlparse']) wrapper_mode_config = get_wrapper_mode_config() MISSING_PARAMS = [] INVALID_PARAMS = [] for rp in wrapper_mode_config['REQUIRED_PARAMS']: if not rp in self.CMD_SETUP['parse_qs'].keys(): MISSING_PARAMS.append(rp) else: IS_VALID_OPTION = True if rp in wrapper_mode_config['PARAM_OPTIONS'].keys(): IS_VALID_OPTION = False for possible_value in wrapper_mode_config['PARAM_OPTIONS'][ rp]: if self.CMD_SETUP['parse_qs'][rp][0] == possible_value: IS_VALID_OPTION = True if not IS_VALID_OPTION: INVALID_PARAMS.append(rp) if len(MISSING_PARAMS) > 0: err = f'{len(MISSING_PARAMS)} Missing URL parameters: {", ".join(MISSING_PARAMS)}' print(f'ERROR: {err}') tornado.options.options.cmd = "echo -e '{}'".format(err) elif len(INVALID_PARAMS) > 0: err = f'{len(INVALID_PARAMS)} Invalid URL parameters: {", ".join(INVALID_PARAMS)}' print(f'ERROR: {err}') tornado.options.options.cmd = "echo -e '{}'".format(err) else: HOST_NAME = self.CMD_SETUP['parse_qs']['hostname'][0] SERVICE_DESCRIPTION = '' tornado.options.options.cmd = write_script_template(self) sys.stderr.write('CMD={}\n'.format(tornado.options.options.cmd)) # New session, opening terminal terminal = Terminal(user, path, self.session, socket, self.request.full_url().replace('/ctl/', '/'), self.render_string, TermWebSocket.broadcast, self.CMD_SETUP) terminal.pty() self.log.info('Opening session %s for secure user %r' % (self.session, user))