### BEGIN LICENSE ###
### Use of the triage tools and related source code is subject to the terms
### of the license below.
###
### ------------------------------------------------------------------------
### Copyright (C) 2011 Carnegie Mellon University. All Rights Reserved.
### Portions Copyright 2013 BlackBerry Ltd. All Rights Reserved.
### ------------------------------------------------------------------------
### Redistribution and use in source and binary forms, with or without
### modification, are permitted provided that the following conditions are
### met:
###
### 1. Redistributions of source code must retain the above copyright
###    notice, this list of conditions and the following acknowledgments
###    and disclaimers.
###
### 2. Redistributions in binary form must reproduce the above copyright
###    notice, this list of conditions and the following disclaimer in the
###    documentation and/or other materials provided with the distribution.
###
### 3. The names "Department of Homeland Security," "Carnegie Mellon
###    University," "CERT" and/or "Software Engineering Institute" shall
###    not be used to endorse or promote products derived from this software
###    without prior written permission. For written permission, please
###    contact permission@sei.cmu.edu.
###
### 4. Products derived from this software may not be called "CERT" nor
###    may "CERT" appear in their names without prior written permission of
###    permission@sei.cmu.edu.
###
### 5. Redistributions of any form whatsoever must retain the following
###    acknowledgment:
###
###    "This product includes software developed by CERT with funding
###     and support from the Department of Homeland Security under
###     Contract No. FA 8721-05-C-0003."
###
### THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
### CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
### EXPRESS OR IMPLIED, AS TO ANY MATTER, AND ALL SUCH WARRANTIES, INCLUDING
### WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE
### EXPRESSLY DISCLAIMED. WITHOUT LIMITING THE GENERALITY OF THE FOREGOING,
### CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND
### RELATING TO EXCLUSIVITY, INFORMATIONAL CONTENT, ERROR-FREE OPERATION,
### RESULTS TO BE OBTAINED FROM USE, FREEDOM FROM PATENT, TRADEMARK AND
### COPYRIGHT INFRINGEMENT AND/OR FREEDOM FROM THEFT OF TRADE SECRETS.
### END LICENSE ###
'''
A collection of objects used to classify GDB Inferiors (Targets).
'''

import copy
import warnings, traceback
from functools import partial

import lib.rules as rules
from lib.tools import AttrDict

class Tag(object):
    '''
    A Tag used for classification. A Tag is a partial description of the
    state of a GDB Inferior. Tags can be ordered by ranking.
    '''
    def __init__(self, tag_dict):
        self.__dict__ = tag_dict

    # for python3
    def __lt__(self, other):
        if type(other) != type(self):
            raise TypeError("cannot compare type %s to type %s" % \
                            (type(other),type(self)))
        return self.ranking[0] < other.ranking[0]

    def __cmp__(self, other):
        if not issubclass(type(other), type(self)):
            raise TypeError("cannot compare type {} to type {}".format(type(other), type(self)))
        return self.ranking[0] - other.ranking[0]

    def __str__(self):
        return "{0} ({1[0]:d}/{1[1]:d})".format(self.short_desc, self.ranking)

class Classification(AttrDict):
    '''
    A classification of a state of a GDB Inferior. Describes how exploitable
    the current state of the GDB Inferior is.

    An instance of this object is returned by a Classifier.
    '''
    def __init__(self, target):
        AttrDict.__init__(self)
        self.tags = []

    def __add__(self, tag):
        if not issubclass(type(tag), Tag):
            raise TypeError("cannot add type {} to type {}".format(type(tag), type(self)))
        self.tags.append(tag)
        self.tags.sort()
        for k, v in self.tags[0].__dict__.items():
            self[k] = v

        return self

    # for python3
    def __lt__(self, other):
        if other is None:
            return False

        if not issubclass(type(other), type(self)):
            raise TypeError("cannot compare type {} to type {}".format(type(other), type(self)))

        if len(self.tags) == 0 or len(other.tags) == 0:
            return len(self.tags) < len(other.tags)

        i = 0
        while i < len(self.tags) and i < len(other.tags):
            if self.tags[i] < other.tags[i]:
                return True
            i += 1
        return False

    def __cmp__(self, other):
        if other is None:
            return 1

        if not issubclass(type(other), type(self)):
            raise TypeError("cannot compare type {} to type {}".format(type(other), type(self)))

        if len(self.tags) == 0 or len(other.tags) == 0:
            return len(self.tags) - len(other.tags)

        i = 0
        while i < len(self.tags) and i < len(other.tags):
            result = cmp(self.tags[i], other.tags[i])
            if result:
                return result
            i += 1
        return result

    def __str__(self):
        if not self.tags:
            return "No matches"

        result = ["Description: {}".format(self.desc),
                  "Short description: {}".format(self.tags[0]),
                  "Hash: {}.{}".format(self.hash.major, self.hash.minor),
                  "Exploitability Classification: {}".format(self.category),
                  "Explanation: {}".format(self.explanation)]
        if len(self.tags) > 1:
            result.append("Other tags: {}".format(
                ", ".join(str(r) for r in self.tags[1:])))
        result.append("")
        return "\n".join(result)

class Classifier(object):
    '''
    A Classifier used for classifying the state of a Target (a Linux GDB
    Inferior).
    '''
    _major_hash_depth = 5

    def getRules(self, target):
        '''
        Organizes the nested list of rules (dicts) for classification

        The rules specified in rules.py are organized into AttrDicts ("rules").
        Each rule is composed of a tag and a match_function.
        '''

        processed_rules = []
        num_rules = sum(len(rl) for (_, rl) in rules.rules)
        ranking = 1
        for cat, user_rule_list in rules.rules:
            for user_rule in user_rule_list:
                match_function = partial(getattr(target.analyzer, user_rule["match_function"])) 
                tag_data = copy.deepcopy(user_rule)
                del tag_data["match_function"]
                tag_data["ranking"] = (ranking, num_rules)
                tag_data["category"] = cat
                rule = AttrDict(matches=match_function, tag=Tag(tag_data))
                processed_rules.append(rule)
                ranking += 1

        return processed_rules

    def getClassification(self, target):
        '''
        Returns the Classification of target, which is a Classification of the
        exploitability of a Linux GDB Inferior.
        '''
        c = Classification(target)
        for rule in self.getRules(target):
            try:
                match = rule.matches()
                if match:
                    c += rule.tag
            except Exception as e:
                warnings.warn("Error while analyzing rule {}: {}\n{}".format(
                    rule.tag, e, traceback.format_exc()))

        c.hash = target.hash()
        return c

