###############################################################################
# Local Security Check Automation Framework
#
# Authors:
# Veerendra GG <veerendragg@secpod.com>
#
# Revision 1.0
# Date: 2009/02/13
#
# 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 Red Hat OSes for parsing. The value is as used in
## gather-package-list.nasl to set "ssh/login/release"
os_map = {

    'Red Hat Enterprise Linux (v. 5 server)' : 'RHENT_5',
    'Red Hat Enterprise Linux ES version 2.1' : 'RHENT_2.1',
    'Red Hat Enterprise Linux WS version 2.1' : 'RHENT_2.1',
    'Red Hat Enterprise Linux AS version 3' : 'RHENT_3',
    'Red Hat Enterprise Linux ES version 3' : 'RHENT_3',
    'Red Hat Enterprise Linux WS version 3' : 'RHENT_3',
    'Red Hat Enterprise Linux AS version 4' : 'RHENT_4',
    'Red Hat Enterprise Linux ES version 4' : 'RHENT_4',
    'Red Hat Enterprise Linux WS version 4' : 'RHENT_4',
    'Red Hat Enterprise Linux AS (Advanced Server) version 2.1' : 'RHENT_2.1',

#    'Red Hat Enterprise Linux Desktop version 4' : 'RHENT_4',
#    'Red Hat Enterprise Linux Desktop (v. 5 client)' : 'RHENT_5',
#    'Red Hat Enterprise Linux AS version 4.5.z' : 'RHENT_4.5',
#    'Red Hat Enterprise Linux ES version 4.5.z' : 'RHENT_4.5',
#    'Red Hat Enterprise Linux AS version 3 Extras' : 'RHENT_3',
#    'Red Hat Enterprise Linux ES version 3 Extras' : 'RHENT_3',
#    'Red Hat Enterprise Linux WS version 3 Extras' : 'RHENT_3',
#    'Red Hat Enterprise Linux AS version 4 Extras' : 'RHENT_4',
#    'Red Hat Enterprise Linux ES version 4 Extras' : 'RHENT_4',
#    'Red Hat Enterprise Linux WS version 4 Extras' : 'RHENT_4',
#    'Red Hat Enterprise Linux AS version 4.5.z Extras' : 'RHENT_4.5',
#    'Red Hat Desktop version 3' : 'RHDeskV3',
#    'Red Hat Desktop version 4 Extras' : 'RHDeskV4Ex',
#    'Red Hat Linux Advanced Workstation 2.1' : 'RHLAWV2.1',
#    'Red Hat Application Server v2 4AS' : 'RHAppSerV24AS',
#    'Red Hat Application Server v2 4ES' : 'RHAppSerV24ES',
#    'Red Hat Application Server v2 4WS' : 'RHAppSerV24WS',
#    'MRG Grid for RHEL 5 Server' : 'MRGGrRHEL5Ser',
#    'Red Hat MRG Grid for RHEL' : 'RHMRGGrRHEL',
#    'RHEL 4 Directory Server ES' : 'RHEL4DirSerES',
#    'RHEL 4 Directory Server AS' : 'RHEL4DirSerAS',
#    'RHEL 3 Directory Server ES' : 'RHEL3DirSerES',
#    'RHEL 3 Directory Server AS' : 'RHEL3DirSerAS',
#    'RHEL Desktop Supplementary (v. 5 client)' : 'RHELDeslSupV5Cli',
#    'RHEL Desktop Workstation (v. 5 client)' : 'RHELDesWorkV5Cli',
#    'RHEL Supplementary (v. 5 server)' : 'RHELSupV5Ser',
#    'Red Hat Desktop version 3 Extras' : 'RHDesV3Ext',
#    'Red Hat Certificate System 7.2 for 4AS' : 'RHCS7.2AS',
#    'Red Hat Certificate System 7.2 for 4ES' : 'RHCS7.2ES',
#    'MRG Realtime for RHEL 5 Server' : 'MRGRTRHEL5Ser',
#    'Red Hat Certificate System 7.3 for 4AS' : 'RHCS7.3AS',
#    'Red Hat Certificate System 7.3 for 4ES' : 'RHCS7.3ES',
#    'RHEL Desktop Multi OS (v. 5 client)' : 'RHELDesMuOSV5Cli',
#    'RHEL Virtualization (v. 5 server)' : 'RHELVirV5Ser',
#    'RHEL Optional Productivity Applications (v. 5 server)' : 'RHELOpProAppV5Ser',
#    'Red Hat Application Stack v1 for Enterprise Linux AS (v.4)' : 'RHAppStV1EntLASV4',
#    'Red Hat Application Stack v1 for Enterprise Linux ES (v.4)' : 'RHAppStV1EntLESV4',
#    'Red Hat Application Stack v2 for Enterprise Linux (v.5)' : 'RHAppStV2EntV5',
#    'Red Hat Network Satellite Server 5.1 (RHEL v.4 AS)' : 'RHNetSatSerV5.1RHELV4AS',
#    'Red Hat Network Satellite Server v 4.2 (RHEL v.3 AS)' : 'RHNetSatSerV4.2RHELV3AS',
#    'Red Hat Network Satellite Server v 4.2 (RHEL v.4 AS)' : 'RHNetSatSerV4.2RHELV4AS',
#    'Red Hat Developer Suite v.3 (AS v.4)' : 'RHDevSuV3ASV4',
#    'JBoss Enterprise Application Platform for RHEL 5 Server' : 'JBEntAppRHEL5Ser',
#    'Red Hat Network Satellite Server 5.0 (RHEL v.4 AS)' : 'RHNetSatSer5.0RHELV4AS',
#    'Red Hat IPA 1 for RHEL 5 Server' : 'RHIPA1RHEL5Ser',
#    'Red Hat Network Proxy v 5.1 (RHEL v.4 AS)' : 'RHNetProV5.1RHELV4AS',
#    'Red Hat Network Proxy v 5.0 (RHEL v.4 AS)' : 'RHNetProV5.0RHELV4AS',
#    'JBoss Enterprise Application Platform 4.3.0 for RHEL 4 AS' : 'JBEntApp4.3.0RHEL4AS',
#    'JBoss Enterprise Application Platform 4.3.0 for RHEL 4 ES' : 'JBEntApp4.3.0RHEL4ES',
#    'Red Hat Network Proxy v 4.2 (RHEL v.3 AS)' : 'RHNetPro4.2RHELV3AS',
#    'Red Hat Network Proxy v 4.2 (RHEL v.4 AS)' : 'RHNetPro4.2RHELV4AS',
#    'Red Hat Directory Server 7.1 (for AS v. 3)' : 'RHDirSer7.1ASV3',
#    'Red Hat Directory Server 8.0 (for AS v. 4)' : 'RHDirSer8.0ASV4',
#    'Red Hat Directory Server 8.0 (for ES v. 4)' : 'RHDirSer8.0ESV4',
#    'Red Hat Directory Server 8.0 (for RHEL 5 Server)' : 'RHDirSer8.0RHEL5Ser',
#    'JBoss Enterprise Application Platform for RHEL 4 AS' : 'JBEntAppRHEL4AS',
#    'JBoss Enterprise Application Platform for RHEL 4 ES' : 'JBEntAppRHEL4ES',
#    'JBoss Enterprise Application Platform 4.3.0 for RHEL 5 Server' : 'JBEntApp4.3.0RHEL5Ser',
}

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

