###############################################################################
# Local Security Check Automation Framework
#
# Authors:
# Veerendra GG <veerendragg@secpod.com>
# Geoff Galitz <geoff@eifel-consulting.eu>
#
# Revision 1.0
# Date: 2009/06/02
#
# 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 httplib2
import commands
import urllib

from common import utils


## Supported Solaris OSes for parsing.
## Old OS MAP
#os_map = {
#    'solaris_5.10_x86'     : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=10_x86_patch_report',
#    'solaris_5.10_sparc'   : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=10_patch_report',
#    'solaris_5.9_x86'      : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=9_x86_patch_report',
#    'solaris_5.9_sparc'    : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=9_patch_report',
#    'solaris_5.8_x86'      : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=8_x86_patch_report',
#    'solaris_5.8_sparc'    : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=8_patch_report',
#    'solaris_5.7_x86'      : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=7_x86_patch_report',
#    'solaris_5.7_sparc'    : 'http://sunsolve.sun.com/patchRedirnew.do?type=report&item=7_patch_report',
#}

os_map = {
    'solaris_5.10_x86'     : 'http://sunsolve.sun.com/show.do?target=patches/zos-x10',
    'solaris_5.10_sparc'   : 'http://sunsolve.sun.com/show.do?target=patches/zos-s10',
    'solaris_5.9_x86'      : 'http://sunsolve.sun.com/show.do?target=patches/zos-x9',
    'solaris_5.9_sparc'    : 'http://sunsolve.sun.com/show.do?target=patches/zos-s9',
    'solaris_5.8_x86'      : 'http://sunsolve.sun.com/show.do?target=patches/zos-x8',
    'solaris_5.8_sparc'    : 'http://sunsolve.sun.com/show.do?target=patches/zos-s8',
}

## Patch Download Automation using wget
## patch = http://sunsolve.sun.com/search/document.do?assetkey=1-9-240066-1
## New OS Links
## http://sunsolve.sun.com/show.do?target=patchpage

