#!/usr/bin/env python3

################################################################################
#                                                                              #
#                 This file is part of the Buildbotics firmware.               #
#                                                                              #
#        Copyright (c) 2015 - 2026, Buildbotics LLC, All rights reserved.      #
#                                                                              #
#         This Source describes Open Hardware and is licensed under the        #
#                                 CERN-OHL-S v2.                               #
#                                                                              #
#         You may redistribute and modify this Source and make products        #
#    using it under the terms of the CERN-OHL-S v2 (https:/cern.ch/cern-ohl).  #
#           This Source is distributed WITHOUT ANY EXPRESS OR IMPLIED          #
#    WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS  #
#     FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable    #
#                                  conditions.                                 #
#                                                                              #
#                Source location: https://github.com/buildbotics               #
#                                                                              #
#      As per CERN-OHL-S v2 section 4, should You produce hardware based on    #
#    these sources, You must maintain the Source Location clearly visible on   #
#    the external case of the CNC Controller or other product you make using   #
#                                  this Source.                                #
#                                                                              #
#                For more information, email info@buildbotics.com              #
#                                                                              #
################################################################################

'''Manage the client certificates required to reach the web interface.

Recovery path when a client key is lost or the wrong certificate was
installed: log in over SSH and run "bbctrl-client-auth disable".
'''

import os
import sys
import signal
import argparse
import datetime

from bbctrl import PID_PATH
from bbctrl.ClientAuth import ClientAuth, ClientAuthError


class Log:
    def info(self, msg, *args): print(msg % args)
    def error(self, msg, *args): print(msg % args, file = sys.stderr)
    def exception(self, msg, *args): self.error(msg, *args)


def reload_bbctrl():
    '''Tell a running bbctrl to rebind its listeners.'''
    try:
        with open(PID_PATH, 'r') as f: pid = int(f.read())

        # Check the pid was not recycled, SIGHUP kills most other programs
        with open('/proc/%d/cmdline' % pid, 'rb') as f:
            if b'bbctrl' not in f.read(): raise ProcessLookupError

        os.kill(pid, signal.SIGHUP)

    except (OSError, ValueError):
        print('bbctrl is not running, changes take effect when it starts')


def show(client_auth):
    certs = client_auth.certs()

    print('Client certificate required: %s' %
          ('yes' if client_auth.enabled else 'no'))
    print('Installed certificates: %d' % len(certs))

    for cert in certs:
        expires = datetime.datetime.fromtimestamp(cert['expires'])
        print('  %-24s %s  expires %s%s%s' % (
            cert['name'], cert['fingerprint'], expires.date(),
            '  EXPIRED' if cert['expired'] else '',
            '  CA:TRUE' if cert['ca'] else ''))


def main():
    parser = argparse.ArgumentParser(description = __doc__.split('\n')[0])
    cmds = parser.add_subparsers(dest = 'command', required = True)

    cmds.add_parser('status',  help = 'Show the current setting')
    cmds.add_parser('list',    help = 'List installed certificates')
    cmds.add_parser('enable',  help = 'Require a client certificate')
    cmds.add_parser('disable', help = 'Stop requiring a client certificate')
    cmds.add_parser('add',     help = 'Install a certificate'
                    ).add_argument('file', help = 'PEM certificate file')
    cmds.add_parser('remove',  help = 'Delete a certificate'
                    ).add_argument('fingerprint', help = 'SHA256 fingerprint')

    args = parser.parse_args()
    client_auth = ClientAuth(Log())

    if args.command in ('status', 'list'): return show(client_auth)

    if args.command == 'add':
        with open(args.file, 'r') as f: client_auth.add(f.read())

    elif args.command == 'remove': client_auth.remove(args.fingerprint)
    else: client_auth.set_enabled(args.command == 'enable')

    reload_bbctrl()


if __name__ == '__main__':
    try:
        main()
    except ClientAuthError as e: sys.exit('Error: %s' % e)
    except PermissionError as e: sys.exit('Error: %s\nTry sudo.' % e)
