Beispiel #1
0
        logging.info("Syntax: trngcli.py SRV_ADDR:SRV_PORT POST_PARAMS")
        sys.exit(1)

    # If no http-like prefix exists, add the appropriate one
    if not (server_url.startswith(HTTP_PREFIX)
            or server_url.startswith(HTTPS_PREFIX)):
        if Storyboard.ENABLE_HTTPS:
            server_url = HTTPS_PREFIX + server_url
        else:
            server_url = HTTP_PREFIX + server_url

    logging.info(
        "CyTrONE training client connecting to {0}...".format(server_url))

    # Parse parameters
    params = query.Parameters()
    params.parse_parameters(POST_parameters)
    action = params.get(query.Parameters.ACTION)

    # Additional parameter needed for instantiate range action
    if action == query.Parameters.INSTANTIATE_RANGE and INSTANTIATE_RANGE_FROM_FILE:
        try:
            instantiate_file = open(SAMPLE_INSTANTIATE_RANGE, "r")
            instantiate_content = instantiate_file.read()
            instantiate_file.close()
            logging.info("Use cyber range description from file {0}.".format(
                SAMPLE_INSTANTIATE_RANGE))

            description_parameters = {
                query.Parameters.DESCRIPTION_FILE: instantiate_content
            }
Beispiel #2
0
    def do_POST(self):

        # Get the parameters of the POST request
        params = query.Parameters(self)

        if DEBUG:
            print SEPARATOR
            print "* DEBUG: instsrv: Client POST request: POST parameters: %s" % (params)

        # Get parameter values for given keys
        user_id = params.get(query.Parameters.USER)
        action = params.get(query.Parameters.ACTION)
        description_file = params.get(query.Parameters.DESCRIPTION_FILE)
        range_id = params.get(query.Parameters.RANGE_ID)

        if DEBUG:
            print SEPARATOR
            print "PARAMETERS:"
            print SEPARATOR
            print "USER: %s" % (user_id)
            print "ACTION: %s" % (action)
            print "DESCRIPTION FILE:\n%s" % (description_file) 
            print "RANGE_ID: %s" % (range_id)
            print SEPARATOR


        ## Handle user information

        # Get user information from YAML file
        # Note: Only reading data that is (potentially) modified externally =>
        #       no need for synchronization
        user_info = userinfo.UserInfo()
        if not user_info.parse_YAML_file(DATABASE_DIR + USERS_FILE):
            self.send_error(SERVER_ERROR, "User information issue")
            return
        if DEBUG:
            user_info.pretty_print()

        # Check that user id is valid
        user_obj = user_info.get_user(user_id) 
        if not user_obj:
            self.send_error(REQUEST_ERROR, "Invalid user id")
            return


        ## Handle action information

        # Check that action is valid
        if action not in self.VALID_ACTIONS:
            self.send_error(REQUEST_ERROR, "Invalid action")
            return

        # If we reached this point, it means processing was successful
        # => act according to each action
        if action == query.Parameters.INSTANTIATE_RANGE: 

            # Check that description is not empty
            if not description_file:
                self.send_error(REQUEST_ERROR, "Invalid description file")
                return

            # Check that range id was provided
            if not range_id:
                self.send_error(REQUEST_ERROR, "Invalid range id")
                return

            # Save the description received as a file
            try:
                range_file_name = RANGE_DESCRIPTION_TEMPLATE.format(range_id)
                range_file = open(range_file_name, "w")
                range_file.write(description_file)
                range_file.close()
                print "* INFO: instsrv: Saved POSTed cyber range description to file '%s'." % (range_file_name)
            except IOError:
                print "* ERROR: instsrv: Could not write to file %s." % (range_file_name)

            print "* INFO: instsrv: Start cyber range instantiation."

            # Use CyRIS to really do cyber range instantiation
            if USE_CYRIS:
                try:
                    command = "python -u " + CYRIS_PATH + "main/cyris.py " + range_file_name + " " + CYRIS_PATH + CYRIS_CONFIG_FILENAME
                    return_value = os.system(command)
                    exit_status = os.WEXITSTATUS(return_value)
                    if exit_status != 0:
                        self.handle_cyris_error(range_id)
                        self.send_error(SERVER_ERROR, "CyRIS execution issue")
                        return

                    status_filename = CYRIS_PATH + CYRIS_RANGE_DIRECTORY + str(range_id) + "/" + CYRIS_STATUS_FILENAME
                    with open(status_filename, 'r') as status_file:
                        status_file_content = status_file.read()
                        if DEBUG:
                            print "* DEBUG: instsrv: Status file content=", status_file_content
                        if Storyboard.SERVER_STATUS_SUCCESS in status_file_content:

                            # Get notification text
                            notification_filename_short = CYRIS_NOTIFICATION_TEMPLATE.format(range_id) 
                            notification_filename = "{0}{1}{2}/{3}".format(CYRIS_PATH,
                                                                           CYRIS_RANGE_DIRECTORY,
                                                                           range_id,
                                                                           notification_filename_short)
                            if DEBUG:
                                print "* DEBUG: instsrv: Notification file name=", notification_filename

                            message = None
                            with open(notification_filename, 'r') as notification_file:
                                notification_file_content = notification_file.read()
                                message = urllib.quote(notification_file_content)

                            response_content = self.build_response(Storyboard.SERVER_STATUS_SUCCESS, message)

                            # We try to prepare the terminal for Moodle, but
                            # errors are only considered as warnings for the
                            # moment, since this functionality is not publicly
                            # released yet in cnt2lms
                            try:
                                if USE_CNT2LMS_SCRIPT_GENERATION:
                                    ssh_command = "ssh -tt -o 'ProxyCommand ssh [email protected] -W %h:%p' cyuser@moodle"
                                    python_command = "python -u " + CNT2LMS_PATH + "get_cyris_result.py " + CYRIS_MASTER_HOST + " " + CYRIS_MASTER_ACCOUNT + " " + CYRIS_PATH + CYRIS_RANGE_DIRECTORY + " " + range_id + " 1"
                                    command = ssh_command + " \"" + python_command + "\""
                                    print "* DEBUG: instsrv: get_cyris_result command: " + command
                                    return_value = os.system(command)
                                    exit_status = os.WEXITSTATUS(return_value)
                                    if exit_status == 0:
                                        #response_content = RESPONSE_SUCCESS
                                        pass
                                    else:
                                        #self.send_error(SERVER_ERROR, "LMS terminal preparation issue")
                                        #return
                                        print "* DEBUG: instsrv: LMS terminal preparation issue"
                            except IOError:
                                #self.send_error(SERVER_ERROR, "LMS terminal preparation I/O error)
                                #return
                                print "* DEBUG: instsrv: LMS terminal preparation I/O error"
                        else:
                            # Even though CyRIS is now destroying automatically the cyber range
                            # in case of error, as it may fail we still try to clean up here
                            self.handle_cyris_error(range_id)
                            response_content = self.build_response(Storyboard.SERVER_STATUS_ERROR,
                                                                   Storyboard.INSTANTIATION_STATUS_FILE_NOT_FOUND)
                except IOError:
                    self.handle_cyris_error(range_id)
                    self.send_error(SERVER_ERROR, Storyboard.INSTANTIATION_CYRIS_IO_ERROR)
                    return

            # Don't use CyRIS, just simulate the instantiation
            else:
                # Simulate time needed to instantiate the cyber range
                if SIMULATION_DURATION == -1:
                    sleep_time = random.randint(2,5)
                else:
                    sleep_time = SIMULATION_DURATION
                print Storyboard.SEPARATOR3
                print "* INFO: instsrv: Simulate instantiation by sleeping %d s." % (sleep_time)
                print Storyboard.SEPARATOR3
                time.sleep(sleep_time)

                # Simulate the success or failure of the instantiation
                if random.random() > 0.0:
                    # Get sample notification text
                    notification_filename = "{0}/{1}".format(DATABASE_DIR,
                                                             CYRIS_NOTIFICATION_SIMULATED)
                    if DEBUG:
                        print "* DEBUG: instsrv: Simulated notification file name=", notification_filename

                    message = None
                    with open(notification_filename, 'r') as notification_file:
                        notification_file_content = notification_file.read()
                        message = urllib.quote(notification_file_content)
                    response_content = self.build_response(Storyboard.SERVER_STATUS_SUCCESS, message)
                else:
                    response_content = self.build_response(Storyboard.SERVER_STATUS_ERROR,
                                                           Storyboard.INSTANTIATION_SIMULATED_ERROR)

        # Destroy the cyber range
        elif action == query.Parameters.DESTROY_RANGE: 

            # Check that the range id is valid
            if not range_id:
                self.send_error(REQUEST_ERROR, "Invalid range id")
                return

            print "* INFO: instsrv: Start destruction of cyber range with id %s." % (range_id)

            # Use CyRIS to really do cyber range destruction
            if USE_CYRIS:
                destruction_filename = CYRIS_PATH + CYRIS_DESTRUCTION_SCRIPT
                destruction_command = "{0} {1} {2}".format(destruction_filename, range_id, CYRIS_PATH + CYRIS_CONFIG_FILENAME)
                print "* DEBUG: instrv: destruction_command: " + destruction_command
                return_value = os.system(destruction_command)
                exit_status = os.WEXITSTATUS(return_value)
                if exit_status == 0:
                    response_content = self.build_response(Storyboard.SERVER_STATUS_SUCCESS)
                else:
                    response_content = self.build_response(Storyboard.SERVER_STATUS_ERROR,
                                                           "CyRIS destruction issue")

            # Don't use CyRIS, just simulate the destruction
            else:
                # Simulate time needed to destroy the cyber range
                if SIMULATION_DURATION == -1:
                    sleep_time = random.randint(2,5)
                else:
                    sleep_time = SIMULATION_DURATION
                print Storyboard.SEPARATOR3
                print "* INFO: instsrv: Simulate destruction by sleeping %d s." % (sleep_time)
                print Storyboard.SEPARATOR3
                time.sleep(sleep_time)

                # Simulate the success or failure of the destruction
                if random.random() > 0.0:
                    response_content = self.build_response(Storyboard.SERVER_STATUS_SUCCESS)
                else:
                    response_content = self.build_response(Storyboard.SERVER_STATUS_ERROR,
                                                           Storyboard.DESTRUCTION_SIMULATED_ERROR)

        # Catch potential unimplemented actions (if any)
        else:
            print "* WARNING: instsrv: Unknown action: %s." % (action)

        # Send response header to requester (triggers log_message())
        self.send_response(HTTP_OK_CODE)
        self.send_header("Content-type", "text/html")
        self.end_headers() 

        # Send scenario database content information to requester
        self.wfile.write(response_content)

        # Output server reply
        if DEBUG:
            print "* DEBUG: instsrv: Server response content: %s" % (response_content)
