###############################################################################
# Local Security Check Automation Framework
#
# Authors:
# Veerendra GG <veerendragg@secpod.com>
#
# Revision 1.0
# Date: 2009/02/10
#
# Copyright:
# Copyright (c) 2009 SecPod , http://www.secpod.org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2
# (or any later version), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################

import re
import os
import sys
import string

from common import utils


## Supported Fedora OSes for parsing. The value is as used in
## gather-package-list.nasl to set "ssh/login/release"
os_map = {

    'Fedora Core 3' : 'FC3',
    'Fedora Core 4' : 'FC4',
    'Fedora Core 5' : 'FC5',
    'Fedora Core 6' : 'FC6',

    'Fedora 7' : 'FC7',
    'Fedora 8' : 'FC8',
    'Fedora 9' : 'FC9',
    'Fedora 10' : 'FC10',
    'Fedora 11' : 'FC11',
    'Fedora 12' : 'FC12',
#    'Fedora 13' : 'FC13',
}

## Strips these from strip list
strip_val = ['.i586.rpm', '.x86_64.rpm', '.noarch.rpm', '.i386.rpm']

## append_url = 'https://www.redhat.com/archives/fedora-package-announce/'
## New URL because no more advisories are been updated in above link
append_url = 'http://lists.fedoraproject.org/pipermail/package-announce/'

## These are not advisories
skip_list = ['']


