#!/usr/bin/python
# 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 3 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, see .
""" cas-admin - fingerprinting utility for cas
"""
import os
import ConfigParser
import optparse
import sys
import urlparse
import datetime
import calendar
import paramiko
from cas.core import CoreBase
from cas.util import UtilBase, Logging
from cas.rpmutils import RPMBase
from cas.db import CasStorage, CasStorageException
from cas.cas_subprocess import Popen, PIPE
from cas.cas_shutil import rmtree
if sys.version_info[:2] < (2,3):
raise SystemExit("Python >= 2.3 required")
# Read in configuration
config = ConfigParser.ConfigParser()
config.read("/etc/cas.conf")
settings = {}
if config.has_section("settings"):
for opt, val in config.items("settings"):
settings[opt.upper()] = val
# read maintenance options
PURGELIMIT = config.get("maintenance","purgeLimit")
AUTOPURGE = config.getboolean("maintenance","autoPurge")
# advance options
BUFFERSIZE=None
if config.has_option("advanced", "buffersize"):
BUFFERSIZE=config.get("advanced", "buffersize")
class CasDatabaseHandler(object):
def __init__(self, logger):
self.casLog = logger
self.util = UtilBase()
# setup database connection
self.first_run = False
if not os.path.isfile(settings["DATABASE"]):
self.first_run = True
self.db = CasStorage(settings["DATABASE"])
self.db.connect()
if self.first_run:
self.db.buildTable()
def run(self):
# Uses emacs regex -- see `man find`
# TODO: rework kernel filtering to expand scope
# beyond just debuginfo rpms.
cmd = ["find", "-L", settings["KERNELS"], "-iregex", settings["RPMFILTER"]]
pipe = Popen(cmd, stdout=PIPE, stderr=PIPE)
# setup count for kernels found, mainly for console output
count = 0
# create list of rpms from `cmd`
for line in pipe.stdout:
self.db.addDebuginfoRPM(line.strip())
self.casLog.status("(found) %-5d kernel(s)" % (count,))
count = count + 1
# query database for debuginfo rpms
rpms = self.db.getAllDebuginfoRPM()
for id, rpm in rpms:
# temporary storage path in form of DEBUGS/COUNT
dst = os.path.join(settings["DEBUGS"], str(count))
if not os.path.isdir(dst):
os.makedirs(dst)
rpmTool = RPMBase()
self.casLog.status("(extracting) %-50s" % (os.path.basename(rpm),))
results = rpmTool.extract(rpm, dst)
# Sort through extracted debug for each type
# e.g. hugemem, PAE, smp, largesmp
for item in results:
vmlinux = item.strip()
stamper = CoreBase()
debugKernel = os.path.normpath(vmlinux)
timestamp = stamper.timestamp(debugKernel, BUFFERSIZE)
# add rpm id, debug, timestamp to database
self.db.addTimestamp(id, debugKernel, timestamp)
# Cleanup extracted debugs
rmtree(dst)
self.casLog.info("Timestamp database built.")
return
class CasServerHandler(object):
def __init__(self, logger):
self.casLog = logger
self.util = UtilBase()
# setup database connection
self.first_run = False
if not os.path.isfile(settings["DATABASE"]):
self.first_run = True
self.db = CasStorage(settings["DATABASE"])
self.db.connect()
if self.first_run:
self.db.buildTable()
def run(self):
host = None
port = 22
hostname_count = 0
ssh_obj = paramiko.SSHClient()
ssh_obj.load_host_keys(os.path.expanduser("~/.ssh/known_hosts"))
for server in ssh_obj.get_host_keys().keys():
server = server.strip()
# here we test for ports and apply accordingly
if ':' in server:
server, port = server.split(':')
# ssh keys place [host] when defining servers with ports
server = server[1:-1]
try:
ssh_obj.connect(server, port=port, username=settings["CASUSER"])
except paramiko.AuthenticationException, e:
raise SystemExit(self.casLog.debug("Failed to connect to %s: %s" % (server,e)))
stdin, stdout, stderr = ssh_obj.exec_command("/bin/uname -m")
if stderr:
self.casLog.debug(stderr)
# clean up arch string
for i in stdout.readlines():
arch = i.strip()
self.db.addServer(server, port, arch)
hostname_count = hostname_count + 1
self.casLog.info("Server database built with %d server(s) added." % (hostname_count,))
return
class PurgeHandler(object):
def __init__(self, purgeDays, logger):
self.purgeDataDays = purgeDays
self.casLog = logger
self.util = UtilBase()
self.todaysDate = datetime.date.today()
def run(self):
# create date of timedelta
cutOffDate = self.todaysDate - datetime.timedelta(days=self.purgeDataDays)
self.casLog.debug(cutOffDate)
# Start of purging data
for root, dirs, files in os.walk(settings["WORKDIRECTORY"]):
for d in dirs:
dirpath = os.path.join(root,d)
# pull out date from directory structure and trim it to
# (year, month, day)
searchDate = self.util.regexSearch('(\d{4})\.(\d+)\.(\d+)', dirpath)
if searchDate:
self.casLog.debug('found %s' % (searchDate,))
(year, month, day) = searchDate.split('.')
# create our datetime object so we can do some arithmetic
dirDate = datetime.date(int(year), int(month), int(day))
self.casLog.debug(dirDate)
if dirDate < cutOffDate:
self.casLog.debug('Should purge old directories, %s' % (dirpath,))
rmtree(dirpath)
class CasAdminApplication(object):
def __init__(self, args):
self.parse_options(args)
self.casLog = Logging("/var/log","cas-admin", settings["DEBUGLEVEL"])
def parse_options(self, args):
parser = optparse.OptionParser(usage="cas-admin [opts] args")
parser.add_option("-b","--build", dest="buildDB",
help="Build CAS DB", action="store_true",
default=False)
parser.add_option("-s","--server", dest="server_init",
help="Build SERVER DB", action="store_true",
default=False)
parser.add_option("-p","--purge", dest="purgeData",
help="Purge files default 90 days, customize with -d",
action="store_true", default=False)
parser.add_option("-d","--days", dest="purgeDataDays",
help="Set how many days back to purge data")
(self.opts, args) = parser.parse_args()
self.buildDB = self.opts.buildDB
self.server_init = self.opts.server_init
self.purgeData = self.opts.purgeData
self.purgeDataDays = self.opts.purgeDataDays
def run(self):
""" Make sure necessary directories and configuration is setup
prior to running the fingerprint
"""
if os.getuid() is not 0:
raise RuntimeError, "You must be root(0), instead you are id(%d)" % (os.getuid())
if not os.path.isdir(os.path.dirname(settings["DATABASE"])):
os.makedirs(os.path.dirname(settings["DATABASE"]))
if not os.path.isdir(settings["DEBUGS"]):
os.makedirs(settings["DEBUGS"])
# if autopurge is enabled lets clean up some stale data
if AUTOPURGE:
self.casLog.debug('Autopurge enabled, purging stale data')
purgeHandler = PurgeHandler(int(PURGELIMIT), self.casLog).run()
if self.purgeData:
ans = raw_input("You are about to purge data, is this what you " \
"really want to do? [Y/y/N/n]: ")
if ans=='Y' or ans=='y':
if not self.purgeDataDays:
self.purgeDataDays = PURGELIMIT
self.casLog.info("Beginning Purge going back %s day(s)" % (self.purgeDataDays,))
purgeHandler = PurgeHandler(int(self.purgeDataDays), self.casLog).run()
raise SystemExit(self.casLog.info("Purge finished"))
else:
raise SystemExit(self.casLog.info("Purge cancelled"))
if self.buildDB:
self.casLog.info("Starting CAS DB instance.")
dbHandler = CasDatabaseHandler(self.casLog).run()
elif self.server_init:
self.casLog.info("Building CAS Server DB instance.")
if not os.path.isfile(os.path.expanduser("~/.ssh/known_hosts")):
raise SystemExit(self.casLog.info("Unable to read ssh hosts keys, " \
"please make sure ssh client is configured properly"))
else:
serverHandler = CasServerHandler(self.casLog).run()
else:
raise SystemExit(self.casLog.info("Missing options, please run with --help."))
if __name__=="__main__":
app = CasAdminApplication(sys.argv[1:])
sys.exit(app.run())