Beispiel #1
0
    def get_dissection(
        self,
        protocol: optional(is_protocol) = None,
    ) -> list_of(OrderedDict):
        """
        Function to get dissection of a capture as a list of frames represented as strings

        :param protocol: Protocol class for filtering purposes
        :type protocol: type
        :raises TypeError: If protocol is not a protocol class
        :raises ReaderError: If the reader couldn't process the file

        :return: A list of Frame represented as API's dict form
        :rtype: [OrderedDict]

        """
        # log.debug('Starting dissection.')
        # Check the protocol is one entered
        if all((protocol, not is_protocol(protocol))):
            raise TypeError(protocol.__name__ + ' is not a protocol class')

        fs = self.frames

        # For speeding up the process
        with Data.disable_name_resolution():

            # Filter the frames for the selected protocol
            if protocol:
                fs, _ = Frame.filter_frames(fs, protocol)

        if fs is None:
            raise Error('Empty capture cannot be dissected')

        # Then return the list of dictionary frame representation
        return [frame.dict() for frame in fs]
Beispiel #2
0
def get_token(tok: optional(str) = None):
    """
    Function to get a token, if there's a valid one entered just return it
    otherwise generate a new one

    :param tok: The token if there's already one
    :type tok: str

    :return: A token, the same if there's already one, a new one otherwise
    :rtype: str
    """

    # If the token is already a correct one
    try:
        if all((
                tok,
                type(tok) == str,
                len(tok) == 28,
                base64.urlsafe_b64decode(tok +
                                         '=')  # Add '=' only for checking
        )):
            return tok
    except:  # If the decode throw an error => Wrong base64
        pass

    # Generate a token because there is none
    token = hashlib.sha1(
        str.encode(
            ("%s%s%04d%s" % (HASH_PREFIX, time.time(), job_id, HASH_SUFFIX)),
            encoding='utf-8'))
    token = base64.urlsafe_b64encode(token.digest()).decode()

    # Remove the '=' at the end of it, it is used by base64 for padding
    return token.replace('=', '')
Beispiel #3
0
    def dissect(
            self,
            protocol: optional(is_protocol) = None) -> list_of(OrderedDict):
        """
        The dissect function to dissect a pcap file into list of frames

        :param protocol: Protocol class for filtering purposes
        :type protocol: type
        :raises TypeError: If protocol is not a protocol class
        :raises ReaderError: If the reader couldn't process the file

        :return: A list of Frame represented as API's dict form
        :rtype: [OrderedDict]
        """
        # log.debug('Starting dissection.')
        # Check the protocol is one entered
        if all((protocol, not is_protocol(protocol))):
            raise TypeError(protocol.__name__ + ' is not a protocol class')

        # For speeding up the process
        with Data.disable_name_resolution():

            # Get the list of frames
            frames = self.__capture.frames

            # Filter the frames for the selected protocol
            if protocol is not None:
                frames, _ = Frame.filter_frames(frames, protocol)

        # Then return the list of dictionary frame representation
        return [frame.dict() for frame in frames]
Beispiel #4
0
    def summary(self,
                protocol: optional(is_protocol) = None) -> list_of((int, str)):
        """
        The summaries function to get the summary of frames

        :param protocol: Protocol class for filtering purposes
        :type protocol: type

        :Example:

        from ttproto.core.lib.all import Ieee802154

        for s in dissector.summary(protocol = Ieee802154):

            print(s)

        :raises TypeError: If protocol is not a protocol class
        :raises ReaderError: If the reader couldn't process the file

        :return: Basic informations about frames like the underlying example
        :rtype: [(int, str)]

        :Example:

            [
                (13, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38515] GET /test'),

                (14, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38515] 2.05 Content'),

                (21, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38516] PUT /test'),

                (22, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38516] 2.04 Changed')]
            ]

        .. note:: With the protocol option we can filter the response
        """

        if all((protocol, not is_protocol(protocol))):
            raise TypeError(protocol.__name__ + ' is not a protocol class')

        fs = self.frames

        # For speeding up the process
        with Data.disable_name_resolution():

            # Filter the frames for the selected protocol
            if protocol:
                fs, _ = Frame.filter_frames(fs, protocol)

        if fs is None:
            raise Error('Empty capture cannot be dissected')

        # Return list of frames summary
        return [frame.summary() for frame in fs]