Beispiel #3
0
    def do_POST(self):

        # Get the parameters of the POST request
        params = query.Parameters(self)

        if DO_DEBUG:
            print SEPARATOR
            print "* INFO: contsrv: Request to content server: POST parameters: %s" % (
                params)

        # Get parameter values for given keys
        user_id = params.get(query.Parameters.USER)
        action = params.get(query.Parameters.ACTION)
        description_file = params.get(query.Parameters.DESCRIPTION_FILE)
        range_id = params.get(query.Parameters.RANGE_ID)
        activity_id = params.get(query.Parameters.ACTIVITY_ID)

        if DO_DEBUG:
            print SEPARATOR
            print "PARAMETERS:"
            print SEPARATOR
            print "USER: %s" % (user_id)
            print "ACTION: %s" % (action)
            print "DESCRIPTION FILE:\n%s" % (description_file)
            print "RANGE_ID: %s" % (range_id)
            print "ACTIVITY_ID: %s" % (activity_id)
            print SEPARATOR

        ## Handle user information

        # Get user information from YAML file
        # Note: Only reading data that is (potentially) modified externally =>
        #       no need for synchronization

        user_info = userinfo.UserInfo()
        if not user_info.parse_YAML_file(DATABASE_DIR + USERS_FILE):
            self.send_error(SERVER_ERROR, "User information issue")
            return
        if DO_DEBUG:
            user_info.pretty_print()

        # Check that user id is valid
        user_obj = user_info.get_user(user_id)
        if not user_obj:
            self.send_error(REQUEST_ERROR, "Invalid user id")
            return

        ## Handle action information

        # Check that action is valid
        if action not in self.VALID_ACTIONS:
            self.send_error(REQUEST_ERROR, "Invalid action")
            return

        # If we reached this point, it means processing was successful
        # => act according to each action
        if action == query.Parameters.UPLOAD_CONTENT:

            # Check that description is not empty
            if not description_file:
                self.send_error(REQUEST_ERROR, "Invalid description file")
                return

            # Save the description received as a file
            try:
                content_file_name = CONTENT_DESCRIPTION_TEMPLATE.format(
                    range_id)
                content_file = open(content_file_name, "w")
                content_file.write(description_file)
                content_file.close()
                print "* INFO: contsrv: Saved POSTed content description to file '%s'." % (
                    content_file_name)
            except IOError as error:
                print(
                    "* ERROR: contsrv: Could not write to file {}: {}".format(
                        content_file_name, error))

            print "* INFO: contsrv: Start LMS content upload."

            # Use Moodle to really do the content upload
            if USE_MOODLE:
                try:
                    # ./cylms.py --convert-content training_example.yml --config-file config_example --add-to-lms 1
                    try:
                        add_output = subprocess.check_output(
                            [
                                "python", "-u", CYLMS_PATH + "cylms.py",
                                "--convert-content", content_file_name,
                                "--config-file", CYLMS_CONFIG, "--add-to-lms",
                                range_id
                            ],
                            stderr=subprocess.STDOUT)
                        # Find the activity id
                        activity_id = None
                        for output_line in add_output.splitlines():
                            print(output_line)
                            # Extract the course id
                            activity_id_tag = "activity_id="
                            if activity_id_tag in output_line:
                                # Split line of form ...to LMS successfully => activity_id=101
                                activity_id = output_line.split(
                                    activity_id_tag)[1]
                                if DO_DEBUG:
                                    print(
                                        "* DEBUG: contsrv: Extracted activity id from command output: {}"
                                        .format(activity_id))
                                response_content = RESPONSE_SUCCESS_ID_PREFIX + activity_id + RESPONSE_SUCCESS_ID_SUFFIX

                        # Check whether the activity id was extracted
                        if not activity_id:
                            self.send_error(SERVER_ERROR, "LMS upload issue")
                            return
                    except subprocess.CalledProcessError as error:
                        print("* ERROR: contsrv: Error message: {}".format(
                            error.output))
                        self.send_error(SERVER_ERROR, "CyLMS execution issue")
                        return

                except IOError:
                    self.send_error(SERVER_ERROR, "LMS upload I/O error")
                    return

            # Don't use Moodle, just simulate the content upload
            else:
                # Simulate time needed to instantiate the cyber range
                if SIMULATION_DURATION == -1:
                    sleep_time = random.randint(SIMULATION_RAND_MIN,
                                                SIMULATION_RAND_MAX)
                else:
                    sleep_time = SIMULATION_DURATION
                print Storyboard.SEPARATOR3
                print "* INFO: contsrv: Simulate upload by sleeping %d s." % (
                    sleep_time)
                print Storyboard.SEPARATOR3
                time.sleep(sleep_time)

                # Simulate the success or failure of the upload
                random_number = random.random()
                if random_number > 0.0:
                    # In case of success, we need to set the activity id to some 'harmless' value
                    activity_id = "N/A"
                    response_content = RESPONSE_SUCCESS_ID_PREFIX + activity_id + RESPONSE_SUCCESS_ID_SUFFIX
                else:
                    response_content = RESPONSE_ERROR

        elif action == query.Parameters.REMOVE_CONTENT:

            print "* INFO: contsrv: Start LMS content removal."

            # Check that range_id is not empty
            if not range_id:
                self.send_error(REQUEST_ERROR, "Invalid range id")
                return

            # Check that activity_id is not empty
            if not activity_id:
                self.send_error(REQUEST_ERROR, "Invalid LMS activity id")
                return

            # Use Moodle to really do the content removal
            if USE_MOODLE:
                try:
                    # ./cylms.py --config-file config_example --remove-from-lms 1,ID
                    config_arg = " --config-file {}".format(CYLMS_CONFIG)
                    remove_arg = " --remove-from-lms {},{}".format(
                        range_id, activity_id)
                    command = "python -u " + CYLMS_PATH + "cylms.py" + config_arg + remove_arg
                    if DO_DEBUG: print("* DEBUG: contsrv: command: " + command)
                    return_value = os.system(command)
                    exit_status = os.WEXITSTATUS(return_value)
                    if exit_status == 0:
                        response_content = RESPONSE_SUCCESS
                    else:
                        self.send_error(SERVER_ERROR,
                                        "LMS content removal issue")
                        return
                except IOError:
                    self.send_error(SERVER_ERROR,
                                    "LMS content removal I/O error")
                    return

            # Don't use Moodle, just simulate the content removal
            else:
                # Simulate time needed to instantiate the cyber range
                if SIMULATION_DURATION == -1:
                    sleep_time = random.randint(SIMULATION_RAND_MIN,
                                                SIMULATION_RAND_MAX)
                else:
                    sleep_time = SIMULATION_DURATION
                print Storyboard.SEPARATOR3
                print "* INFO: contsrv: Simulate removal by sleeping %d s." % (
                    sleep_time)
                print Storyboard.SEPARATOR3
                time.sleep(sleep_time)

                # Simulate the success or failure of the upload
                random_number = random.random()
                if random_number > 0.0:
                    response_content = RESPONSE_SUCCESS
                else:
                    response_content = RESPONSE_ERROR

        # Catch potential unimplemented actions (if any)
        else:
            print "* WARNING: contsrv: Unknown action: %s." % (action)

        # Send response header to requester (triggers log_message())
        self.send_response(SUCCESS_CODE)
        self.send_header("Content-type", "text/html")
        self.end_headers()

        # Send scenario database content information to requester
        self.wfile.write(response_content)

        # Output server reply
        print "* INFO: contsrv: Server response content: %s" % (
            response_content)
