#!/usr/bin/env python
# -*- coding: utf-8 -*-

#***************************************************************************
#                             cacheCRLs4FreeRadius.py
#                             -------------------
#    copyright            : (c) 2012 by Robert Penz
#    license		  : GPL v3
#    email                : robert@penz.name
#    version		  : 0.1
#***************************************************************************

"""
    This script does following
    
    1. downloads via HTTP the CRL files (possible other protocols, check what urlib supports)
    2. verifies that files and converts them if needed
    3. moves the files in the CRL directory (so this is atomic)
    4. generates the symlinks if needed
    
    No error means no output - so you can use it via cron and get a mail on error

    usage: cacheCRLs.py [-h]

    -h      print this text
    
    Written by Robert Penz <robert@penz.name>
"""

crlUrls =  [{"name": "rootca", "url": "http://pki.example.com/rootca.crl", "type": "pem"}, 
            {"name": "subca", "url": "http://pki.example.com/subca.crl", "type": "pem"}, 
            {"name": "windowsca", "url": "http://ca.example.local/CertEnroll/WindowsCA.crl", "type": "der"}
           ] 
           
tmpDir = "/var/tmp/cacheCRLs"
crlDir = "/etc/pki/crl/"

opensslCmd = "/usr/bin/openssl crl -inform %(type)s -in %(downloadFile)s -out %(verifiedFile)s"
crehashCmd = "/usr/bin/c_rehash %s" % crlDir

import os
import sys
import getopt
import urllib
import subprocess
import traceback

def logError(e):
    msg = "=========\n"
    # args can be empty
    if e.args:
        if len(e.args) > 1:
            msg += str(e.args) + "\n"
        else:
            msg += e.args[0] + "\n"
    else:
        # print exception class name
        msg += str(e.__class__) + "\n"
    msg += "---------" + "\n"
    msg += traceback.format_exc() + "\n"
    msg += "=========" + "\n"
    return msg

def doCacheCRls():
    """ functions to download, verify, convert and hashing the CRLs """

    # cleanup temp dir (files only)
    for filename in os.listdir(tmpDir):
        filepath = os.path.join(tmpDir, filename)
        if os.path.isdir(filepath):
            os.unlink(filepath)
    
    # download URL and check/convert
    for crl in crlUrls:
        basePath = os.path.join(tmpDir, crl["name"])
        try:
            urllib.urlretrieve(crl["url"], basePath + ".download")
            s = opensslCmd % {"type": crl["type"],  "downloadFile": basePath + ".download", "verifiedFile": basePath + ".verified" }
            retcode = subprocess.call(s.split())
            if retcode:
                # openssl has reported an eror - we don't use that file
                print >>sys.stderr, "Error: Could not valitate/convert %s CRL" % crl["name"]
                continue
            # all ok
            os.rename(basePath + ".verified", os.path.join(crlDir, crl["name"] + ".pem"))
        except Exception, e:
            print >>sys.stderr, "Error: Could not retrieve %s CRL" % crl["name"]
            print >>sys.stderr, logError(e)
    
    # we're done ... hashing 
    # and we need to filter the output ... as c_rehash reports always it works
    
    p = subprocess.Popen(crehashCmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
    (child_stdout, child_stderr) = (p.stdout, p.stderr)
    # result of stdout is not interesting .. we just need to read if to make sure the program is finished
    child_stdout.read()
    # but we've a interest in the stderr ..
    s = child_stderr.read()
    if s:
        print >>sys.stderr, "Error: CRL hashing did not work!\n", s
        sys.exit(3)


# Print usage message and exit
def usage(*args):
    sys.stdout = sys.stderr
    for msg in args: print msg
    print __doc__
    sys.exit(2)

def main():
    
    # parse the command lines
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'h')
    except getopt.error, msg:
        usage(msg)
    
    for o, a in opts:
        if o == '-h':
            print __doc__
            sys.exit()
        # may more options later
        
    doCacheCRls()
    
    
if __name__ == '__main__':
    main()