Beispiel #5
0
    def summary(self,
                protocol: optional(is_protocol) = None) -> list_of((int, str)):
        """
        The summaries function to get the summary of frames

        :param protocol: Protocol class for filtering purposes
        :type protocol: type

        :Example:

        from ttproto.core.lib.all import Ieee802154

        for s in dissector.summary(protocol = Ieee802154):

            print(s)

        :raises TypeError: If protocol is not a protocol class
        :raises ReaderError: If the reader couldn't process the file

        :return: Basic informations about frames like the underlying example
        :rtype: [(int, str)]

        :Example:

            [
                (13, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38515] GET /test'),

                (14, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38515] 2.05 Content'),

                (21, '[127.0.0.1 -> 127.0.0.1] CoAP [CON 38516] PUT /test'),

                (22, '[127.0.0.1 -> 127.0.0.1] CoAP [ACK 38516] 2.04 Changed')]
            ]

        .. todo:: Filter uninteresting frames ? (to decrease the load)
        .. note:: With the protocol option we can filter
        """

        # Check the protocol
        if all((protocol, not is_protocol(protocol))):
            raise TypeError(protocol.__name__ + ' is not a protocol class')

        # Disable the name resolution in order to improve performances
        with Data.disable_name_resolution():

            # Get the frames from the capture
            frames = self.__capture.frames

            # Filter the frames for the selected protocol
            if protocol is not None:
                frames, _ = Frame.filter_frames(frames, protocol)

        # Then give the summary of every frames
        return [frame.summary() for frame in frames]
Beispiel #6
0
    def is_compiled_regex(arg):
        """
        Check if a parameter is a valid regex compiled object.
        This function is used for the typechecker decorator.

        :return: True if a valid compiled regex, False if not
        :rtype: bool
        """
        return all(
            (arg is not None, isinstance(arg,
                                         type(re.compile('dummy_pattern')))))
Beispiel #7
0
    def get_dissection_simple_format(
        self,
        protocol: optional(is_protocol) = None,
    ) -> list_of(str):
        """
        Function to get dissection of a capture as a list of frames represented as strings

        :param protocol: Protocol class for filtering purposes
        :type protocol: type
        :raises TypeError: If protocol is not a protocol class
        :raises ReaderError: If the reader couldn't process the file

        :return: A list of Frame represented as plain non-structured text
        :rtype: [str]

        """
        # log.debug('Starting dissection.')
        # Check the protocol is one entered
        if all((protocol, not is_protocol(protocol))):
            raise TypeError(protocol.__name__ + ' is not a protocol class')

        fs = self.frames

        # For speeding up the process
        with Data.disable_name_resolution():

            # Filter the frames for the selected protocol
            if protocol:
                fs, _ = Frame.filter_frames(fs, protocol)

        if fs is None:
            raise Error('Empty capture cannot be dissected')

        # fixme modify Message class from ttproto.data structure so I can get text display wihtout this patch
        class WritableObj(object):
            def __init__(self, text=''):
                self.val = text

            def __str__(self):
                return self.val

            def write(self, text):
                self.val += text

        frame_dissection_list = []

        for f in fs:
            text_output = WritableObj()
            f.message.display(output=text_output)
            frame_dissection_list.append(str(text_output))

        # Then return the list of frames,each as a simple text dissection
        return frame_dissection_list
Beispiel #8
0
def is_layer_value(arg: anything) -> bool:
    """
    Check if a parameter is a valid layer value.
    This function is used for the typechecker decorator.

    :param arg: The object to check
    :type arg: anything

    :return: True if a valid layer value, False if not
    :rtype: bool
    """
    return all((arg is not None, isinstance(arg, Value)))
Beispiel #9
0
def is_protocol(arg: anything) -> bool:
    """
    Check if a parameter is a valid protocol.
    This function is used for the typechecker decorator.

    :param arg: The object to check
    :type arg: anything

    :return: True if a valid protocol, False if not
    :rtype: bool
    """
    return all((arg is not None, type(arg) == type, arg
                in get_dissectable_protocols()))
Beispiel #10
0
def is_verdict(arg) -> bool:
    """
    Check if a parameter is a valid verdict.
    This function is used for the typechecker decorator.

    :return: True if a valid verdict, False if not
    :rtype: bool
    """
    return all((
        arg is not None,
        type(arg) == str,
        arg in Verdict.values()
    ))
Beispiel #11
0
def correct_get_param(par: list, is_number: optional(bool) = False) -> bool:
    """
    Function to check if a get parameter is correct

    :param par: The get parameter to check (care it's a list)
    :param is_number: If we expect it to be a number
    :type par: list
    :type is_number: bool

    :return: True if the get parameter is correct, False if not
    :rtype: bool
    """
    return all(
        (len(par) == 1, type(par[0]) == str, par[0] != '',
         not (is_number and (not par[0].isdigit() or int(par[0]) <= 0))))
