def check_scp_sftp(self): """ This method checks if the user is trying to SCP a file onto the server. If this is the case, it checks if the user is allowed to use SCP or not, and acts as requested. : ) """ if 'ssh' in self.conf: if 'SSH_CLIENT' in os.environ \ and 'SSH_TTY' not in os.environ: # check if sftp is requested and allowed if 'sftp-server' in self.conf['ssh']: if self.conf['sftp'] is 1: self.log.error('SFTP connect') utils.exec_cmd(self.conf['ssh']) self.log.error('SFTP disconnect') sys.exit(0) else: self.log.error('*** forbidden SFTP connection') sys.exit(0) # initialize cli session from lshell.shellcmd import ShellCmd cli = ShellCmd(self.conf, None, None, None, None, self.conf['ssh']) ret_check_path, self.conf = sec.check_path(self.conf['ssh'], self.conf, ssh=1) if ret_check_path == 1: self.ssh_warn('path over SSH', self.conf['ssh']) # check if scp is requested and allowed if self.conf['ssh'].startswith('scp '): if self.conf['scp'] is 1 or 'scp' in self.conf['overssh']: if ' -f ' in self.conf['ssh']: # case scp download is allowed if self.conf['scp_download']: self.log.error('SCP: GET "%s"' % self.conf['ssh']) # case scp download is forbidden else: self.log.error( 'SCP: download forbidden: "%s"' % self.conf['ssh']) sys.exit(0) elif ' -t ' in self.conf['ssh']: # case scp upload is allowed if self.conf['scp_upload']: if 'scpforce' in self.conf: cmdsplit = self.conf['ssh'].split(' ') scppath = os.path.realpath(cmdsplit[-1]) forcedpath = os.path.realpath( self.conf['scpforce']) if scppath != forcedpath: self.log.error('SCP: forced SCP ' 'directory: %s' % scppath) cmdsplit.pop(-1) cmdsplit.append(forcedpath) self.conf['ssh'] = string.join( cmdsplit) self.log.error('SCP: PUT "%s"' % self.conf['ssh']) # case scp upload is forbidden else: self.log.error('SCP: upload forbidden: "%s"' % self.conf['ssh']) sys.exit(0) utils.exec_cmd(self.conf['ssh']) self.log.error('SCP disconnect') sys.exit(0) else: self.ssh_warn('SCP connection', self.conf['ssh'], 'scp') # check if command is in allowed overssh commands elif self.conf['ssh']: # replace aliases self.conf['ssh'] = utils.get_aliases( self.conf['ssh'], self.conf['aliases']) # if command is not "secure", exit ret_check_secure, self.conf = sec.check_secure( self.conf['ssh'], self.conf, strict=1, ssh=1) if ret_check_secure: self.ssh_warn('char/command over SSH', self.conf['ssh']) # else self.log.error('Over SSH: "%s"' % self.conf['ssh']) # if command is "help" if self.conf['ssh'] == "help": cli.do_help(None) else: utils.exec_cmd(self.conf['ssh']) self.log.error('Exited') sys.exit(0) # else warn and log else: self.ssh_warn('command over SSH', self.conf['ssh']) else: # case of shell escapes self.ssh_warn('shell escape', self.conf['ssh'])
def __getattr__(self, attr): """ This method actually takes care of all the called method that are not resolved (i.e not existing methods). It actually will simulate the existence of any method entered in the 'allowed' variable list. e.g. You just have to add 'uname' in list of allowed commands in the 'allowed' variable, and lshell will react as if you had added a do_uname in the ShellCmd class! """ # expand environment variables in command line self.g_cmd = os.path.expandvars(self.g_cmd) self.g_line = os.path.expandvars(self.g_line) self.g_arg = os.path.expandvars(self.g_arg) # in case the configuration file has been modified, reload it if self.conf['config_mtime'] != os.path.getmtime( self.conf['configfile']): from lshell.checkconfig import CheckConfig self.conf = CheckConfig(['--config', self.conf['configfile']], refresh=1).returnconf() self.conf['promptprint'] = utils.updateprompt( os.getcwd(), self.conf) self.log = self.conf['logpath'] if self.g_cmd in ['quit', 'exit', 'EOF']: self.log.error('Exited') if self.g_cmd == 'EOF': self.stdout.write('\n') if self.conf['disable_exit'] != 1: sys.exit(0) # check that commands/chars present in line are allowed/secure ret_check_secure, self.conf = sec.check_secure( self.g_line, self.conf, strict=self.conf['strict']) if ret_check_secure == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 return object.__getattribute__(self, attr) # check that path present in line are allowed/secure ret_check_path, self.conf = sec.check_path(self.g_line, self.conf, strict=self.conf['strict']) if ret_check_path == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 # in case request was sent by WinSCP, return error code has to be # sent via an specific echo command if self.conf['winscp'] and re.search('WinSCP: this is end-of-file', self.g_line): utils.exec_cmd('echo "WinSCP: this is end-of-file: %s"' % self.retcode) return object.__getattribute__(self, attr) if self.g_cmd in self.conf['allowed']: if self.conf['timer'] > 0: self.mytimer(0) self.g_arg = re.sub('^~$|^~/', '%s/' % self.conf['home_path'], self.g_arg) self.g_arg = re.sub(' ~/', ' %s/' % self.conf['home_path'], self.g_arg) # replace previous command exit code # in case multiple commands (using separators), only replace first # command. Regex replaces all occurrences of $?, before ;,&,| if re.search('[;&|]', self.g_line): p = re.compile("(\s|^)(\$\?)([\s|$]?[;&|].*)") else: p = re.compile("(\s|^)(\$\?)(\s|$)") self.g_line = p.sub(r' %s \3' % self.retcode, self.g_line) if type(self.conf['aliases']) == dict: self.g_line = utils.get_aliases(self.g_line, self.conf['aliases']) self.log.info('CMD: "%s"' % self.g_line) if self.g_cmd == 'cd': # split cd <dir> and rest of command cmd_split = re.split(';|&&|&|\|\||\|', self.g_line, 1) # in case the are commands following cd, first change the # directory, then execute the command if len(cmd_split) == 2: directory, command = cmd_split # only keep cd's argument directory = directory.split('cd', 1)[1].strip() # change directory then, if success, execute the rest of # the cmd line self.retcode, self.conf = builtins.cd(directory, self.conf) if self.retcode == 0: self.retcode = utils.exec_cmd(command) else: # set directory to command line argument and change dir directory = self.g_arg self.retcode, self.conf = builtins.cd(directory, self.conf) # built-in lpath function: list all allowed path elif self.g_cmd == 'lpath': self.retcode = builtins.lpath(self.conf) # built-in lsudo function: list all allowed sudo commands elif self.g_cmd == 'lsudo': self.retcode = builtins.lsudo(self.conf) # built-in history function: print command history elif self.g_cmd == 'history': self.retcode = builtins.history(self.conf, self.log) # built-in export function elif self.g_cmd == 'export': self.retcode, var = builtins.export(self.g_line) if self.retcode == 1: self.log.critical( "** forbidden environment variable '%s'" % var) # case 'cd' is in an alias e.g. {'toto':'cd /var/tmp'} elif self.g_line[0:2] == 'cd': self.g_cmd = self.g_line.split()[0] directory = ' '.join(self.g_line.split()[1:]) self.retcode, self.conf = builtins.cd(directory, self.conf) else: self.retcode = utils.exec_cmd(self.g_line) elif self.g_cmd not in ['', '?', 'help', None]: self.log.warn('INFO: unknown syntax -> "%s"' % self.g_line) self.stderr.write('*** unknown syntax: %s\n' % self.g_cmd) self.g_cmd, self.g_arg, self.g_line = ['', '', ''] if self.conf['timer'] > 0: self.mytimer(self.conf['timer']) return object.__getattribute__(self, attr)
def cmdloop(self, intro=None): """Repeatedly issue a prompt, accept input, parse an initial prefix off the received input, and dispatch to action methods, passing them the remainder of the line as argument. """ self.preloop() if self.use_rawinput and self.completekey: try: readline.read_history_file(self.conf['history_file']) readline.set_history_length(self.conf['history_size']) except IOError: # if history file does not exist try: open(self.conf['history_file'], 'w').close() readline.read_history_file(self.conf['history_file']) except IOError: pass readline.set_completer_delims( readline.get_completer_delims().replace('-', '')) self.old_completer = readline.get_completer() readline.set_completer(self.complete) readline.parse_and_bind(self.completekey + ": complete") try: if self.intro and isinstance(self.intro, str): self.stdout.write("%s\n" % self.intro) if self.conf['login_script']: utils.exec_cmd(self.conf['login_script']) stop = None while not stop: if self.cmdqueue: line = self.cmdqueue.pop(0) else: if self.use_rawinput: try: # raw_input renamed as input in py3 try: line = raw_input(self.conf['promptprint']) except NameError: line = input(self.conf['promptprint']) except EOFError: line = 'EOF' except KeyboardInterrupt: self.stdout.write('\n') line = '' else: self.stdout.write(self.conf['promptprint']) self.stdout.flush() line = self.stdin.readline() if not len(line): line = 'EOF' else: # chop \n line = line[:-1] line = self.precmd(line) stop = self.onecmd(line) stop = self.postcmd(stop, line) self.postloop() finally: if self.use_rawinput and self.completekey: try: readline.set_completer_delims( readline.get_completer_delims().replace('-', '')) readline.set_completer(self.old_completer) except ImportError: pass try: readline.write_history_file(self.conf['history_file']) except IOError: self.log.error('WARN: couldn\'t write history ' 'to file %s\n' % self.conf['history_file'])
def check_scp_sftp(self): """ This method checks if the user is trying to SCP a file onto the server. If this is the case, it checks if the user is allowed to use SCP or not, and acts as requested. : ) """ if 'ssh' in self.conf: if 'SSH_CLIENT' in os.environ \ and 'SSH_TTY' not in os.environ: # check if sftp is requested and allowed if 'sftp-server' in self.conf['ssh']: if self.conf['sftp'] is 1: self.log.error('SFTP connect') utils.exec_cmd(self.conf['ssh']) self.log.error('SFTP disconnect') sys.exit(0) else: self.log.error('*** forbidden SFTP connection') sys.exit(0) # initialize cli session from lshell.shellcmd import ShellCmd cli = ShellCmd(self.conf, None, None, None, None, self.conf['ssh']) ret_check_path, self.conf = sec.check_path(self.conf['ssh'], self.conf, ssh=1) if ret_check_path == 1: self.ssh_warn('path over SSH', self.conf['ssh']) # check if scp is requested and allowed if self.conf['ssh'].startswith('scp '): if self.conf['scp'] is 1 or 'scp' in self.conf['overssh']: if ' -f ' in self.conf['ssh']: # case scp download is allowed if self.conf['scp_download']: self.log.error('SCP: GET "%s"' % self.conf['ssh']) # case scp download is forbidden else: self.log.error('SCP: download forbidden: "%s"' % self.conf['ssh']) sys.exit(0) elif ' -t ' in self.conf['ssh']: # case scp upload is allowed if self.conf['scp_upload']: if 'scpforce' in self.conf: cmdsplit = self.conf['ssh'].split(' ') scppath = os.path.realpath(cmdsplit[-1]) forcedpath = os.path.realpath(self.conf ['scpforce']) if scppath != forcedpath: self.log.error('SCP: forced SCP ' 'directory: %s' % scppath) cmdsplit.pop(-1) cmdsplit.append(forcedpath) self.conf['ssh'] = string.join( cmdsplit) self.log.error('SCP: PUT "%s"' % self.conf['ssh']) # case scp upload is forbidden else: self.log.error('SCP: upload forbidden: "%s"' % self.conf['ssh']) sys.exit(0) utils.exec_cmd(self.conf['ssh']) self.log.error('SCP disconnect') sys.exit(0) else: self.ssh_warn('SCP connection', self.conf['ssh'], 'scp') # check if command is in allowed overssh commands elif self.conf['ssh']: # replace aliases self.conf['ssh'] = utils.get_aliases(self.conf['ssh'], self.conf['aliases']) # if command is not "secure", exit ret_check_secure, self.conf = sec.check_secure( self.conf['ssh'], self.conf, strict=1, ssh=1) if ret_check_secure: self.ssh_warn('char/command over SSH', self.conf['ssh']) # else self.log.error('Over SSH: "%s"' % self.conf['ssh']) # if command is "help" if self.conf['ssh'] == "help": cli.do_help(None) else: utils.exec_cmd(self.conf['ssh']) self.log.error('Exited') sys.exit(0) # else warn and log else: self.ssh_warn('command over SSH', self.conf['ssh']) else: # case of shell escapes self.ssh_warn('shell escape', self.conf['ssh'])
def __getattr__(self, attr): """ This method actually takes care of all the called method that are not resolved (i.e not existing methods). It actually will simulate the existence of any method entered in the 'allowed' variable list. e.g. You just have to add 'uname' in list of allowed commands in the 'allowed' variable, and lshell will react as if you had added a do_uname in the ShellCmd class! """ # expand environment variables in command line self.g_cmd = os.path.expandvars(self.g_cmd) self.g_line = os.path.expandvars(self.g_line) self.g_arg = os.path.expandvars(self.g_arg) # in case the configuration file has been modified, reload it if self.conf['config_mtime'] != os.path.getmtime( self.conf['configfile']): from lshell.checkconfig import CheckConfig self.conf = CheckConfig(['--config', self.conf['configfile']], refresh=1).returnconf() self.conf['promptprint'] = utils.updateprompt(os.getcwd(), self.conf) self.log = self.conf['logpath'] if self.g_cmd in ['quit', 'exit', 'EOF']: self.log.error('Exited') if self.g_cmd == 'EOF': self.stdout.write('\n') if self.conf['disable_exit'] != 1: sys.exit(0) # check that commands/chars present in line are allowed/secure ret_check_secure, self.conf = sec.check_secure( self.g_line, self.conf, strict=self.conf['strict']) if ret_check_secure == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 return object.__getattribute__(self, attr) # check that path present in line are allowed/secure ret_check_path, self.conf = sec.check_path(self.g_line, self.conf, strict=self.conf['strict']) if ret_check_path == 1: # see http://tldp.org/LDP/abs/html/exitcodes.html self.retcode = 126 # in case request was sent by WinSCP, return error code has to be # sent via an specific echo command if self.conf['winscp'] and re.search('WinSCP: this is end-of-file', self.g_line): utils.exec_cmd('echo "WinSCP: this is end-of-file: %s"' % self.retcode) return object.__getattribute__(self, attr) if self.g_cmd in self.conf['allowed']: if self.conf['timer'] > 0: self.mytimer(0) self.g_arg = re.sub('^~$|^~/', '%s/' % self.conf['home_path'], self.g_arg) self.g_arg = re.sub(' ~/', ' %s/' % self.conf['home_path'], self.g_arg) # replace previous command exit code # in case multiple commands (using separators), only replace first # command. Regex replaces all occurrences of $?, before ;,&,| if re.search('[;&\|]', self.g_line): p = re.compile("(\s|^)(\$\?)([\s|$]?[;&|].*)") else: p = re.compile("(\s|^)(\$\?)(\s|$)") self.g_line = p.sub(r' %s \3' % self.retcode, self.g_line) if type(self.conf['aliases']) == dict: self.g_line = utils.get_aliases(self.g_line, self.conf['aliases']) self.log.info('CMD: "%s"' % self.g_line) if self.g_cmd == 'cd': # split cd <dir> and rest of command cmd_split = re.split(';|&&|&|\|\||\|', self.g_line, 1) # in case the are commands following cd, first change the # directory, then execute the command if len(cmd_split) == 2: directory, command = cmd_split # only keep cd's argument directory = directory.split('cd', 1)[1].strip() # change directory then, if success, execute the rest of # the cmd line self.retcode, self.conf = builtins.cd(directory, self.conf) if self.retcode == 0: self.retcode = utils.exec_cmd(command) else: # set directory to command line argument and change dir directory = self.g_arg self.retcode, self.conf = builtins.cd(directory, self.conf) # built-in lpath function: list all allowed path elif self.g_cmd == 'lpath': self.retcode = builtins.lpath(self.conf) # built-in lsudo function: list all allowed sudo commands elif self.g_cmd == 'lsudo': self.retcode = builtins.lsudo(self.conf) # built-in history function: print command history elif self.g_cmd == 'history': self.retcode = builtins.history(self.conf, self.log) # built-in export function elif self.g_cmd == 'export': self.retcode, var = builtins.export(self.g_line) if self.retcode == 1: self.log.critical("** forbidden environment variable '%s'" % var) # case 'cd' is in an alias e.g. {'toto':'cd /var/tmp'} elif self.g_line[0:2] == 'cd': self.g_cmd = self.g_line.split()[0] directory = ' '.join(self.g_line.split()[1:]) self.retcode, self.conf = builtins.cd(directory, self.conf) else: self.retcode = utils.exec_cmd(self.g_line) elif self.g_cmd not in ['', '?', 'help', None]: self.log.warn('INFO: unknown syntax -> "%s"' % self.g_line) self.stderr.write('*** unknown syntax: %s\n' % self.g_cmd) self.g_cmd, self.g_arg, self.g_line = ['', '', ''] if self.conf['timer'] > 0: self.mytimer(self.conf['timer']) return object.__getattribute__(self, attr)