#!/usr/bin/env python

"""
Script which uses code similar to scriptlog to check rss vulnerability feeds
and logs vulnerabilities for critical pieces of software listed. This script
then reports a warning or error until a systems administrator acknowledges
the vulnerability.
"""

################################################################################
#
# Writen By: Scott McCarty
# Date: 8/2009
# Email: scott.mccarty@gmail.com
#
# Copyright (C) 2009 Scott McCarty
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
#################################################################################

# Standard imports
from optparse import OptionParser
import sys
import signal
import re
import syslog
import logging
import socket
import warnings

## Ignore deprication warnings to support a wide range of python versions
warnings.simplefilter('ignore', DeprecationWarning)

socket.setdefaulttimeout(1)

# Special imports
sys.path.append("/usr/local/chev/lib")
import feedparser
from crunchtools import ScriptLog

# Process Signals
## Ignore problems when piping to head
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
## Exit when control-C is pressed
def signal_handler(signal, frame):
	sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)

def version():
	print "Version: 0.5"
	sys.exit(0)

def get_options(argv):
	""" Used to capturoe all of the command line args and perform initializations"""

	# Use global namespace for flags
	global options
	global logging

	# Declarations & Variables
	usage = "usage: %prog [options] [file]"
	parser = OptionParser(usage)

	# Get config elements
	f = open("/usr/local/chev/etc/chev.conf")
	for line in f.readlines():
		exec(line.rstrip())
	f.close()

	# Handle flags
	parser.add_option("-V", "--version", dest="mode", action="store_const", const="version", help="show program's version number and exit")
	parser.add_option("-v", "--verbose", dest="verbose", action="count", help="Show verbose output")
	parser.add_option("-c", "--check", dest="mode", action="store_const", const="check", default="check", help="Check log for security vulnerabilities")
	parser.add_option("-a", "--ack", dest="mode", action="store_const", const="acknowledge", help="Acknowledge all security vulnerabilities")
	parser.add_option("-f", "--file=", dest="file", action="store", type="string", default=file, help="Specify log file to open and check")

	# parse options/args
	(options, args) = parser.parse_args()

	# Pull off file name
	if len(args) > 1:
		print "Too many arguments"
		sys.exit(15)
	if len(args) == 1:
		individual = args[0]
	else:
		individual = "__none__"

	# Set Verbosity
	log_level = logging.WARNING
	if options.verbose == 1:
		log_level = logging.INFO
	elif options.verbose >= 2:
		log_level = logging.DEBUG

	# Set up basic configuration, out to stderr with a reasonable default format.
	logging.basicConfig(level=log_level)

	# Determine mode
	if options.mode == "version":
		version()
	if options.mode == "check":
		check()
	if options.mode == "acknowledge":
		acknowledge(individual)
	else:
		print "No action specified"
		sys.exit()

def check():

	# Log file
	scriptlog = ScriptLog(options.file, "__CkV__")

	 # Critical Software, Warning Software, and Vulnerability Feeds
	warn_software = []
	crit_software = []
	feeds = []

	# Open files
	warnf = open("/usr/local/chev/etc/warn.conf")
	critf = open("/usr/local/chev/etc/crit.conf")
	feedsf = open("/usr/local/chev/etc/feeds.conf")

	# Read each configuration file into arrays. Remove duplicate entries which can
	# be from misconfiguration
	for line in warnf.readlines():
		line = line.rstrip()
		if line not in warn_software:
			warn_software.append(line)

	for line in critf.readlines():
		line = line.rstrip()
		if line not in crit_software:
			crit_software.append(line.rstrip())

	for line in feedsf.readlines():
		line = line.rstrip()
		if line not in feeds:
			feeds.append(line.rstrip())

	# Close files
	warnf.close();
	critf.close();


	# Iterate each feed to gather recent vulnerabilities
	for feed in feeds:
		d = feedparser.parse(feed)

		# Iterate each entry returned by each feed
		for entry in d['entries']:

			entry['title'] = entry['title'].encode("utf-8")
			entry['link'] = entry['link'].encode("utf-8")

			# Iterate each piece of critical software
			for software in crit_software:

				# Check for critcal software
				if re.search(software, entry['title'], re.IGNORECASE):
					logging.info("Checking vulnerability: "+software+" in "+entry['title'])

					key = entry['title']+" "+entry['link']

					if scriptlog.has_entry(key):
						logging.debug("Already Found Entry: "+key)
					else:
						logging.debug("Adding Entry: "+key)
						scriptlog.add_crit(key)

			# Iterate each piece of warning software
			for software in warn_software:

				# Check for warning software
				if re.search(software, entry['title'], re.IGNORECASE):
					logging.info("Checking vulnerability for: "+software+" in "+entry['title'])

					key = entry['title']+" "+entry['link']

					if scriptlog.has_entry(key):
						logging.debug("Already Found Entry: "+key)
					else:
						logging.debug("Adding Entry: "+key)
						scriptlog.add_warn(key)

	# Finally, show unacknowledged entries
	RETVAL = scriptlog.show_unacknowledged()
	sys.exit(RETVAL)

def acknowledge(individual):

	# Log file
	scriptlog = ScriptLog(options.file, "__CkV__")

	if individual == "__none__":

		# Acknowledge all items
		scriptlog.show_unacknowledged()
		scriptlog.acknowledge_all()
	else:
		scriptlog.add_ack(individual)

if __name__ == "__main__":
        get_options(sys.argv[1:])
