#!/usr/bin/env python3
"""certpeek - check the TLS certificate a host actually serves.

Usage: certpeek [--warn DAYS] [--port N] [--timeout S] host[:port] ...

Exit status: 0 all OK, 1 at least one host expires within --warn days,
2 at least one host could not be checked.
"""
import argparse, socket, ssl, sys
from datetime import datetime, timezone

__version__ = "0.4.0"


def peek(host, port, timeout):
    ctx = ssl.create_default_context()
    with socket.create_connection((host, port), timeout=timeout) as sock:
        with ctx.wrap_socket(sock, server_hostname=host) as tls:
            cert = tls.getpeercert()
            version = tls.version()
    not_after = datetime.fromtimestamp(ssl.cert_time_to_seconds(cert["notAfter"]), timezone.utc)
    issuer = dict(x[0] for x in cert["issuer"])
    name = issuer.get("organizationName", "?")
    if issuer.get("commonName"):
        name += " " + issuer["commonName"]
    return not_after, name, version


def main(argv=None):
    ap = argparse.ArgumentParser(prog="certpeek", description=__doc__.split("\n")[0])
    ap.add_argument("hosts", nargs="+")
    ap.add_argument("--warn", type=int, default=14, help="warn if fewer days left (default 14)")
    ap.add_argument("--port", type=int, default=443)
    ap.add_argument("--timeout", type=float, default=5.0)
    ap.add_argument("-q", "--quiet", action="store_true", help="only print problems")
    ap.add_argument("--version", action="version", version=__version__)
    a = ap.parse_args(argv)

    status = 0
    width = max(len(h) for h in a.hosts)
    for spec in a.hosts:
        host, _, port = spec.partition(":")
        try:
            not_after, issuer, version = peek(host, int(port or a.port), a.timeout)
        except (OSError, ssl.SSLError) as e:
            print(f"{spec:<{width}}  FAIL  {e}")
            status = 2
            continue
        days = (not_after - datetime.now(timezone.utc)).days
        state = "OK" if days >= a.warn else "WARN"
        if state == "WARN":
            status = max(status, 1)
        if state != "OK" or not a.quiet:
            print(f"{spec:<{width}}  {state:<4}  {days:>3} days  ({issuer}, {version})")
    return status


if __name__ == "__main__":
    sys.exit(main())
