#!/usr/bin/python

import logging
import optparse
import sys
import time
from cam import config


USAGE = '''cam [<OPTIONS>] <COMMAND> [<ARG>...]
CAM v%(version)s - (c)2012-2014 by <ale@incal.net>
Minimal X509 Certification Authority management tool.

Known commands:

  init [<RSA_CRT>]
    Initialize the environment and create a new CA certificate
    (you can also import an existing certificate)

  gen <TAG>...
    Create (or re-create) the certificates corresponding
    to TAG

  gencrl
    Update the CRL

  list
    List all known certificates

  verify FILES...
    Verify the certificates found in FILES against the CA

  fp [<TAG>...]
    Print SHA1/MD5 fingerprints of certificates

  files <TAG>...
    Dump all the certificate-related files of this TAG

  check
    Should be run weekly from a cron job to warn you if some
    certificates are about to expire (controlled by the 'warning_days'
    parameter in the 'global' section of the configuration)

The configuration file consists of a ini-style file, with a 'ca'
section that specifies global CA parameters, and more sections for
each tag with certificate-specific information. See the documentation
for more details on how to write your own configuration.

Run `cam --help' to get a list of available command-line options.

''' % {'version': '2.1'}


def find_cert(certs, name):
    for c in certs:
        if c.name == name:
            return c
    raise Exception('Certificate "%s" not found' % name)


def cmd_init(global_config, ca, certs, args):
    ca.create()


def cmd_gen(global_config, ca, certs, args):
    if len(args) < 1:
        print 'Nothing to do.'
    for tag in args:
        ca.generate(find_cert(certs, tag))


def cmd_gencrl(global_config, ca, certs, args):
    ca.gencrl()


def cmd_files(global_config, ca, certs, args):
    if len(args) < 1:
        print 'Nothing to do.'
    for tag in args:
        c = find_cert(certs, tag)
        print c.public_key_file
        print c.private_key_file


def cmd_list(global_config, ca, certs, args):
    now = time.time()
    for cert in sorted(certs, key=lambda x: x.name):
        expiry = cert.get_expiration_date()
        state = 'OK'
        expiry_str = ''
        if not expiry:
            state = 'MISSING'
        else:
            if expiry < now:
                state = 'EXPIRED'
            expiry_str = time.strftime('%Y/%m/%d', time.gmtime(expiry))
        print cert.name, cert.cn, state, expiry_str


def cmd_verify(global_config, ca, certs, args):
    if len(args) < 1:
        print 'Nothing to do.'
    failed = False
    for path in args:
        if not ca.verify(path):
            print '%s: FAIL' % path
            failed = True
        else:
            print '%s: OK' % path
    return failed


def cmd_fingerprint(global_config, ca, certs, args):
    if len(args) > 0:
        certs = [find_cert(certs, x) for x in args]
    for cert in certs:
        print cert.name, cert.cn
        print '  SHA1:', cert.get_fingerprint('sha1')
        print '  MD5:', cert.get_fingerprint('md5')


def cmd_check(global_config, ca, certs, args):
    now = time.time()
    warning_time = 86400 * int(global_config.get('warning_days', 15))
    retval = 0
    for cert in certs:
        exp = cert.get_expiration_date()
        if exp and (exp - now) < warning_time:
            print '%s (%s) is about to expire.' % (cert.name, cert.cn)
            retval = 1
    return retval


cmd_table = {
    'init': cmd_init,
    'gen': cmd_gen,
    'gencrl': cmd_gencrl,
    'files': cmd_files,
    'list': cmd_list,
    'verify': cmd_verify,
    'fp': cmd_fingerprint,
    'fingerprint': cmd_fingerprint,
    'check': cmd_check,
}


def main():
    parser = optparse.OptionParser(usage=USAGE)
    parser.add_option('-d', '--debug', dest='debug', help='Be verbose',
                      action='store_true')
    parser.add_option('-c', '--config', dest='config', help='Config file')
    opts, args = parser.parse_args()

    if len(args) > 0 and args[0] == 'help':
        parser.print_help()
        return 0
    if not opts.config:
        parser.error('Must specify --config')
    if len(args) < 1:
        parser.error('Must specify a command')

    logging.basicConfig(
        format='cam: %(levelname)s: %(message)s',
        level=logging.DEBUG if opts.debug else logging.INFO)

    try:
        global_config, ca, certs = config.read_config(opts.config)
        try:
            cmd, args = args[0], args[1:]
            if cmd not in cmd_table:
                parser.error('unknown command "%s"' % cmd)
            cmdfn = cmd_table[cmd]
            return cmdfn(global_config, ca, certs, args)
        finally:
            ca.close()
    except Exception as e:
        if opts.debug:
            logging.exception(e)
        else:
            logging.error(e)
        return 1


def main_wrapper():
    try:
        return main()
    except Exception:
        logging.exception('uncaught exception')
        return 1


if __name__ == '__main__':
    sys.exit(main_wrapper())
