def disable(commanddict, modulename): """ <Purpose> Disables a module and removes its commands from the seash commanddict. <Arguments> modulename: The module to disable. <Side Effects> All commands inside the specified module will be removed from the seash commanddict. A file (modulename.disabled) will be created under /modules/ indicating that this module has been disabled. <Exceptions> Exceptions raised by merge_commanddict() <Returns> None """ # Is this an installed module? if not modulename in module_data: raise seash_exceptions.UserError("Error, module '"+modulename+"' is not installed") # Is this module enabled? if not _is_module_enabled(modulename): raise seash_exceptions.UserError("Module is not enabled.") remove_commanddict(commanddict, module_data[modulename]['command_dict']) cleanup(modulename) # We mark this module as disabled by adding a modulename.disabled file. open(MODULES_FOLDER_PATH + os.sep + modulename + ".disabled", 'w')
def get(input_dict, environment_dict): """ <Purpose> Gets the specified vessels. <Arguments> input_dict: The commanddict representing the user's input. environment_dict: The dictionary representing the current seash environment. <Side Effects> Connects to the Clearinghouse and acquires vessels. Adds the acquired vessels to the list of valid targets. <Exceptions> None <Returns> None """ if environment_dict[ 'currentkeyname'] is None or not seash_global_variables.keys[ environment_dict['currentkeyname']]['privatekey']: raise seash_exceptions.UserError( "Error, must get as an identity with a private key") # Activate secure mode if user did not specify the insecure keyword allow_ssl_insecure = _get_user_argument(input_dict, 'insecure') is not None vesselcount = int(_get_user_argument(input_dict, 'vesselcount')) try: vesseltype = _get_user_argument(input_dict, 'type') # User may not have specified a vesseltype except IndexError: vesseltype = None if not vesseltype in ['wan', 'lan', 'nat', None]: raise seash_exceptions.UserError("Error, unknown vessel type '" + vesseltype + "'") client = _connect_to_clearinghouse(environment_dict['currentkeyname'], allow_ssl_insecure) # Get the vessels! try: if vesseltype is None: vesseldicts = client.acquire_random_resources(vesselcount) else: vesseldicts = client.acquire_resources(vesseltype, vesselcount) _update_targets(vesseldicts, environment_dict) except (seattleclearinghouse_xmlrpc.UnableToAcquireResourcesError, seattleclearinghouse_xmlrpc.NotEnoughCreditsError), e: print str(e)
def set_user_variable(input_dict, environment_dict): """ <Purpose> Seash callback to allow user to define a custom variable and assign a value to it. <Arguments> input_dict: Input dictionary generated by seash_dictionary.parse_command(). environment_dict: Dictionary describing the current seash environment. For more information, see command_callbacks.py's module docstring. <Side Effects> A new variable will be added to the uservariables dictionary. <Exceptions> UserError: The user did not provide a value to assign to the variable <Return> None """ # Iterates through the dictionary to retrieve the variable name command_key = input_dict.keys()[0] while input_dict[command_key]['name'] is not 'variable_name': input_dict = input_dict[command_key]['children'] variable_name = command_key = input_dict.keys()[0] # User may accidentally omit this value, we must catch the exception and # return something readable... try: # Iterates through the dictionary to retrieve the variable value while input_dict[command_key]['name'] is not 'variable_value': input_dict = input_dict[command_key]['children'] variable_value = command_key = input_dict.keys()[0] except IndexError, e: raise seash_exceptions.UserError( "Error, expected a value to assign to variable")
def show_coordinates(input_dict, environment_dict): if not environment_dict['currenttarget']: raise seash_exceptions.UserError("Error, command requires a target") geoip_init_client() # we should only visit a node once... printedIPlist = [] for longname in seash_global_variables.targets[ environment_dict['currenttarget']]: thisnodeIP = seash_global_variables.vesselinfo[longname]['IP'] # if we haven't visited this node if thisnodeIP not in printedIPlist: printedIPlist.append(thisnodeIP) location_dict = geoip_record_by_addr(thisnodeIP) if location_dict: print str(seash_global_variables.vesselinfo[longname] ['ID']) + '(' + str(thisnodeIP) + '): ' + str( location_dict['latitude']) + ", " + str( location_dict['longitude']) else: print str(seash_global_variables.vesselinfo[longname]['ID'] ) + '(' + str(thisnodeIP) + '): Location unknown'
def print_module_help(input_dict, environment_dict): """ <Purpose> Displays the module level help for a module. <Arguments> input_dict: The commanddict representing the user's input. environment_dict: The dictionary representing the current seash environment. <Side Effects> The helptext for the module specified in the 'modulename' node will be printed to stdout. <Exceptions> UserError: input_dict did not contain a 'modulename' node, or the user specified a module that is not installed. <Returns> None """ # Get the modulename dict_mark = input_dict try: command = dict_mark.keys()[0] while dict_mark[command]['name'] != 'modulename': dict_mark = input_dict[command]['children'] command = dict_mark.keys()[0] modulename = command except IndexError: raise seash_exceptions.UserError( "Error, command requires a modulename") # Is this module installed? if not modulename in seash_modules.module_data: raise seash_exceptions.UserError("Module is not installed.") print seash_modules.module_data[modulename]['help_text'] # Now, print out all the commands under this module print "Commands in this module:" print '\n'.join( seash_helper.get_commands_from_commanddict( seash_modules.module_data[modulename]['command_dict']))
def check_key_set(name): if (name in seash_global_variables.keys and 'publickey' in seash_global_variables.keys[name] and 'privatekey' in seash_global_variables.keys[name] and seash_global_variables.keys[name]['publickey'] and seash_global_variables.keys[name]['privatekey']): if not check_key_pair_compatibility(name): raise seash_exceptions.UserError( "Error: Mis-matched Public/Private key pair!")
def disable_module(input_dict, environment_dict): """ <Purpose> Disables an enabled module. <Arguments> input_dict: The commanddict representing the user's input. environment_dict: The dictionary representing the current seash environment. <Side Effects> The module specified in the 'modulename' node will be enabled. <Exceptions> UserError: input_dict did not contain a 'modulename' node, or the user tried to disable this module. <Returns> None """ # Get the modulename dict_mark = input_dict try: command = dict_mark.keys()[0] while dict_mark[command]['name'] != 'modulename': dict_mark = input_dict[command]['children'] command = dict_mark.keys()[0] modulename = command except IndexError: raise seash_exceptions.UserError( "Error, command requires a modulename") if modulename == 'modules': raise seash_exceptions.UserError( "Error, cannot disable the 'modules' module") seash_modules.disable(seash_dictionary.seashcommanddict, modulename)
def enable(commanddict, modulename): """ <Purpose> Enables a module and imports its commands into the seash commanddict. <Arguments> modulename: The module to import. <Side Effects> All commands inside the specified module will be inserted into the seash commanddict if possible. The file modulename.disabled will be removed from /modules/ indicating that this module has been enabled. <Exceptions> Exceptions raised by merge_commanddict() <Returns> None """ # Is this an installed module? if not modulename in module_data: raise seash_exceptions.UserError("Error, module '"+modulename+"' is not installed") if _is_module_enabled(modulename): raise seash_exceptions.UserError("Module is already enabled.") merge_commanddict(commanddict, module_data[modulename]['command_dict']) try: # We mark this module as enabled by deleting the modulename.disabled file os.remove(MODULES_FOLDER_PATH + os.sep + modulename + ".disabled") except OSError, e: # If the file was deleted before we were able to delete it, it should not # be a problem. if not "cannot find the file" in str(e): raise
def _connect_to_clearinghouse(identity, allow_ssl_insecure): try: return seattleclearinghouse_xmlrpc.SeattleClearinghouseClient( username=identity, private_key_string=rsa_privatekey_to_string( seash_global_variables.keys[identity]['privatekey']), allow_ssl_insecure=allow_ssl_insecure) except ImportError, e: if "M2Crypto" in str(e): raise seash_exceptions.UserError("You must have M2Crypto " + "installed to connect to the Clearinghouse securely.\n" + "For more information, run 'modulehelp clearinghouse'.") # Unknown error... raise
def savestate(statefn, handleinfo, host, port, expnum, filename, cmdargs, defaulttarget, defaultkeyname, autosave, currentkeyname): # obtain the handle info dictionary for longname in seash_global_variables.vesselinfo.keys(): vessel_handle = seash_global_variables.vesselinfo[longname]['handle'] handleinfo[longname] = fastnmclient.nmclient_get_handle_info( vessel_handle) state = {} state['targets'] = seash_global_variables.targets state['keys'] = seash_global_variables.keys state['vesselinfo'] = seash_global_variables.vesselinfo state['nextid'] = seash_global_variables.nextid state['handleinfo'] = handleinfo state['host'] = host state['port'] = port state['expnum'] = expnum state['filename'] = filename state['cmdargs'] = cmdargs state['defaulttarget'] = defaulttarget state['defaultkeyname'] = defaultkeyname state['autosave'] = autosave state['globalseashtimeout'] = seash_global_variables.globalseashtimeout state['globaluploadrate'] = seash_global_variables.globaluploadrate # serialize states and encrypt if seash_global_variables.keys.has_key(defaultkeyname): cypher = rsa_encrypt( serialize_serializedata(state), seash_global_variables.keys[currentkeyname]['publickey']) else: raise seash_exceptions.UserError("The keyname '" + defaultkeyname + "' is not loaded.") # writing encrypted serialized states to file # Exceptions are caught outside of the method try: state_obj = open(statefn, 'w') state_obj.write(cypher) finally: state_obj.close()
def show_coordinates(input_dict, environment_dict): if not environment_dict['currenttarget']: raise seash_exceptions.UserError("Error, command requires a target") geoip_init_client() # we should only visit a node once... printedIPlist = [] for longname in seash_global_variables.targets[ environment_dict['currenttarget']]: thisnodeIP = seash_global_variables.vesselinfo[longname]['IP'] # if we haven't visited this node if thisnodeIP not in printedIPlist: printedIPlist.append(thisnodeIP) try: # The GeoIP server doesn't understand hostnames. thisnodeIP # will be a hostname if it is a NAT node and we're using # Affixes. node_ip = gethostbyname(thisnodeIP) location_dict = geoip_record_by_addr(node_ip) # Name resolution and geoip lookups could fail, in which case # we just don't know where the node is. except: location_dict = None if location_dict: print str(seash_global_variables.vesselinfo[longname] ['ID']) + '(' + str(thisnodeIP) + '): ' + str( location_dict['latitude']) + ", " + str( location_dict['longitude']) else: print str(seash_global_variables.vesselinfo[longname]['ID'] ) + '(' + str(thisnodeIP) + '): Location unknown'
def enable_module(input_dict, environment_dict): """ <Purpose> Enables an installed module. <Arguments> input_dict: The commanddict representing the user's input. environment_dict: The dictionary representing the current seash environment. <Side Effects> The module specified in the 'modulename' node will be enabled. <Exceptions> UserError: input_dict did not contain a 'modulename' node. <Returns> None """ # Get the modulename dict_mark = input_dict try: command = dict_mark.keys()[0] while dict_mark[command]['name'] != 'modulename': dict_mark = input_dict[command]['children'] command = dict_mark.keys()[0] modulename = command except IndexError: raise seash_exceptions.UserError( "Error, command requires a modulename") try: seash_modules.enable(seash_dictionary.seashcommanddict, modulename) except seash_exceptions.ModuleConflictError, e: print "Module cannot be imported due to the following conflicting command:" print str(e)
def upload_directory_contents(input_dict, environment_dict): """This function serves to upload every file in a user-supplied source directory to all of the vessels in the current target group. It essentially calls seash's `upload` function repeatedly, each time with a file name taken from the source directory. A note on the input_dict argument: `input_dict` contains our own `command_dict` (see below), with the `"[ARGUMENT]"` sub-key of `children` renamed to what argument the user provided. In our case, this will be the source dir to read from. (If not, this is an error!) """ # Check user input and seash state: # 1, Make sure there is an active user key. if environment_dict["currentkeyname"] is None: raise seash_exceptions.UserError( """Error: Please set an identity before using 'uploaddir'! Example: !> loadkeys your_user_name !> as your_user_name your_user_name@ !> """) # 2, Make sure there is a target to work on. if environment_dict["currenttarget"] is None: raise seash_exceptions.UserError( """Error: Please set a target to work on before using 'uploaddir'! Example your_user_name@ !> on browsegood your_user_name@browsegood !> """) # 3, Complain if we don't have a source dir argument try: source_directory = input_dict["uploaddir"]["children"].keys()[0] except IndexError: raise seash_exceptions.UserError( """Error: Missing operand to 'uploaddir' Please specify which source directory's contents you want uploaded, e.g. your_user_name@browsegood !> uploaddir a_local_directory """) # Sanity check: Does the source dir exist? if not os.path.exists(source_directory): raise seash_exceptions.UserError("Error: Source directory '" + source_directory + "' does not exist.") # Sanity check: Is the source dir a directory? if not os.path.isdir(source_directory): raise seash_exceptions.UserError( "Error: Source directory '" + source_directory + "' is not a directory.\nDid you mean to use the 'upload' command instead?" ) # Alright --- user input and seash state seem sane, let's do the work! # These are the files we will need to upload: file_list = os.listdir(source_directory) for filename in file_list: # We construct the filename-to-be uploaded from the source dir, # the OS-specific path separator, and the actual file name. # This is enough for `upload_target` to find the file. path_and_filename = source_directory + os.sep + filename if not os.path.isdir(path_and_filename): print "Uploading '" + path_and_filename + "'..." # Construct an input_dict containing command args for seash's # `upload FILENAME` function. # XXX There might be a cleaner way to do this. faked_input_dict = { "upload": { "name": "upload", "children": { path_and_filename: { "name": "filename" } } } } command_callbacks.upload_filename(faked_input_dict, environment_dict) else: print "Skipping sub-directory '" + filename + "'. You may upload it separately."
def simplify_command(input_dict, environment_dict): """This function simplifies three library files specified in `start`: dylink.r2py, encasementlib.r2py, and sensor_layer.r2py. When a program is run by `execute`, these three files do not need to be specified. A note on the input_dict argument: `input_dict` contains our own `command_dict` (see below), with the `"[ARGUMENT]"` sub-key of `children` renamed to what argument the user provided. """ #print "In simplify_command, input_dict is ", #pprint.pprint(input_dict) #print # Check user input and seash state: # 1, Make sure there is an active user key. if environment_dict["currentkeyname"] is None: raise seash_exceptions.UserError("""Error: Please set an identity! Example: !> loadkeys your_user_name !> as your_user_name your_user_name@ !> """) # 2, Make sure there is a target to work on. if environment_dict["currenttarget"] is None: raise seash_exceptions.UserError( """Error: Please set a target to work on! Example your_user_name@ !> on browsegood your_user_name@browsegood !> """) # currently support one experiment with a (unlimited) number of args experiment = input_dict['execute']['children'].keys()[0] # Construct an input_dict containing command args for seash's # `upload FILENAME` function. # XXX There might be a cleaner way to do this. faked_input_dict = { "start": { "name": "start", "callback": None, "children": { "dylink.r2py": { "name": "filename", "callback": command_callbacks.start_remotefn, "children": { "encasementlib.r2py sensor_layer.r2py " + experiment: { "name": "args", "callback": command_callbacks.start_remotefn_arg, "children": {} } } } } } } command_callbacks.start_remotefn_arg(faked_input_dict, environment_dict)
allow_ssl_insecure=allow_ssl_insecure) except ImportError, e: if "M2Crypto" in str(e): raise seash_exceptions.UserError("You must have M2Crypto " + "installed to connect to the Clearinghouse securely.\n" + "For more information, run 'modulehelp clearinghouse'.") # Unknown error... raise except seattleclearinghouse_xmlrpc.SeattleClearinghouseError, e: if not allow_ssl_insecure and "No CA certs found in file" in str(e): print ("Your CA Certs file is corrupt or does not exist.\n" + "You need to download a CA certs file (you can do so here: \n" + "http://curl.haxx.se/ca/cacert.pem) and place it in the following location.") # Re-raise to get Seash to print out this error message raise seash_exceptions.UserError(str(e)) # Unknown error... raise def _get_user_argument(input_dict, argname): # Iterates through the dictionary to retrieve the user's argument command_key = input_dict.keys()[0] while input_dict[command_key]['name'] is not argname: input_dict = input_dict[command_key]['children'] # No more children, argument couldn't be found. if not input_dict: return None command_key = input_dict.keys()[0] return command_key
def preprocess_user_variables(userinput): """ <Purpose> Command parser for user variables. Takes the raw userinput and replaces each user variable with its set value. <Arguments> userinput: A raw user string <Side Effects> Each user variable will be replaced by the value that it was previously set to. <Exceptions> UserError: User typed an unrecognized or invalid variable name <Returns> The preprocessed string """ retstr = "" while '$' in userinput: text_before_variable, variable_delimiter, userinput = userinput.partition( '$') retstr += text_before_variable # Treat $$ as an escape for a single $. # Also escape if there is nothing left if not userinput or userinput.startswith('$'): retstr += '$' userinput = userinput[1:] continue # Look for the next space, or the next $. The closest one of these will be # used as the delimiter. Then update the remaining user input. space_variable_length = userinput.find(' ') dollarsign_variable_length = userinput.find('$') # If the length is -1, then the delimiter was not found. # We use the length of the entire string to represent this. # If there was one delimiter found, then that delimiter's value # will always be less than the string's length. # If it is a tie, then it simply means that the entire string # is the variable name. if space_variable_length == -1: space_variable_length = len(userinput) if dollarsign_variable_length == -1: dollarsign_variable_length = len(userinput) variable_length = min(space_variable_length, dollarsign_variable_length) variable_name = userinput[:variable_length] userinput = userinput[variable_length + 1:] # Skip the actual delimiter # Perform the replacement! # User may type in a variable that has not yet been defined try: retstr += uservariables[variable_name] except KeyError: raise seash_exceptions.UserError("Variable does not exist: " + variable_name) # The user expects a space before the string right after the variable. # e.g. 'loadkeys $myname as awesome' should turn into # 'loadkeys theusername as awesome' if space_variable_length < dollarsign_variable_length: retstr += ' ' # Now add the remaining text after the last variable else: retstr += userinput return retstr
def command_loop(test_command_list): # If a test command list is passed, filter the tab completion warning if test_command_list: warnings.filterwarnings("ignore", "Auto tab completion is off, because it is not available on your operating system.", ImportWarning) # Things that may be set herein and used in later commands. # Contains the local variables of the original command loop. # Keeps track of the user's state in seash. Referenced # during command executions by the command_parser. environment_dict = { 'host': None, 'port': None, 'expnum': None, 'filename': None, 'cmdargs': None, 'defaulttarget': None, 'defaultkeyname': None, 'currenttarget': None, 'currentkeyname': None, 'autosave': False, 'handleinfo': {}, 'showparse': True, } # Set up the tab completion environment (Added by Danny Y. Huang) if tabcompletion: # Initializes seash's tab completer completer = tab_completer.Completer() readline.parse_and_bind("tab: complete") # Determines when a new tab complete instance should be initialized, # which, in this case, is never, so the tab completer will always take # the entire user's string into account readline.set_completer_delims("") # Sets the completer function that readline will utilize readline.set_completer(completer.complete) else: warnings.warn("Auto tab completion is off, because it is not available on your operating system.",ImportWarning) # If passed a list of commands, do not prompt for user input if test_command_list: seash_helper.update_time() # Iterates through test_command_list in sequential order for command_strings in test_command_list: # Saving state after each command? (Added by Danny Y. Huang) if environment_dict['autosave'] and environment_dict['defaultkeyname']: try: # State is saved in file "autosave_username", so that user knows which # RSA private key to use to reload the state. autosavefn = "autosave_" + str(environment_dict['defaultkeyname']) seash_helper.savestate(autosavefn, environment_dict['handleinfo'], environment_dict['host'], environment_dict['port'], environment_dict['expnum'], environment_dict['filename'], environment_dict['cmdargs'], environment_dict['defaulttarget'], environment_dict['defaultkeyname'], environment_dict['autosave'], environment_dict['defaultkeyname']) except Exception, error: raise seash_exceptions.UserError("There is an error in autosave: '" + str(error) + "'. You can turn off autosave using the command 'set autosave off'.") # Returns the dictionary of dictionaries that correspond to the # command string cmd_input = seash_dictionary.parse_command(command_strings, display_parsed_result=environment_dict['showparse']) # by default, use the target specified in the prompt environment_dict['currenttarget'] = environment_dict['defaulttarget'] # by default, use the identity specified in the prompt environment_dict['currentkeyname'] = environment_dict['defaultkeyname'] # calls the command_dispatch method of seash_dictionary to execute the callback # method associated with the command the user inputed seash_dictionary.command_dispatch(cmd_input, environment_dict)
def print_factoids(input_dict, environment_dict): """ <Purpose> Used to print seash factoids when user uses 'show factoids' command. <Arguments> input_dict: Input dictionary generated by seash_dictionary.parse_command(). environment_dict: Dictionary describing the current seash environment. For more information, see command_callbacks.py's module docstring. <Side Effects> Prints factoids onto the screen. <Exceptions> UserError: If user does not type appropriate command. ValueError: If user does not provide valid input (integer). <Return> None """ # User will insert an argument regarding how many factoids should be printed. # We have to find what is user argument. # User can type any positive number or he can type 'all' to see all factoids. dict_mark = input_dict try: command = dict_mark.keys()[0] while dict_mark[command]['name'] != 'args': dict_mark = dict_mark[command]['children'] command = dict_mark.keys()[0] args = command except IndexError: raise seash_exceptions.UserError( "\nError, Syntax of the command is: show factoids [number of factoids]/all \n" ) # User decided to print all factoids if args == 'all': print for factoid in factoids: print factoid print return # User can not insert other than integer number. try: no_of_factoids = int(args) except ValueError: raise seash_exceptions.UserError("\nYou have to enter number only.\n") # If number of factoids decided by user is greater than total number of # available factoids than whole factoids list is printed. if (no_of_factoids > (len(factoids))): print "\nWe have only %d factoids. Here is the list of factoids:" % ( len(factoids)) no_of_factoids = len(factoids) elif (no_of_factoids <= 0): raise seash_exceptions.UserError( "\nYou have to enter positive number only.\n") # 'factoids' list will be shuffled every time for printing random factoids. random.shuffle(factoids) # Factoids will be printed. for factoid in factoids[:no_of_factoids]: print factoid print
completer.set_target_list() completer.set_keyname_list() # Saving state after each command? (Added by Danny Y. Huang) if environment_dict['autosave'] and environment_dict['defaultkeyname']: try: # State is saved in file "autosave_username", so that user knows which # RSA private key to use to reload the state. autosavefn = "autosave_" + str(environment_dict['defaultkeyname']) seash_helper.savestate(autosavefn, environment_dict['handleinfo'], environment_dict['host'], environment_dict['port'], environment_dict['expnum'], environment_dict['filename'], environment_dict['cmdargs'], environment_dict['defaulttarget'], environment_dict['defaultkeyname'], environment_dict['autosave'], environment_dict['defaultkeyname']) except Exception, error: raise seash_exceptions.UserError("There is an error in autosave: '" + str(error) + "'. You can turn off autosave using the command 'set autosave off'.") prompt = '' if environment_dict['defaultkeyname']: prompt = seash_helper.fit_string(environment_dict['defaultkeyname'],20)+"@" # display the thing they are acting on in their prompt (if applicable) if environment_dict['defaulttarget']: prompt = prompt + seash_helper.fit_string(environment_dict['defaulttarget'],20) prompt = prompt + " !> " # the prompt should look like: justin@good !> # get the user input userinput = raw_input(prompt)
def simplify_command(input_dict, environment_dict): """This function simplifies the `start` command for the user by including three libraries: dylink.r2py, encasementlib.r2py, and sensor_layer.r2py When a program is run by `execute`, these three filenames do not need to be specified. A note on the `input_dict` argument: `input_dict` contains our own `command_dict` (see below), with the `"[ARGUMENT]"` sub-key of `children` renamed to what argument the user provided. """ # Check user input and seash state: # 1, Make sure there is an active user key. if environment_dict["currentkeyname"] is None: raise seash_exceptions.UserError( """Error: Please set an identity before using 'execute'! Example: !> loadkeys your_user_name !> as your_user_name your_user_name@ !> """) # 2, Make sure there is a target to work on. if environment_dict["currenttarget"] is None: raise seash_exceptions.UserError( """Error: Please set a target to work on before using 'execute'! Example your_user_name@ !> on browsegood your_user_name@browsegood !> """) try: user_files_and_args = input_dict["execute"]["children"].keys()[0] except IndexError: # The user didn't specify files to execute. raise seash_exceptions.UserError("""Error: Missing operand to 'execute' Please specify which file(s) I should execute, e.g. your_user_name@browsegood !> execute my_sensor_program.r2py """) # `commands_list`'s first item is the filename to feed into # `command_callbacks.start_remotefn`; all other items plus # the user-specified filenames/args are collected here: filenames_and_args = " ".join( commands_list[1:]) + " " + user_files_and_args # Construct an input_dict containing command args for seash's # `start FILENAME [ARGS]` function. # XXX There might be a cleaner way to do this. faked_input_dict = { "start": { "name": "start", 'callback': None, "children": { commands_list[0]: { "callback": command_callbacks.start_remotefn, "name": "filename", "children": { filenames_and_args: { "callback": command_callbacks.start_remotefn_arg, "children": {}, "name": "args" } } } } } } command_callbacks.start_remotefn_arg(faked_input_dict, environment_dict)