class Parser:
    """
    Fedora security advisory parser, parse and populate the global variables
    """

    ## Global parse structure, initializing
    AdvID = ''
    Description = ''
    Packages = {}
    CVEs = ''
    Name = ''
    Summary = ''
    Platforms = ''
    Product = []
    Html_content = ''
    XREF = []
    FileName = ''


    def _getYearLinks(self, link, year, debug=0):
        """
        Gets the advisory links for the given year
        """
        year_links = []
        data = utils.getHTMLCon(link)
        links = re.findall('href="(' + str(year) + '.*thread.html)', data)
        if links:
            for i in links:
                year_links.append(append_url + i)

            if debug:
                if year_links:
                    print "\nAdvisory Links for (%s) year" %(year)
                    for i in year_links:
                        print i

            return year_links

        return []


    def _getEachAdvLink(self, link, debug=0):
        """
        Get security advisory links.
        """
        month_links = []
        data = utils.getHTMLCon(link)
        links = re.findall('HREF="(.*\.html).*\[SECURITY\]', data)
        year_month = os.path.basename(os.path.split(link)[0])

        if links:
            for i in links:
                month_links.append(append_url + year_month + '/' + i)

            if debug:
                print "\nFedora Advisories for (%s) year-month" %(year_month)
                print "Total (%s) Fedora Advisories : " %(len(month_links))

            return month_links

        return []


    def fetchHTML(self, year, debug=0):
        """
        Retrive Fedora Advisories locally
        """

        try:
            all_adv_links = []

            year_links = self._getYearLinks(self.main_url, year, debug)

            if not year_links:
                print "ERROR: Din't find mentioned (%s) year in Fedora "+ \
                                             "Advisories..." %(year)
                print "Exiting ..."
                sys.exit(0)

            for link in year_links:
                month_links = self._getEachAdvLink(link, debug)
                if not month_links:
                    if debug:
                        print "ERROR: No Fedora Security Advisories for : \n", link
                    continue

                all_adv_links.extend(month_links)

            if not all_adv_links:
                print "ERROR: Din't find any Fedora Security Advisories...", year
                print "Exiting ..."
                sys.exit(0)

            all_adv_links = utils.removeDups(all_adv_links)

            for adv_url in all_adv_links:

                base_name = adv_url.split('/')[-1]
                month_year = adv_url.split('/')[-2]
                file_name = self.html_cache + month_year + '_' + base_name

                if not os.path.isfile(file_name):
                    if debug:
                        print "\nFetching Fedora Advisory..." + \
                                        os.path.basename(adv_url)
                    try:
                        utils.fetchFiles(adv_url, file_name, debug)
                    except Exception, msg:
                        print 'ERROR: Error fething the url %s' % msg

        except Exception, msg:
            print "Exception in : fedora -> Parser(Class) -> fetchHTML method()"
            sys.exit(msg)


    def _findAll(self, regex):
        """
        Returns Matched data
        """
        return regex.findall(self.Html_content, re.IGNORECASE)


    def getCVE(self, debug=0):
        """
        Returns CVE list
        """
        if debug:
            print "\nGetting CVE List..."

        cve_regex = re.compile('CVE-[0-9]+-[0-9]+')
        can_regex = re.compile('CAN-[0-9]+-[0-9]+')

        cve_list = self._findAll(cve_regex)
        cve_list.extend(self._findAll(can_regex))

        cve_list = utils.removeDups(cve_list)

        if cve_list:
            cve_list = '", "'.join(cve_list)
        else:
            cve_list = ''

        if debug:
            print "CVE List : ", cve_list

        return cve_list


    def getAdvID(self, debug=0):
        """
        Returns Fedora Security Advisory ID
        """

        if debug:
            print "\nGetting Advisory ID..."

        adv_id_regex =  re.compile('FEDORA-\d\d\d\d-[0-9]+')
        adv_id = self._findAll(adv_id_regex)

        if not adv_id:
            return ''

        if debug:
            print "Advisory ID : ", adv_id

        return adv_id[0].strip()


    def getAffectedPackage(self, debug=0):
        """
        Returns Affected Packages/RPM's
        """

        if debug:
            print "\nGetting Affected Packages/RPM List..."

        pkg_regex =  re.compile("Name\s+:(.*)", re.IGNORECASE)
        pkg = self._findAll(pkg_regex)

        if pkg:
            pkg = pkg[0].strip()
        else:
            pkg = ''

        if debug:
            print "Affected Packages/RPMS : ", pkg

        return pkg


    def getDescription(self, debug=0):
        """
        Returns Vulnerability Description
        """
        description = ''

        if debug:
            print "\nGetting Vulnerability Description..."

        if string.find(self.Html_content, 'Update Information:') >= 0:
            desc_regex =  re.compile("(?s)Description\s+:\s+(.*)\n\n.*Update"+\
                                                 " Information:", re.IGNORECASE)
        elif string.find(self.Html_content, 'ChangeLog:') >= 0:
            desc_regex =  re.compile("(?s)Description\s+:\s+(.*)\n\n.*"+ \
                                               "ChangeLog:", re.IGNORECASE)
        elif string.find(self.Html_content, 'References:') >= 0:
            desc_regex =  re.compile("(?s)Description\s+:\s+(.*)\n\n.*"+ \
                                               "References:", re.IGNORECASE)
        else:
            desc_regex =  re.compile("(?s)Description\s+:\s+(.*)\.\n\n--.*", \
                                                                re.IGNORECASE)

        desc = self._findAll(desc_regex)

        if not desc:
            desc = re.findall('(?s)Description\s+:\s+(.*)\.?\n\n--+\n\n', \
                                                         self.Html_content)
        if desc:
            desc = desc[0].strip()
            for i in desc.split('\n'):
                description += '  ' + i + '\n'
            description = description.replace('"'," &qt ")
        else:
            description = ''

        return description


    def getAffectedProduct(self, debug=0):
        """
        Returns Affected Product/Platform
        """

        ## Get Affected Product/Platform
        prod =  re.findall("Product\s+:(.*)", self.Html_content)

        if prod:
            prod[0] = prod[0].strip()
        else:
            return []

        if debug:
            print "\nAffected Product is : (%s)" %(prod)

        ## Don't include Product/Platform, If not in "os_map" Dict
        ref_list = []
        if os_map.has_key(prod[0]):
            ref_list.append(prod[0])
        elif debug and prod[0]:
              print "UPDATE: Not Generating Code for (%s) OS" %(prod[0])
              print "If Needed to generate code, then "+ \
                        "add into dict variable os_map in parser"
        if ref_list and debug:
            print "Generating Code for (%s) Products " %(ref_list)

        return ref_list


    def getRPM(self, prod_list,  debug=0):
        """
        Returns OS Package Dictionary
        """

        if debug:
            print "\nGetting RPM List..."

        all_rpms = []
        os_pkg_dict = {}

        rpm = re.findall("<H1>\[SECURITY\].*Fedora.*:(.*)<", self.Html_content)
        if rpm:
            rpm = rpm[0].strip() + ".i386.rpm"
            all_rpms.append(rpm)

        rpms = re.findall(".* (.*\.rpm)", self.Html_content)
        if rpms:
            for i in rpms:
                i = i.strip()
                flag = 1
                for st_val in strip_val:
                    if st_val in i:
                        all_rpms.append(i)
                        flag = 0
                        break

                if flag and ".rpm" in st_val:
                    if debug:
                        print "Found rpm other then, %s in line :: %s" \
                                            % (', '.join(strip_val), i)

        if not all_rpms:
            return []

        all_rpms = utils.stripIt(all_rpms, strip_val)

        for i in prod_list:
           if os_map.has_key(i):
             os_pkg_dict[os_map[i]] = all_rpms

        if debug:
            print "OS PKG Dict : ", os_pkg_dict

        return os_pkg_dict


    def formatReference(self, main_url, file_name):
       """
       Constructs a reference for advisory
       """
       if not main_url.endswith('/'):
           main_url = main_url + '/'

       reference = main_url + '/'.join(file_name.split('_'))

       return reference


    def parser(self, html_content, debug=0):
        """
        Main parser function, builds the parser object
        by invoking parse functions
        """

        try:
            if debug:
                print "Fedora Parser Initiated..."

            self.Html_content = html_content.replace('\r\n', '\n')

            self.CVEs = self.getCVE(debug)

            self.Platforms = self.getAffectedProduct(debug)
            if not self.Platforms or self.Platforms == []:
                if debug:
                    print "ERROR: Required Products not found..."
                return False
            if len(self.Platforms) > 1:
                print "ERROR: More than one platform found : "

            self.Packages = self.getRPM(self.Platforms, debug)
            if not self.Packages or self.Packages == '':
                if debug:
                    print "ERROR: Required Packages not found..."
                return False

            self.Description = self.getDescription(debug)
            if not self.Description or self.Description == '':
                if debug:
                    print "ERROR: Description not found..."
                return False

            self.AdvID = self.getAdvID(debug)
            if not self.AdvID or self.AdvID == '':
                if debug:
                    print "ERROR: Advisory ID not found..."
                return False

            self.Product = self.getAffectedPackage(debug)
            if not self.Product or self.Product == '':
                if debug:
                    print "ERROR: Required Products not found..."
                return False

            self.Platforms = self.Platforms[0]

            self.Summary = self.Product

            self.Name = self.Product + " " + self.AdvID

            self.Impact = '  '

            ## Construct File Name
            (name, value1, value2) = self.AdvID.split('-')
            os_ver = re.findall('\d\d?\d?',self.Platforms)[0]
            self.FileName = "_".join(['fedora', value1 + "_" + value2,\
                                       self.Product, 'fc' + os_ver ])
            ## Set XREF
            self.XREF = [name, '-'.join([value1, value2])]

            if debug:
                print "\nAll mandatory attributes are parsed: ", self.AdvID

            return True

        except Exception, msg:
            print 'Exception in Parser fedora -> Parser -> parser() Method '
            sys.exit(msg)