append_url = 'https://www.redhat.com/archives/rhsa-announce/'

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


class Parser:
    """
    Red Hat 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 = ''
    total_prod_list = []


    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 "\nRed Hat Advisory 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="(msg.*.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 "\nRead Hat Advisories for (%s) year-month" %(year_month)
                print "Total (%s) Red Hat Advisories : " %(len(month_links))

            return month_links

        return []


    def fetchHTML(self, year, debug=0):
        """
        Retrive Red Hat 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 Red Hat "+ \
                                                    "\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 "No Red Hat Security Advisories for : \n", link
                    continue

                all_adv_links.extend(month_links)

            if not all_adv_links:
                print "ERROR: Din't find any Red Hat 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 Red Hat 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 : redhat -> 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 Red Hat Security Advisory ID
        """

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

        adv_id_regex =  re.compile('Advisory ID:\s+ (.*)')
        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("Synopsis:.*:(.*) security")
        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..."

        desc_regex =  re.compile("(?s)Description:\s+(.*)\n.*Solution", \
                                                           re.IGNORECASE)
        desc = self._findAll(desc_regex)

        if desc:
            desc = desc[0].strip()

            ## Formatting the description
            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
        """
        prd_list = []

        ## Get Affected Product/Platform
        prod_regex =  re.compile("(?s)Relevant releases/architectures:(.*)"+ \
                                           "3\..*Description:", re.IGNORECASE)
        products = self._findAll(prod_regex)

        if products:
            products = products[0].strip().split('\n')

            if products:
                for i in products:
                    prd_list.append(i.split('-')[0].strip())

        if debug:
            print "\nAffected Product is/are : (%s)" %(products)

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

        if ref_list and debug:
            print "\nGenerating 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..."

        os_pkg_dict = {}
        data = re.findall("(?s)Package List:(.*) References:", \
                                              self.Html_content)
        if not data:
            data = re.findall("(?s)RPMs required:(.*) References:", \
                                                  self.Html_content)
        if data:
            data = data[0].strip()
        else:
            if debug:
                print "\nERROR: Package List not found in the advisory."
            return {}

        for prod in prod_list:
            rpm_list = []

            if debug:
                print "\nGetting RPM For : ", prod

            if string.find(data, prod) == -1:
                if debug:
                    print "\nERROR: Product not found in the data : ", prod
                continue

            tmp_data = data[string.find(data, prod) + len(prod) + 2:]
            tmp_data = tmp_data.strip()

            for line in tmp_data.split('\n'):
                line = line.strip().strip(':')
                if not line:
                    continue

                if line in self.total_prod_list:
                    break

                flag = 1
                for i in strip_val:
                    if i in line:
                        line = line.split(' ')[-1]
                        rpm_list.append(line)
                        flag = 0

                if flag and ".rpm" in line:
                    if debug:
                        print "Found rpm other then, %s in line :: %s" \
                                            % (', '.join(strip_val), line)
            if not rpm_list:
                if debug:
                    print "\nERROR: RPMs not found for Product ", prod
                continue

            rpm_list = utils.stripIt(rpm_list, strip_val)
            rpm_list = utils.removeDups(rpm_list)

            if debug:
                print "Found PRMS are : ", rpm_list

            if os_map.has_key(prod):
                os_pkg_dict[os_map[prod]] = rpm_list

            if debug:
                print "\nOS PKG Dict : ", os_pkg_dict

        if not os_pkg_dict:
            if debug:
                print "\nNo/different RPMs Found for any product ",

        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 "Red Hat 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 "\nERROR: Required Products not found..."
                return False

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

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

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

            self.Product = self.getAffectedPackage(debug)
            if not self.Product or self.Product == '':
                if debug:
                    print "\nERROR: Required Products not found..."
                return False
            self.Platforms = ",\n  ".join(self.Platforms)

            self.Summary = self.Product

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

            self.Impact = '  '

            ## Construct File Name
            (name, value1, value2) = self.AdvID.split('-')
            self.FileName = "_".join([self.AdvID.replace(':', '_'), \
                                                       self.Product])
            ## 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 redhat -> Parser -> parser() Method '
            sys.exit(msg)