class Parser:
    """
    Solaris 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 _getMainSolarisAdvPage(self, url, debug=0):
        """
        Read Solaris Main Advisory page
        """

        http = httplib2.Http()

        ## Required Headers
        headers = {'User-Agent':'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008120121 Firefox/3.0.5', 'Content-Type':'application/x-www-form-urlencoded', 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language':'en-us,en;q=0.5', 'Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Keep-Alive':'300','Accept-Encoding':'gzip,deflate', 'Connection':'keep-alive'}

        ## Add Cookie into the header
        headers['Cookie'] = 'SunSolve_SLA=accept=Y'

        ## Send Get Request and get response, content
        response, content = http.request(url, 'GET', '', headers)

        return (response, content)


    def _getAdvIds(self, data, year, debug=0):
	"""
        Get Advisory ID from Solaris Advisory
        """

        adv_list = []
        adv_list = re.findall('nowrap>(\d{6}\d?\d?-\d{2}\d?\d?).*\n.*nowrap>\w+/\d\d/'+ year[2:], data)
        return adv_list


    def _getSolarisPatch(self, patch_id, debug=0):
       """
       Download and Extract Solaris Patch using wget and unzip commands
       """

       ## Command to download Patches
       ## Auth-no-challenge makes wget use basic HTTP authorization.
       cmd = "wget --http-user tech.veerendra@gmail.com --http-password " \
            +" vf2kLQn1 --no-check-certificate --auth-no-challenge "

       if not patch_id:
           if debug:
               print "Did not find patch id"
               return False

       ## defines the URL to get the patch data 
       url = '"http://sunsolve.sun.com/pdownload.do?target=%s&method=h"' %(patch_id)
       if debug:
           print "\nDownloading (%s) patch from \n(%s)\n" %(patch_id, url)
           print "Started Downloading (%s) patch using (wget)..."%(patch_id)
           print "NOTE: Downloading will take time depending on patch size..."

       ## Execute command for downloading patches
       ## This runs wget and creates an output file named after the update
       ## e.g.: 103232-01.zip
       cmd = cmd + " " + url + " -O " + patch_id + ".zip"

       if debug:
           print "Executing command: %s" %(cmd)

       msg = commands.getoutput(cmd)

       ## Verify the result
       if not "200 OK" in msg and "404 Not Found" in msg:
           if debug:
               print 'ERROR 404: Not Found.'
           cmd = 'rm -rf ' + patch_id + '.zip'
           msg = commands.getoutput(cmd)
           return False

       ## Premium Contents/Patches allowed for only Subscribers
       if "ERROR 403" in msg and "Forbidden" in msg:
           if debug:
               print "ERROR: Not able to download the patch for : ",patch_id
               print "Probable Reason : You have selected premium content " \
                     + "which requires a valid Sun Contract to access"
           cmd = 'rm -rf ' + patch_id + '.zip'
           msg = commands.getoutput(cmd)
           return False

       ## Verify Patch Download
       if not os.path.exists(patch_id + '.zip'):
           if debug:
               print "(%s) Patch not downloaded." %(patch_id)
               print "Error MSG : ", msg
           return False

       ## Get size of the download
       length = re.findall('\[(\d+)/\d+\]', msg)
       if length:
           length = length[0]
           if debug:
               print "Downloaded (%s) patch having (%s) size" \
                                            %(patch_id, length)
       ## Execute command to unzip patches
       cmd = 'unzip '+ patch_id + '.zip'
       if debug:
           print "\nExtracting downloaded patch %s : " %(patch_id + '.zip')
       msg = commands.getoutput(cmd)

       if "cannot find or open" in msg:
           print "ERROR: ",msg
           return False

       ## Remove zipped files
       cmd = 'rm -rf ' + patch_id + '.zip'
       if debug:
           print "Removing zip file %s " %(patch_id + '.zip')
       msg = commands.getoutput(cmd)

       return True


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

        try:
            all_adv_ids = []

            ## Iterate over all os and collect all advisory ids into the list
            for _os in os_map.keys():
                if debug:
                    print "\nGetting Advisory ids for (%s) ..."%(_os)

                link = os_map[_os]
                ## Get main advisory to collect advisory ids for perticular os
                content = utils.getHTMLCon(link, debug)
                if not ("Patches and Updates<" in content and "Patch Id"
                         in content and "Download" in content and "Description"
                         in content):
                    if debug:
                        print "ERROR: Din't get a proper response " \
                                                  "from %s os " %(_os)
                    continue

                ## Get Advisory ids from the main page
                adv_ids = self._getAdvIds(content, year, debug)
 
                if not adv_ids:
                    if debug:
                        print "Didn't found any Solaris Advisory "+ \
                                      "ids for (%s) -> year (%s)" %(_os, year)
                    continue

                if debug:
                    print "Advisory List for %s OS" %(_os)
                    print "Advisory List : ", adv_ids
                    print "Found (%s) Advisory ids for (%s) os" \
                                             %(len(adv_ids), _os)
                    print "\n###############################################"

                ## Append all advisory ids
                all_adv_ids.extend(adv_ids)

            ## Remove dupicate Advisory ids in the list
            all_adv_ids = utils.removeDups(all_adv_ids)

            if not all_adv_ids:
                print "ERROR: Solaris Advisories ids not found for any of the OS"
                print "Exiting ..."
                return "Exit: No Advisory Found"
#                sys.exit(0)

            if debug:
                print "\nTotal (%s) Advisories found " %(len(all_adv_ids))

            ## Iterate over all Advisory ids, download the 
            ## Advisory page and Patch for the same
            count = 0
            for adv_id in all_adv_ids:
                count = count + 1
                print "\n###############################################"
                adv_url = "http://sunsolve.sun.com/search/document.do?"+ \
                                            "assetkey=1-21-%s-1" %(adv_id)

                ## Read the advisory page
                url_open = urllib.URLopener()
                url_open = url_open.open(adv_url)
                temp_data = url_open.read()

                ## Get the Advisory year
                adv_year = re.findall("Date:.*; [A-Za-z]{3}/\d\d?/(\d\d\d?\d?)<" \
                                     ".*Installation Requirements:", temp_data)
                if not adv_year:
                    adv_year = re.findall("Date:.*; [A-Za-z]{3}/\d\d?/(\d\d\d?\d?)",\
                                                                      temp_data)
                    if not adv_year:
                        if debug:
                            print "ERROR: Not able to find date in the advisory"+ \
                                                       " page in,\n %s" %(adv_url)
                        continue

                ## Check advisory belongs to user suplied year,
                ## Ignore if it is not same
                if not year == adv_year[0]:
                    if debug:
                        print "Ignoring advisory as it related to %s year " %(adv_year)
                        print adv_url
                    continue

                base_name = adv_url.split('=')[-1]
                file_name = self.html_cache + "solaris" + '_' + \
                                              base_name + '.html'

                ## Download Advisory page, if not downloaded
                if not os.path.isfile(file_name):
                    if debug:
                        print "\nFetching Solaris 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

                ## Create patch Dir, if not exists
                patch_down_path = self.html_cache + '../Solaris_Patches'
                if utils.createDir(patch_down_path, debug):
                    if debug:
                        print "\nPatches will be downloaded in (%s) path " \
                                                         %(patch_down_path)
                else:
                    if debug:
                        print "\nERROR: Error while creating directory..."
                        print "\nPatches will not be downloaded..."

                ## Check patch is downloaded or not,
                ## If not then download
                patch_path = patch_down_path + '/' + adv_id 
                if not os.path.exists(patch_path):
                    ## Download Patch for Advisory
                    try:
                        ## Get working dir and change to patch download path
                        cwd = os.getcwd()
                        os.chdir(patch_down_path)

                        ## Download path and get back to proper path
                        val = self._getSolarisPatch(adv_id, debug)
                        os.chdir(cwd)
                        if not val:
                            if debug:
                                print "ERROR: Error while downloading (%s) "+ \
                                                              "patch " % adv_id
                    except Exception, msg:
                        ## Get back to proper path from patch download path
                        os.chdir(cwd)
                        if debug:
                            print "Exception while downloading (%s) patch " \
                                                                     % adv_id
                            print "Exception: ",msg
                else:
                    print "\n%s patch is already downloaded..." %(adv_id)

                print "##############################################################"

        except Exception, msg:
            print "Exception in : solaris -> Parser(Class) -> fetchHTML "+ \
                                                             "method()", msg
            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 Solaris Security Advisory ID
        """

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

        adv_id_regex =  re.compile('Patch Id:.*(\d{6}\d?\d?-\d{2}\d?\d?)<')

        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
        """

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

        pkg_regex =  re.compile("<title>(.*)</title>")
        pkg = self._findAll(pkg_regex)

        if pkg:
            pkg = pkg[0].strip()
            for i in pkg.split(':'):
                i = i.strip()
                if 'patch' in i or 'Patch' in i:
                    i = i.replace('patch','')
                    i = i.replace('Patch','')
                    package = i.strip()
                    break
                if not i or "SunOS" in i or "sparc" in i or "x86" in i:
                    continue
                elif i.startswith('#'):
                    continue
                else:
                    package = i
                    break 
        if debug:
            print "Affected Packages/RPMS : ", package

        return package


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

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

        description = \
"""The remote host is missing a patch containing a security fix,
  which affects the following component(s): \n  %s
  For more information please visit the below reference link.""" %(packages)

        return description


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

        ## Check "Sun OS Release" is present in the advisory page
        loc = self.Html_content.find('Sun OS Release:')
        if loc <= 0:
            print "Sun OS Release: string is not present"
            return prd_list

        data = self.Html_content[loc:loc+128]

        products = re.findall("Sun OS Release:.*;(\d.*\d)<", data)
        if products:
            products = products[0].strip().split(' ')
            if products:
                for i in products:
                    prd_list.append(i.strip())
        if debug:
            print "\nAffected Product is/are : (%s)" %(prd_list)

        ## Don't include Product/Platform, If not in "os_map" Dict
        ref_list = []
        for prod in prd_list:
            if "x86" in prod:
                arch = "x86"
            else:
                arch = "sparc"
            os_ver = prod.split('_')[0]

            prod = 'solaris_' + os_ver + '_' + arch

            ## Check os_map has product
            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 getPackages(self, prod_list,  patch_id, debug=0):
        """
        Returns OS Package Dictionary
        """

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

        os_pkg_dict = {}
        packages = ""

        patch_path = self.html_cache + '../Solaris_Patches/' + patch_id
        if not os.path.isdir(patch_path):
            if debug:
                print "ERROR: %s Patch not downloaded " %(patch_id)
            return os_pkg_dict

        list_files = os.listdir(patch_path)

        if debug:
            print "List of (%s) files for (%s) Patch " %(list_files, patch_id)

 
        exclude_files = ['OEM_NOTES.fjsv', 'patchinfo', 'LEGAL_LICENSE.TXT']

        for _file in list_files:
            _file = _file.strip()
            if os.path.isdir(patch_path + '/' + _file) and \
               file not in exclude_files and not _file.startswith('README'):
                packages += _file + " "

        if not packages:
            packages = ''
            if debug:
                print "Either Packages or Patch is not present"
        else:
            if debug:
                print "Packages are : ", packages

        for prod in prod_list:
            prod = prod.strip()
            packages = packages.strip()
            if os_map.has_key(prod):
                os_pkg_dict[prod] = [packages, patch_id]

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

        if not os_pkg_dict:
            if debug:
                print "\nERROR: No/different OS found ",

        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 + '/'
       
       main_url = main_url + 'search/document.do?assetkey='
       file_name = file_name.split('_')[-1].strip('.html')

       reference = main_url + file_name

       return reference


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

        try:
            if debug:
                print "Solaris 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 Platform 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.Packages = self.getPackages(self.Platforms, self.AdvID, debug)
            if not self.Packages or self.Packages == '':
                if debug:
                    print "\nERROR: Required Packages not found..."
                return False

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

            self.Description = self.getDescription(self.Product, debug)
            if not self.Description or self.Description == '':
                if debug:
                    print "\nERROR: Description 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
            self.FileName = 'solaris_' + self.AdvID.replace('-','_')

            ## Set XREF
            self.XREF = ["SUNSolve", self.AdvID]

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

            return True

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