Beispiel #12
0
    def next_skip_ack(self, optional: bool = False):
        """
        Call self.next() but skips possibly interleaved ACKs

        :param optional: If we have to get a next frame or not
        :type optional: bool
        """

        # Goes to next frame
        self.next(optional)

        # While there is one and that it's an ack, pass it
        while all((self._frame is not None, self._frame[CoAP]
                   in CoAP(type='ack', code=0))):
            self.next(optional)
Beispiel #13
0
def is_tc_subclass(arg) -> bool:
    """
    Check if a parameter is a valid traceback object.
    This function is used for the typechecker decorator.

    :return: True if a valid traceback, False if not
    :rtype: bool

    .. note:: Didn't find a better way to check this, isinstance or type
              seems to not work
    """
    return all((
        arg is not None,
        type(arg) == type,
        inspect.isclass(arg) and issubclass(arg, TestCase)
    ))
Beispiel #14
0
def is_traceback(arg) -> bool:
    """
    Check if a parameter is a valid traceback object.
    This function is used for the typechecker decorator.

    :return: True if a valid traceback, False if not
    :rtype: bool

    .. note:: Didn't find a better way to check this, isinstance or type
              seems to not work
    """
    return all((
        arg is not None,
        hasattr(arg, '__class__'),
        hasattr(arg.__class__, '__name__'),
        isinstance(arg.__class__.__name__, str),
        arg.__class__.__name__ == 'traceback'
    ))