Beispiel #4
0
    def do_POST(self):

        # Get the parameters of the POST request
        params = query.Parameters(self)

        print ""
        print Storyboard.SEPARATOR2
        if Storyboard.ENABLE_PASSWORD:
            print(
                "* INFO: trngsrv: Request POST parameters: [not shown because password use is enabled]"
            )
        else:
            print(
                "* INFO: trngsrv: Request POST parameters: {}".format(params))

        # Get the values of the parameters for given keys
        user_id = params.get(query.Parameters.USER)
        password = params.get(query.Parameters.PASSWORD)
        action = params.get(query.Parameters.ACTION)
        language = params.get(query.Parameters.LANG)
        instance_count = params.get(query.Parameters.COUNT)
        ttype = params.get(query.Parameters.TYPE)
        scenario = params.get(query.Parameters.SCENARIO)
        level = params.get(query.Parameters.LEVEL)
        range_id = params.get(query.Parameters.RANGE_ID)

        if DEBUG:
            print Storyboard.SEPARATOR1
            print "POST PARAMETERS:"
            print Storyboard.SEPARATOR1
            print "USER: %s" % (user_id)
            if password:
                print "PASSWORD: ******"
            print "ACTION: %s" % (action)
            print "LANGUAGE: %s" % (language)
            print "COUNT: %s" % (instance_count)
            print "TYPE: %s" % (ttype)
            print "SCENARIO: %s" % (scenario)
            print "LEVEL: %s" % (level)
            print "RANGE_ID: %s" % (range_id)
            print Storyboard.SEPARATOR1

        ## Verify user information

        # Get user information from YAML file
        # Note: Only reading data that is (potentially) modified externally =>
        #       no need for synchronization
        user_info = userinfo.UserInfo()
        if not user_info.parse_YAML_file(DATABASE_DIR + USERS_FILE):
            self.respond_error(Storyboard.USER_SETTINGS_LOADING_ERROR)
            return
        if DEBUG:
            user_info.pretty_print()

        # Check that user id is valid
        if not user_id:
            self.respond_error(Storyboard.USER_ID_MISSING_ERROR)
            return
        user_obj = user_info.get_user(user_id)
        if not user_obj:
            self.respond_error(Storyboard.USER_ID_INVALID_ERROR)
            return

        # Check password (if enabled)
        if Storyboard.ENABLE_PASSWORD:
            # Check whether password exists in database for current user
            if not user_obj.password:
                self.respond_error(
                    Storyboard.USER_PASSWORD_NOT_IN_DATABASE_ERROR)
                return
            # If a password was provided, verify that it matches the encrypted one from the database
            if password:
                if not Password.verify(password, user_obj.password):
                    self.respond_error(
                        Storyboard.USER_ID_PASSWORD_INVALID_ERROR)
                    return
            else:
                self.respond_error(Storyboard.USER_PASSWORD_MISSING_ERROR)
                return

        ## Verify action information

        # Check that action is valid
        if not action:
            self.respond_error(Storyboard.ACTION_MISSING_ERROR)
            return
        if action not in self.VALID_ACTIONS:
            self.respond_error(Storyboard.ACTION_INVALID_ERROR)
            return

        # Create training database content information object
        training_info = trnginfo.TrainingInfo()

        # Check that language is valid
        if not language:
            self.respond_error(Storyboard.LANGUAGE_MISSING_ERROR)
            return
        if language not in self.VALID_LANGUAGES:
            self.respond_error(Storyboard.LANGUAGE_INVALID_ERROR)
            return

        # Select the scenario information file based on the
        # requested language
        if language == query.Parameters.JA:
            training_settings_file = DATABASE_DIR + SCENARIOS_FILE_JA
        else:
            training_settings_file = DATABASE_DIR + SCENARIOS_FILE_EN

        if DEBUG:
            print "* DEBUG: trngsrv: Read training settings from '%s'..." % (
                training_settings_file)

        # Note: Only reading data that is (potentially) modified externally =>
        #       no need for synchronization
        result = training_info.parse_YAML_file(training_settings_file)

        if not result:
            self.respond_error(Storyboard.TRAINING_SETTINGS_LOADING_ERROR)
            return

        if DEBUG:
            training_info.pretty_print()

        # If we reached this point, it means processing was successful
        # => act according to each action

        ####################################################################
        # Fetch content action
        # Note: Only reading data that is (potentially) modified externally =>
        #       no need for synchronization
        if action == query.Parameters.FETCH_CONTENT:

            # Convert the training info to the external JSON
            # representation that will be provided to the client
            response_data = training_info.get_JSON_representation()

        ####################################################################
        # Create training action
        # Note: Requires synchronized access to active sessions list
        elif action == query.Parameters.CREATE_TRAINING:

            if not instance_count:
                self.respond_error(Storyboard.INSTANCE_COUNT_MISSING_ERROR)
                return

            try:
                instance_count_value = int(instance_count)
            except ValueError as error:
                self.respond_error(Storyboard.INSTANCE_COUNT_INVALID_ERROR)
                return

            if not ttype:
                self.respond_error(Storyboard.TRAINING_TYPE_MISSING_ERROR)
                return

            if not scenario:
                self.respond_error(Storyboard.SCENARIO_NAME_MISSING_ERROR)
                return

            if not level:
                self.respond_error(Storyboard.LEVEL_NAME_MISSING_ERROR)
                return

            # Synchronize access to active sessions list and related variables
            self.lock_active_sessions.acquire()
            try:
                cyber_range_id = self.generate_cyber_range_id(
                    self.pending_sessions)
                if cyber_range_id:
                    # Convert to string for internal representation
                    cyber_range_id = str(cyber_range_id)
                    self.pending_sessions.append(cyber_range_id)
                    print "* INFO: trngsrv: Allocated session with ID #%s." % (
                        cyber_range_id)
                else:
                    self.respond_error(Storyboard.SESSION_ALLOCATION_ERROR)
                    return
            finally:
                self.lock_active_sessions.release()

            ########################################
            # Handle content upload
            content_file_name = training_info.get_content_name(scenario, level)
            if content_file_name == None:
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.CONTENT_IDENTIFICATION_ERROR)
                return

            content_file_name = DATABASE_DIR + content_file_name

            if DEBUG:
                print "* DEBUG: trngsrv: Training content file: %s" % (
                    content_file_name)

            # Open the content file
            try:
                content_file = open(content_file_name, "r")
                content_file_content = content_file.read()
                content_file.close()

            except IOError as error:
                print "* ERROR: trngsrv: File error: %s." % (error)
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.CONTENT_LOADING_ERROR)
                return

            try:
                # Note: creating a dictionary as below does not
                # preserve the order of the parameters, but this has
                # no negative influence in our implementation
                query_tuples = {
                    query.Parameters.USER: user_id,
                    query.Parameters.ACTION: query.Parameters.UPLOAD_CONTENT,
                    query.Parameters.DESCRIPTION_FILE: content_file_content,
                    query.Parameters.RANGE_ID: cyber_range_id
                }

                query_params = urllib.urlencode(query_tuples)
                print "* INFO: trngsrv: Send upload request to content server %s." % (
                    CONTENT_SERVER_URL)
                if DEBUG:
                    print "* DEBUG: trngsrv: POST parameters: %s" % (
                        query_params)
                data_stream = urllib.urlopen(CONTENT_SERVER_URL, query_params)
                data = data_stream.read()
                if DEBUG:
                    print "* DEBUG: trngsrv: Content server response body: %s" % (
                        data)

                (status,
                 activity_id) = query.Response.parse_server_response(data)

                if DEBUG:
                    print(
                        "* DEBUG: trngsrv: Response status: {}".format(status))
                    print("* DEBUG: trngsrv: Response activity_id: {}".format(
                        activity_id))

                if status == Storyboard.SERVER_STATUS_SUCCESS:
                    # Store activity id in session description
                    pass
                else:
                    print "* ERROR: trngsrv: Content upload error."
                    self.removePendingSession(cyber_range_id)
                    self.respond_error(Storyboard.CONTENT_UPLOAD_ERROR)
                    return

                # Save the response data
                contsrv_response = data

            except IOError as error:
                print "* ERROR: trngsrv: URL error: %s." % (error)
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.CONTENT_SERVER_ERROR)
                return

            ########################################
            # Handle instantiation
            spec_file_name = training_info.get_specification_name(
                scenario, level)
            if spec_file_name == None:
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.TEMPLATE_IDENTIFICATION_ERROR)
                return

            spec_file_name = DATABASE_DIR + spec_file_name

            if DEBUG:
                print "* DEBUG: trngsrv: Scenario specification file: %s" % (
                    spec_file_name)

            # Open the specification file (template)
            try:
                spec_file = open(spec_file_name, "r")
                spec_file_content = spec_file.read()
                spec_file.close()

            except IOError as error:
                print "* ERROR: trngsrv: File error: %s." % (error)
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.TEMPLATE_LOADING_ERROR)
                return

            # Do instantiation
            try:
                # Replace variables in the specification file
                spec_file_content = user_obj.replace_variables(
                    spec_file_content, cyber_range_id, instance_count_value)

                # Note: creating a dictionary as below does not
                # preserve the order of the parameters, but this has
                # no negative influence in our implementation
                query_tuples = {
                    query.Parameters.USER: user_id,
                    query.Parameters.ACTION:
                    query.Parameters.INSTANTIATE_RANGE,
                    query.Parameters.DESCRIPTION_FILE: spec_file_content,
                    query.Parameters.RANGE_ID: cyber_range_id
                }

                query_params = urllib.urlencode(query_tuples)
                print "* INFO: trngsrv: Send instantiate request to instantiation server %s." % (
                    INSTANTIATION_SERVER_URL)
                if DEBUG:
                    print "* DEBUG: trngsrv: POST parameters: %s" % (
                        query_params)
                data_stream = urllib.urlopen(INSTANTIATION_SERVER_URL,
                                             query_params)
                data = data_stream.read()
                if DEBUG:
                    print "* DEBUG: trngsrv: Instantiation server response body: %s" % (
                        data)

                # Remove pending session
                self.removePendingSession(cyber_range_id)

                (status, message) = query.Response.parse_server_response(data)

                if DEBUG:
                    print "* DEBUG: trngsrv: Response status:", status
                    print "* DEBUG: trngsrv: Response message:", message

                if status == Storyboard.SERVER_STATUS_SUCCESS:
                    session_name = "Training Session #%s" % (cyber_range_id)
                    crt_time = time.asctime()
                    print "* INFO: trngsrv: Instantiation successful => save training session: %s (time: %s)." % (
                        session_name, crt_time)

                    # Synchronize access to active sessions list
                    self.lock_active_sessions.acquire()
                    try:
                        # Read session info
                        session_info = sessinfo.SessionInfo()
                        session_info.parse_YAML_file(ACTIVE_SESSIONS_FILE)

                        # Add new session and save to file
                        # Scenarios and levels should be given as arrays,
                        # so we convert values to arrays when passing arguments
                        session_info.add_session(session_name, cyber_range_id,
                                                 user_id, crt_time, ttype,
                                                 [scenario], [level], language,
                                                 instance_count, activity_id)
                        session_info.write_YAML_file(ACTIVE_SESSIONS_FILE)
                    finally:
                        self.lock_active_sessions.release()
                else:
                    print "* ERROR: trngsrv: Range instantiation error."
                    self.respond_error(Storyboard.INSTANTIATION_ERROR)
                    return

                # Save the response data
                instsrv_response = data

                if DEBUG:
                    print "* DEBUG: trngsrv: contsrv response: %s" % (
                        contsrv_response)
                    print "* DEBUG: trngsrv: instsrv response: %s" % (
                        instsrv_response)

                # Prepare the response as a message
                # TODO: Should create a function to handle this
                if message:
                    response_data = '[{{"{0}": "{1}"}}]'.format(
                        Storyboard.SERVER_MESSAGE_KEY, message)
                else:
                    response_data = None

            except IOError as error:
                print "* ERROR: trngsrv: URL error: %s." % (error)
                self.removePendingSession(cyber_range_id)
                self.respond_error(Storyboard.INSTANTIATION_SERVER_ERROR)
                return

        ####################################################################
        # Retrieve saved training configurations action
        # Note: Requires synchronized access to saved configurations list
        elif action == query.Parameters.GET_CONFIGURATIONS:

            self.lock_saved_configurations.acquire()
            try:
                # Read session info
                session_info = sessinfo.SessionInfo()
                session_info.parse_YAML_file(SAVED_CONFIGURATIONS_FILE)

                # TODO: Catch error in operation above
            finally:
                self.lock_saved_configurations.release()

            # Convert the training session info to the external JSON
            # representation that will be provided to the client
            response_data = session_info.get_JSON_representation(user_id)

        ####################################################################
        # Retrieve active training sessions action
        # Note: Requires synchronized access to active sessions list
        elif action == query.Parameters.GET_SESSIONS:

            self.lock_active_sessions.acquire()
            try:
                # Read session info
                session_info = sessinfo.SessionInfo()
                session_info.parse_YAML_file(ACTIVE_SESSIONS_FILE)

                # TODO: Catch error in operation above
            finally:
                self.lock_active_sessions.release()

            # Convert the training session info to the external JSON
            # representation that will be provided to the client
            response_data = session_info.get_JSON_representation(user_id)

        ####################################################################
        # End training action
        # Note: Requires synchronized access to active sessions list
        elif action == query.Parameters.END_TRAINING:

            if not range_id:
                print "* ERROR: trngsrv: %s." % (
                    Storyboard.SESSION_ID_MISSING_ERROR)
                self.respond_error(Storyboard.SESSION_ID_MISSING_ERROR)
                return

            activity_id = None
            self.lock_active_sessions.acquire()
            try:
                activity_id = self.check_range_id_exists(range_id, user_id)
                if not activity_id:
                    print "* ERROR: trngsrv: Session with ID " + range_id + " doesn't exist for user " + user_id
                    error_msg = Storyboard.SESSION_ID_INVALID_ERROR + ": " + range_id
                    self.respond_error(error_msg)
                    return
            finally:
                self.lock_active_sessions.release()

            ########################################
            # Handle content removal
            try:
                # Note: Creating a dictionary as below does not
                # preserve the order of the parameters, but this has
                # no negative influence in our implementation
                query_tuples = {
                    query.Parameters.USER: user_id,
                    query.Parameters.ACTION: query.Parameters.REMOVE_CONTENT,
                    query.Parameters.RANGE_ID: range_id,
                    query.Parameters.ACTIVITY_ID: activity_id
                }

                query_params = urllib.urlencode(query_tuples)
                print "* INFO: trngsrv: Send removal request to content server %s." % (
                    CONTENT_SERVER_URL)
                print "* INFO: trngsrv: POST parameters: %s" % (query_params)
                data_stream = urllib.urlopen(CONTENT_SERVER_URL, query_params)
                data = data_stream.read()
                if DEBUG:
                    print "* DEBUG: trngsrv: Content server response body: %s" % (
                        data)

                # We don't parse the response since the content server does not provide
                # a uniformly formatted one; instead we only check for SUCCESS key
                if Storyboard.SERVER_STATUS_SUCCESS in data:
                    # Nothing to do on success as there is no additional info provided
                    pass
                else:
                    print "* ERROR: trngsrv: Content removal error."
                    self.respond_error(Storyboard.CONTENT_REMOVAL_ERROR)
                    return

                # Save the response data
                contsrv_response = data

            except IOError as error:
                print "* ERROR: trngsrv: URL error: %s." % (error)
                self.respond_error(Storyboard.CONTENT_SERVER_ERROR)
                return

            ########################################
            # Handle range destruction
            try:
                # Note: creating a dictionary as below does not
                # preserve the order of the parameters, but this has
                # no negative influence in our implementation
                query_tuples = {
                    query.Parameters.USER: user_id,
                    query.Parameters.ACTION: query.Parameters.DESTROY_RANGE,
                    query.Parameters.RANGE_ID: range_id
                }

                query_params = urllib.urlencode(query_tuples)
                print "* INFO: trngsrv: Send destroy request to instantiation server %s." % (
                    INSTANTIATION_SERVER_URL)
                print "* INFO: trngsrv: POST parameters: %s" % (query_params)
                data_stream = urllib.urlopen(INSTANTIATION_SERVER_URL,
                                             query_params)
                data = data_stream.read()
                if DEBUG:
                    print "* DEBUG: trngsrv: Instantiation server response body: %s" % (
                        data)

                (status, message) = query.Response.parse_server_response(data)

                if status == Storyboard.SERVER_STATUS_SUCCESS:

                    self.lock_active_sessions.acquire()
                    try:
                        # Read session info
                        session_info = sessinfo.SessionInfo()
                        session_info.parse_YAML_file(ACTIVE_SESSIONS_FILE)

                        # Remove session and save to file
                        if not session_info.remove_session(range_id, user_id):
                            print "* ERROR: Cannot remove training session %s." % (
                                range_id)
                            self.respond_error(
                                Storyboard.SESSION_INFO_CONSISTENCY_ERROR)
                            return
                        else:
                            session_info.write_YAML_file(ACTIVE_SESSIONS_FILE)
                    finally:
                        self.lock_active_sessions.release()

                else:
                    print "* ERROR: trngsrv: Range destruction error: %s." % (
                        message)
                    self.respond_error(Storyboard.DESTRUCTION_ERROR)
                    return

                instsrv_response = data

                if DEBUG:
                    print "* DEBUG: trngsrv: contsrv response: %s" % (
                        contsrv_response)
                    print "* DEBUG: trngsrv: instsrv response: %s" % (
                        instsrv_response)

                # Prepare the response: no data needs to be returned,
                # hence we set the content to None
                response_data = None

            except IOError as error:
                print "* ERROR: trngsrv: URL error: %s." % (error)
                self.respond_error(Storyboard.INSTANTIATION_SERVER_ERROR)
                return

        ####################################################################
        # Catch unknown actions
        else:
            print "* WARNING: trngsrv: Unknown action: %s." % (action)

        # Emulate communication delay
        if EMULATE_DELAY:
            sleep_time = random.randint(2, 5)
            print "* INFO: trngsrv: Simulate communication by sleeping %d s." % (
                sleep_time)
            time.sleep(sleep_time)

        # Respond to requester with SUCCESS
        self.respond_success(response_data)