# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
import os
import sys
import time
from common import parseDir

__authors__ = "Nick Beede, Drew Bonasera"
__license__ = "MPL 2.0"

TYPE = "Signature"
NAME = "Yara"
DEFAULTCONF = {"ruledir":os.path.join(os.path.realpath(os.path.dirname(sys.argv[0])), 'etc', 'yarasigs'),
    "fileextensions":[".yar", ".yara", ".sig"],
    "ignore-tags":["TLPRED"],
    'ENABLED': True
    }
    
try:
    import yara
except:
    print("yara-python module not installed...")
    yara = False
    
def check(conf=DEFAULTCONF):
    if not conf['ENABLED']:
        return False
    if not yara:
        return False
    return True

def scan(filelist, conf=DEFAULTCONF):
    ruleDir = conf["ruledir"]
    extlist = conf["fileextensions"]
    ruleset = {}
    rules = parseDir(ruleDir, recursive=True)
    for r in rules:
        for ext in extlist:
            if r.endswith(ext):
                ruleset[r] = os.path.join(ruleDir, r)
                break
    
    #Ran into a weird issue with file locking, this fixes it
    goodtogo = False
    i = 0
    yararules = None
    while not goodtogo:
        try:
            yararules = yara.compile(filepaths=ruleset)
            goodtogo = True
        except yara.SyntaxError as e:
            bad_file = e.message.split('(')[0]
            del ruleset[bad_file]
            print(e)

    matches = []
    for m in filelist:
        #Ran into a weird issue with file locking, this fixes it
        goodtogo = False
        i = 0
        while not goodtogo and i < 5:
            try:
                f = open(m, 'rb')
                goodtogo = True
            except Exception as e:
                print('yara:', e)
                time.sleep(3)
                i += 1
        try:
            hit = yararules.match(data=f.read())
        except:
            continue
        finally:
            f.close()
        if hit:
            hlist = []
            for h in hit:
                if not set(h.tags).intersection(set(conf["ignore-tags"])):
                    hlist.append(str(h))
            hlist.sort()
            matches.append((m, hlist))
            
    metadata = {}
    rulelist = list(ruleset)
    rulelist.sort()
    metadata["Name"] = NAME
    metadata["Type"] = TYPE
    metadata["Rules"] = rulelist
    return (matches, metadata)


@@ -351,16 +351,48 @@
 parseDir
+%0Afrom operator import itemgetter
 %0A%0A__auth
@@ -2327,26 +2327,321 @@
    h
-list.append(str(h)
+it_dict = %7B%0A                        'meta'      : h.meta,%0A                        'namespace' : h.namespace,%0A                        'rule'      : h.rule,%0A                        'strings'   : h.strings,%0A                        'tags'      : h.tags,%0A                    %7D%0A                    hlist.append(hit_dict
 )%0A  
@@ -2651,30 +2651,63 @@
        hlist
-.
+ = 
 sort
-(
+ed(hlist, key=itemgetter('rule')
 )%0A          

Add yara.Match as a Python dict for additional context