Beispiel #15
0
    def do_POST(self):

        # The job counter
        global job_id
        job_id += 1

        # ########################## ttproto API ########################### #

        # POST handler for the analyzer_testCaseAnalyze uri
        # It will allow users to analyze a pcap file corresponding to a TC
        #
        # \param pcap_file => The pcap file that we want to analyze
        # \param token => The token previously provided
        # \param testcase_id => The id of the corresponding test case
        # The pcap_file or the token is required, having both is also forbidden
        #
        if self.path == '/api/v1/analyzer_testCaseAnalyze':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Get the content type
            try:
                content_type = cgi.parse_header(self.headers['Content-Type'])
            except TypeError:
                self.api_error(
                    "Non empty POST datas and format of 'multipart/form-data' expected"
                )
                return

            # Get post values
            form = cgi.FieldStorage(fp=self.rfile,
                                    headers=self.headers,
                                    keep_blank_values=True,
                                    environ={
                                        'REQUEST_METHOD': 'POST',
                                        'CONTENT_TYPE': content_type[0]
                                    })

            # Check that we have the two values
            if any((
                    len(form) != 2,
                    'testcase_id' not in form,
                    all((  # None of the two required => Error
                        'pcap_file' not in form, 'token' not in form)),
                    all((  # Both of them => Error
                        'pcap_file' in form, 'token' in form)))):
                self.api_error(
                    'Expected POST=([pcap_file={file}|token={text}], testcase_id={text})'
                )
                return

            # Get the test case and its informations
            testcase_id = form.getvalue('testcase_id')
            if not type(testcase_id) == str:
                self.api_error(
                    'The value of the testcase_id should be a string from text input'
                )
                return

            # Try to get the test case
            try:
                test_case = get_test_cases(testcase_id)
            except FileNotFoundError:
                self.api_error('Test case %s not found' % testcase_id)
                return

            # Get the token
            token = form.getvalue('token')

            # Get analysis results from the token
            if token:

                # Just get the path
                pcap_path = os.path.join(TMPDIR, token + '.dump')

            # Get analysis results from the pcap file
            else:

                # Check headers
                if any((len(content_type) == 0, content_type[0] is None,
                        content_type[0] != 'multipart/form-data')):
                    self.api_error(
                        "POST format of 'multipart/form-data' expected, no file input 'pcap_file' found"
                    )
                    return

                # Get the same token or generate a new one
                token = get_token(token)

                # Get and check the pcap file entered
                pcap_file = form.getvalue('pcap_file')

                # Path to save the file
                pcap_path = os.path.join(TMPDIR, token + '.dump')

                # Write the pcap file to a temporary destination
                try:
                    with open(pcap_path, 'wb') as f:
                        f.write(pcap_file)
                except:
                    self.api_error("Couldn't write the temporary file %s" %
                                   pcap_path)
                    return

                # Get the dissection from analysis tool
                try:
                    dissection = Dissector(pcap_path).dissect()
                except pure_pcapy.PcapError:
                    self.api_error(
                        "Expected 'pcap_file' to be a non empty pcap file")
                except:
                    self.api_error("Couldn't read the temporary file %s" %
                                   pcap_path)
                    return

                # Save the json dissection result into a file
                json_save = os.path.join(TMPDIR, token + '.json')
                try:
                    with open(json_save, 'w') as f:
                        json.dump(dissection, f)
                except:
                    self.api_error("Couldn't write the json file")
                    return

            # Get the result of the analysis
            analysis_results = Analyzer('tat_coap').analyse(
                pcap_path, testcase_id)

            # self.log_message("###############################################")
            # self.log_message("Verdict description is : %s", analysis_results[0][3])
            # self.log_message("###############################################")

            # print(analysis_results)

            # Error for some test cases that the analysis doesn't manage to get
            try:
                assert type(analysis_results) == tuple
                assert len(analysis_results) == 5
                assert type(analysis_results[0]) == str
                assert type(analysis_results[1]) == str
                assert type(analysis_results[2]) == list
                assert type(analysis_results[3]) == str
                assert type(analysis_results[5]) == list
                for exception_tuple in analysis_results[5]:
                    assert type(exception_tuple) == tuple
                    assert len(exception_tuple) == 3
                    assert isinstance(exception_tuple[0], type)
                    assert isinstance(exception_tuple[1], Exception)
                    assert isinstance(exception_tuple[2], object)
                assert analysis_results[0] == test_case['tc_basic']['id']
            except AssertionError:
                self.api_error(
                    'Problem with the analyse of TC %s, wrong result received'
                    % testcase_id)
                return

            # Only take the first
            verdict = OrderedDict()
            verdict['_type'] = 'verdict'
            verdict['verdict'] = analysis_results[1]
            verdict['description'] = analysis_results[3]
            verdict['review_frames'] = analysis_results[2]

            token_res = OrderedDict()
            token_res['_type'] = 'token'
            token_res['value'] = token

            # Prepare the result to return
            json_result = OrderedDict()
            json_result['_type'] = 'response'
            json_result['ok'] = True
            json_result['content'] = [
                token_res, test_case['tc_basic'], verdict
            ]

            # Here we will analyze the pcap file and get the results as json
            print(json.dumps(json_result))
            return

        # POST handler for the analyzer_allMightyAnalyze uri
        # It will allow users to analyze a pcap file without giving
        # a corresponding test case
        #
        # \param pcap_file => The pcap file that we want to analyze
        # \param token => The token previously provided
        # The pcap_file or the token is required, having both is also forbidden
        #
        elif self.path == '/api/v1/analyzer_allMightyAnalyze':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Not implemented for the moment
            self.api_error(
                "This method is not implemented yet, please come back later")
            return

        # POST handler for the dissector_dissectFile uri
        # It will allow users to analyze a pcap file corresponding to a TC
        #
        # \param pcap_file => The pcap file that we want to dissect
        # \param protocol_selection => The protocol name
        #
        elif self.path == '/api/v1/dissector_dissectFile':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Get the content type
            try:
                content_type = cgi.parse_header(self.headers['Content-Type'])
            except TypeError:
                self.api_error(
                    "Non empty POST datas and format of 'multipart/form-data' expected"
                )
                return

            # Check headers
            if any((len(content_type) == 0, content_type[0] is None,
                    content_type[0] != 'multipart/form-data')):
                self.api_error(
                    "POST format of 'multipart/form-data' expected, no file input 'pcap_file' found"
                )
                return

            # Get post values
            form = cgi.FieldStorage(fp=self.rfile,
                                    headers=self.headers,
                                    environ={
                                        'REQUEST_METHOD': 'POST',
                                        'CONTENT_TYPE': content_type[0]
                                    })

            # Check the parameters passed
            if any((len(form) != 2, 'pcap_file'
                    not in form, 'protocol_selection' not in form)):
                self.api_error(
                    'Expected POST=(pcap_file={file}, protocol_selection={text})'
                )
                return

            # Check the protocol_selection value
            protocol_selection = form.getvalue('protocol_selection')
            if not type(protocol_selection) == str:
                self.api_error(
                    'Expected protocol_selection post value to be a text (eq string)'
                )
                return

            # In function of the protocol asked
            prot = get_protocol(protocol_selection)
            if prot is None:
                self.api_error('Unknown protocol %s' % protocol_selection)
                return

            # Generate a new token
            token = get_token()

            # Get the pcap file
            pcap_file = form.getvalue('pcap_file')

            # Path to save the file
            pcap_path = os.path.join(TMPDIR, token + '.dump')

            # Write the pcap file to a temporary destination
            try:
                with open(pcap_path, 'wb') as f:
                    f.write(pcap_file)
            except:
                self.api_error("Couldn't write the temporary file")
                return

            # Prepare the result to return
            json_result = OrderedDict()
            json_result['_type'] = 'response'
            json_result['ok'] = True

            token_res = OrderedDict()
            token_res['_type'] = 'token'
            token_res['value'] = token

            # Get the dissection from dissector tool
            try:
                dissection = Dissector(pcap_path).dissect(eval(prot['name']))
            except TypeError as e:
                self.api_error('Dissector error: ' + str(e))
                return
            except pure_pcapy.PcapError:
                self.api_error(
                    "Expected 'pcap_file' to be a non empty pcap file")
                return
            except:
                self.api_error(
                    "Couldn't read the temporary file %s and protocol is %s" %
                    (pcap_path, prot['name']))
                return

            # Save the json dissection result into a file
            json_save = os.path.join(TMPDIR, token + '.json')
            try:
                with open(json_save, 'w') as f:
                    json.dump(dissection, f)
            except:
                self.api_error("Couldn't write the json file")
                return

            # Add the token to the results
            dissection.insert(0, token_res)

            # The json result to return
            json_result['content'] = dissection

            # Here we will analyze the pcap file and get the results as json
            print(json.dumps(json_result))
            return

        # ######################## End of API part ######################### #

        # DEPRECATED
        # elif (self.path == "/submit"):

        #     if os.fork():
        #         # close the socket right now(because the
        #         # requesthandler may do a shutdown(), which triggers a
        #         # SIGCHLD in the child process)
        #         self.connection.close()
        #         return

        #     parser = BytesFeedParser()
        #     ct = self.headers.get("Content-Type")
        #     if not ct.startswith("multipart/form-data;"):
        #         self.send_error(400)
        #         return

        #     parser.feed(bytes("Content-Type: %s\r\n\r\n" % ct, "ascii"))
        #     parser.feed(self.rfile.read(int(self.headers['Content-Length'])))
        #     msg = parser.close()

        #     # agree checkbox is selected
        #     for part in msg.get_payload():
        #         if isinstance(part, email.message.Message):
        #             disposition = part.get("content-disposition")
        #             if disposition and 'name="agree"' in disposition:
        #                 agree = True
        #                 break
        #     else:
        #         agree = False

        #     # urifilter checkbox is selected
        #     for part in msg.get_payload():
        #         if isinstance(part, email.message.Message):
        #             disposition = part.get("content-disposition")
        #             if disposition and 'name="urifilter"' in disposition:
        #                 urifilter = True
        #                 break
        #     else:
        #         urifilter = False

        #     # content of the regex box
        #     for part in msg.get_payload():
        #         if isinstance(part, email.message.Message):
        #             disposition = part.get("content-disposition")
        #             if disposition and 'name="regex"' in disposition:
        #                 regex = part.get_payload()
        #                 if not regex:
        #                     regex = None
        #                 break
        #     else:
        #         regex = None

        #     # profile radio buttons
        #     for part in msg.get_payload():
        #         if isinstance(part, email.message.Message):
        #             disposition = part.get("content-disposition")
        #             if disposition and 'name="profile"' in disposition:
        #                 profile = part.get_payload()
        #                 break
        #     else:
        #         profile = "client"

        #     # receive the pcap file
        #     for part in msg.get_payload():
        #         if isinstance(part, email.message.Message):
        #             disposition = part.get("content-disposition")
        #             if disposition and 'name="file"' in disposition:
        #                 mo = re.search('filename="([^"]*)"', disposition)

        #                 orig_filename = mo.group(1) if mo else None

        #                 timestamp = time.strftime("%y%m%d_%H%M%S")

        #                 pcap_file = os.path.join(
        #                         (DATADIR if agree else TMPDIR),
        #                         "%s_%04d.dump" % (timestamp, job_id)
        #                 )
        #                 self.log_message("uploading %s(urifilter=%r, regex=%r)", pcap_file, urifilter, regex)
        #                 with open(pcap_file, "wb") as fd:
        #                     # FIXME: using hidden API(._payload) because it seems that there is something broken with the encoding when getting the payload using .get_payload()
        #                     fd.write(part._payload.encode("ascii", errors="surrogateescape"))

        #                 break
        #     else:
        #         self.send_error(400)
        #         return

        #     self.send_response(200)
        #     self.send_header("Content-Type", "text/html;charset=utf-8")
        #     self.end_headers()

        #     out = UTF8Wrapper(self.wfile)

        #     self.wfile.flush()

        #     os.dup2(self.wfile.fileno(), sys.stdout.fileno())

        #     try:
        #         exceptions = []
        #         analysis.analyse_file_html(pcap_file, orig_filename, urifilter, exceptions, regex, profile)
        #         for tc in exceptions:
        #             self.log_message("exception in %s", type(tc).__name__, append=tc.exception)
        #     except pure_pcapy.PcapError:
        #         print("Bad file format!")

        #     shutdown()

        # If we didn't manage to bind the request
        else:
            self.send_error(404)
            return
Beispiel #16
0
    def run_test_case(self) -> (
            str,
            list_of(int),
            str,
            list_of((str, str)),
            list_of((type, Exception, is_traceback))
    ):
        """
        Run the test case

        :return: A tuple with the informations about the test results which are
                 - The verdict as a string
                 - The list of the result important frames
                 - A string with the log of the test case
                 - A list of all the partial verdicts and their messages
                 - A list of typles representing the exceptions that occurred
        :rtype: (str, [int], str,[(str,str)], [(type, Exception, traceback)])
        """
        # Next line is before for 6lowpan TC, where nodes_identification_templates
        # is not generic.
        TestCase.get_nodes_identification_templates = self.get_nodes_identification_templates

        # Pre-process / filter conversations corresponding to the TC

        self._conversations, self._ignored = self.preprocess(
            capture=self._capture,
            expected_frames_pattern=self.get_stimulis()
        )

        # print("----conversations----")
        # print(self._conversations)
        # print("----ignored----")
        # print(self._ignored)

        if self._conversations == [[]] or self._conversations == []:
            self.set_verdict(
                'inconclusive',
                'Capture doesnt match expected pattern: \n\tgot %s, \n\texpected %s' %
                (str(self._capture.frames), str(self.get_stimulis()))
            )

        else:
            # Run the test case for every conversations
            for conv in self._conversations:
                if logger.getEffectiveLevel() == logging.DEBUG:
                    for frame in conv: logger.debug(frame)
                try:
                    # Get an iterator on the current conversation frames
                    # and its list of nodes
                    self._iter = iter(conv)
                    self._nodes = conv.nodes
                    self.next()

                    # Run the test case
                    self.run()

                except self.Stop:
                    # Ignore this testcase result if the first frame gives an
                    # inconclusive verdict
                    if all((
                                self._verdict.get_value() == 'inconclusive',
                                self._frame == conv[0]
                    )):
                        self.set_verdict('none', 'no match')

                except Exception as e:
                    # Get the execution information, it's a tuple with
                    #     - The type of the exception being handled
                    #     - The exception instance
                    #     - The traceback object
                    _exception_type, _exception_value, _exception_traceback = sys.exc_info()

                    logger.error(e)
                    traceback.print_exc(file=sys.stdout)

                    # Add those exception information to the list
                    self._exceptions.append((
                        _exception_type,
                        _exception_value,
                        _exception_traceback
                    ))

                    # Put the verdict and log the exception
                    self.set_verdict('error', 'unhandled exception')
                    self.log(_exception_value)

        # Return the results
        return (
            self._verdict.get_value(),
            self._failed_frames,
            self._text,
            self._verdict.get_traceback(),
            self._exceptions,
        )
Beispiel #17
0
    def do_POST(self):

        # The job counter
        global job_id
        job_id += 1

        # ########################## ttproto API ########################### #

        # POST handler for the analyzer_testCaseAnalyze uri
        # It will allow users to analyze a pcap file corresponding to a TC
        #
        # \param pcap_file => The pcap file that we want to analyze
        # \param token => The token previously provided
        # \param testcase_id => The id of the corresponding test case
        # The pcap_file or the token is required, having both is also forbidden
        #
        if self.path == '/api/v1/analyzer_testCaseAnalyze':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Get the content type
            try:
                content_type = cgi.parse_header(self.headers['Content-Type'])
            except TypeError:
                self.api_error(
                    "Non empty POST datas and format of 'multipart/form-data' expected"
                )
                return

            # Get post values
            form = cgi.FieldStorage(
                fp=self.rfile,
                headers=self.headers,
                keep_blank_values=True,
                environ={
                    'REQUEST_METHOD': 'POST',
                    'CONTENT_TYPE': content_type[0]
                })

            # Check that we have the two values
            if any((
                len(form) != 2,
                'testcase_id' not in form,
                all((  # None of the two required => Error
                    'pcap_file' not in form,
                    'token' not in form
                )),
                all((  # Both of them => Error
                    'pcap_file' in form,
                    'token' in form
                ))
            )):
                self.api_error(
                    'Expected POST=([pcap_file={file}|token={text}], testcase_id={text})'
                )
                return

            # Get the test case and its informations
            testcase_id = form.getvalue('testcase_id')
            if not type(testcase_id) == str:
                self.api_error('The value of the testcase_id should be a string from text input')
                return

            # Try to get the test case
            try:
                test_case = get_test_cases(testcase_id)
            except FileNotFoundError:
                self.api_error('Test case %s not found' % testcase_id)
                return

            # Get the token
            token = form.getvalue('token')

            # Get analysis results from the token
            if token:

                # Just get the path
                pcap_path = os.path.join(
                    TMPDIR,
                    token + '.dump'
                )

            # Get analysis results from the pcap file
            else:

                # Check headers
                if any((
                    len(content_type) == 0,
                    content_type[0] is None,
                    content_type[0] != 'multipart/form-data'
                )):
                    self.api_error(
                        "POST format of 'multipart/form-data' expected, no file input 'pcap_file' found"
                    )
                    return

                # Get the same token or generate a new one
                token = get_token(token)

                # Get and check the pcap file entered
                pcap_file = form.getvalue('pcap_file')

                # Path to save the file
                pcap_path = os.path.join(
                    TMPDIR,
                    token + '.dump'
                )

                # Write the pcap file to a temporary destination
                try:
                    with open(pcap_path, 'wb') as f:
                        f.write(pcap_file)
                except:
                    self.api_error(
                        "Couldn't write the temporary file %s"
                        %
                        pcap_path
                    )
                    return

                # Get the dissection from analysis tool
                try:
                    dissection = Dissector(pcap_path).dissect()
                except pure_pcapy.PcapError:
                    self.api_error(
                        "Expected 'pcap_file' to be a non empty pcap file"
                    )
                except:
                    self.api_error(
                        "Couldn't read the temporary file %s"
                        %
                        pcap_path
                    )
                    return

                # Save the json dissection result into a file
                json_save = os.path.join(
                    TMPDIR,
                    token + '.json'
                )
                try:
                    with open(json_save, 'w') as f:
                        json.dump(dissection, f)
                except:
                    self.api_error("Couldn't write the json file")
                    return

            # Get the result of the analysis
            analysis_results = Analyzer('tat_coap').analyse(
                                pcap_path,
                                testcase_id
                            )

            self.log_message("Analysis result: " + str(analysis_results))

            # Error for some test cases that the analysis doesn't manage to get
            try:
                assert type(analysis_results[4]) is list
                if len(analysis_results[4]) != 0:
                    assert type(analysis_results[4][0]) is tuple
                assert type(analysis_results) == tuple
                assert len(analysis_results) == 6
                assert type(analysis_results[0]) == str
                assert type(analysis_results[1]) == str
                assert type(analysis_results[2]) == list
                assert type(analysis_results[3]) == str
                assert type(analysis_results[5]) == list
                for exception_tuple in analysis_results[5]:
                    assert type(exception_tuple) == tuple
                    assert len(exception_tuple) == 3
                    assert isinstance(exception_tuple[0], type)
                    assert isinstance(exception_tuple[1], Exception)
                    assert isinstance(exception_tuple[2], object)
                assert analysis_results[0] == test_case['tc_basic']['id']
            except AssertionError:
                self.api_error(
                    'Problem with the analyse of TC %s, wrong result received'
                    %
                    testcase_id
                )
                return

            # Only take the first
            verdict = OrderedDict()
            verdict['_type'] = 'verdict'
            verdict['verdict'] = analysis_results[1]
            verdict['description'] = analysis_results[3]
            verdict['review_frames'] = analysis_results[2]
            verdict['partial_verdicts'] = analysis_results[4]

            token_res = OrderedDict()
            token_res['_type'] = 'token'
            token_res['value'] = token

            # Prepare the result to return
            json_result = OrderedDict()
            json_result['_type'] = 'response'
            json_result['ok'] = True
            json_result['content'] = [
                token_res,
                test_case['tc_basic'],
                verdict
            ]
            self.log_message("Analysis response sent: " + str((analysis_results[4])))

            self.log_message("Analysis response sent: " + str(json.dumps(json_result)))

            # Here we will analyze the pcap file and get the results as json
            print(json.dumps(json_result))
            return



        # POST handler for the analyzer_allMightyAnalyze uri
        # It will allow users to analyze a pcap file without giving
        # a corresponding test case
        #
        # \param pcap_file => The pcap file that we want to analyze
        # \param token => The token previously provided
        # The pcap_file or the token is required, having both is also forbidden
        #
        elif self.path == '/api/v1/analyzer_allMightyAnalyze':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Not implemented for the moment
            self.api_error(
                "This method is not implemented yet, please come back later"
            )
            return

        # POST handler for the dissector_dissectFile uri
        # It will allow users to analyze a pcap file corresponding to a TC
        #
        # \param pcap_file => The pcap file that we want to dissect
        # \param protocol_selection => The protocol name
        #
        elif self.path == '/api/v1/dissector_dissectFile':

            # Send the header
            self.send_response(200)
            self.send_header('Content-Type', 'application/json;charset=utf-8')
            self.end_headers()

            # Bind the stdout to the http output
            os.dup2(self.wfile.fileno(), sys.stdout.fileno())

            # Get the content type
            try:
                content_type = cgi.parse_header(self.headers['Content-Type'])
            except TypeError:
                self.api_error(
                    "Non empty POST datas and format of 'multipart/form-data' expected"
                )
                return

            # Check headers
            if any((
                len(content_type) == 0,
                content_type[0] is None,
                content_type[0] != 'multipart/form-data'
            )):
                self.api_error(
                    "POST format of 'multipart/form-data' expected, no file input 'pcap_file' found"
                )
                return

            # Get post values
            form = cgi.FieldStorage(
                fp=self.rfile,
                headers=self.headers,
                environ={
                    'REQUEST_METHOD': 'POST',
                    'CONTENT_TYPE': content_type[0]
                })

            # Check the parameters passed
            if any((
                len(form) != 2,
                'pcap_file' not in form,
                'protocol_selection' not in form
            )):
                self.api_error(
                    'Expected POST=(pcap_file={file}, protocol_selection={text})'
                )
                return

            # Check the protocol_selection value
            protocol_selection = form.getvalue('protocol_selection')
            if not type(protocol_selection) == str:
                self.api_error('Expected protocol_selection post value to be a text (eq string)')
                return

            # In function of the protocol asked
            prot = get_protocol(protocol_selection)
            if prot is None:
                self.api_error('Unknown protocol %s' % protocol_selection)
                return

            # Generate a new token
            token = get_token()

            # Get the pcap file
            pcap_file = form.getvalue('pcap_file')

            # Path to save the file
            pcap_path = os.path.join(
                TMPDIR,
                token + '.dump'
            )

            # Write the pcap file to a temporary destination
            try:
                with open(pcap_path, 'wb') as f:
                    f.write(pcap_file)
            except:
                self.api_error("Couldn't write the temporary file")
                return

            # Prepare the result to return
            json_result = OrderedDict()
            json_result['_type'] = 'response'
            json_result['ok'] = True

            token_res = OrderedDict()
            token_res['_type'] = 'token'
            token_res['value'] = token

            # Get the dissection from dissector tool
            try:
                dissection = Dissector(pcap_path).dissect(eval(prot['name']))
            except TypeError as e:
                self.api_error('Dissector error: ' + str(e))
                return
            except pure_pcapy.PcapError:
                self.api_error(
                    "Expected 'pcap_file' to be a non empty pcap file"
                )
                return
            except:
                self.api_error(
                    "Couldn't read the temporary file %s and protocol is %s"
                    %
                    (
                        pcap_path,
                        prot['name']
                    )
                )
                return

            # Save the json dissection result into a file
            json_save = os.path.join(
                TMPDIR,
                token + '.json'
            )
            try:
                with open(json_save, 'w') as f:
                    json.dump(dissection, f)
            except:
                self.api_error("Couldn't write the json file")
                return

            # Add the token to the results
            dissection.insert(0, token_res)

            # The json result to return
            json_result['content'] = dissection

            # Here we will analyze the pcap file and get the results as json
            print(json.dumps(json_result))
            return

        # If we didn't manage to bind the request
        else:
            self.send_error(404)
            return