#!/usr/bin/python3.11
# ------------------------------------------------------------------------------
# Copyright 2024 Open Text.
#
# The only warranties for products and services of Open Text and its
# affiliates and licensors ("Open Text") are as may be set forth in the
# express warranty statements accompanying such products and services.
# Nothing herein should be construed as constituting an additional
# warranty. Open Text shall not be liable for technical or editorial
# errors or omissions contained herein. The information contained herein
# is subject to change without notice.
#
# Except as specifically indicated otherwise, this document contains
# confidential information and a valid license is required for possession,
# use or copying. If this work is provided to the U.S. Government,
# consistent with FAR 12.211 and 12.212, Commercial Computer Software,
# Computer Software Documentation, and Technical Data for Commercial Items
# are licensed to the U.S. Government under vendor's standard commercial
# license.
# -------------------------------------------------------------------------

import os
import sys
import argparse
import logging
from pathlib import Path
import shutil
import configparser
import logging.handlers
import oes_cert_mgmt_utils
import subprocess
import re

"""
Arguments
1 - operation with below valid values
CERTCHANGE = 1
RECONFIG = 2
MOVETOEDIR = 4
EDIRCERTCHANGE = 3
2 - certificate
3 - cacertificate
4 - privatekey
5 - restart
For operations 3 and 4, no arguments required
operation 3 - Service should reconfigure to use eDirectory Server certificate either servercert.pem Or serverECCert.pem
operation 4 - Indication that eDirectory server certificate has changed. Service should take required measures
operation 1 and 2 - Reconfigure with new certificate, privatekey and cacertificate

Sample usage
1. oes-cert-mgmt-mfa-agent-reconfig --operation 3 (Change in eDirectory server certificate)
2. oes-cert-mgmt-mfa-agent-reconfig --operation 4 (Move mfa-agent service to eDirectory server certificate)
3. oes-cert-mgmt-mfa-agent-reconfig --operation 1 --certificate /tmp/newcert.pem --cacertificate /tmp/newncacert.pem --privatekey /tmp/newcertprivatekey.pem
"""

args = []
EDIR_RSA_SERVER_CERT_PATH = "/etc/ssl/servercerts/servercert.pem"
EDIR_ECDSA_SERVER_CERT_PATH = "/etc/ssl/servercerts/serverECcert.pem"
EDIR_RSA_SSL_KEY_FILE_PATH = "/etc/ssl/servercerts/serverkey.pem"
EDIR_ECDSA_SSL_KEY_FILE_PATH = "/etc/ssl/servercerts/serverECkey.pem"
CONF_FILE_PATH = "/etc/opt/novell/oes-mfa-agent/mfa_agent_conf.yaml"
LOG_PATH = "/var/opt/novell/log/oes-cert-mgmt/oes-cert-mgmt.log"
CRT_SEARCH_STRING = "clientCertPath"
SSL_KEY_SEARCH_STRING="clientCertKeyPath"
CERT_CHANGE = 1
RECONFIG = 2
EDIR_CERT_CHANGE = 3
MOVE_TO_EDIR = 4
certificate = ""
caCertificate = ""
privateKey = ""
privateKeyPath = ""
certificatePath = ""

logger = logging.getLogger('Logger')
logLevel = oes_cert_mgmt_utils.getloglevel()
logger.setLevel(logLevel)

CMD = "mfa-agent-cli print-config"


def initialize_logger():
    """
    Initialize logger.
    """
    rfh = logging.handlers.RotatingFileHandler(LOG_PATH, maxBytes=1024*1024*10, backupCount=2)
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%m/%d/%Y %I:%M:%S')
    rfh.setFormatter(formatter)
    logger.addHandler(rfh)


def parse_cli_args():
    global args
    parser = argparse.ArgumentParser()
    parser.add_argument('--operation', type=int)
    parser.add_argument('--certificate', type=str)
    parser.add_argument('--cacertificate', type=str)
    parser.add_argument('--privatekey', type=str)
    parser.add_argument('--restart', type=str)
    args = parser.parse_args()


def validate_cli_args():
    #Operation is mandatory
    global certificate, caCertificate, privateKey
    if args.operation:
        operation = args.operation
        if(operation != CERT_CHANGE  and operation != RECONFIG and operation != EDIR_CERT_CHANGE and operation != MOVE_TO_EDIR):
            print("Invalid Operation passed")
            logger.info("mfa-agent - Invalid Operation passed")
            exit(1)
    else:
        print("Operation argument is missing")
        logger.info("mfa-agent - Operation argument is missing")
        exit(1)

    #For reconfiguration, new certificate should be passed
    if args.certificate:
        certificate = args.certificate
    if args.cacertificate:
        caCertificate = args.cacertificate
    if args.privatekey:
        privateKey = args.privatekey
    if (args.operation == CERT_CHANGE or args.operation == RECONFIG):
        if len(sys.argv) < 4:
            print("One or more arguments are missing for reconfiguration")
            exit(1)
        if (len(certificate) == 0  or len(caCertificate) == 0 or len(privateKey) == 0):
            print("One or more certificate details of new certificate are missing")
            exit(1)


def get_cert_path(cmd,searchString):
    """
    Get the certificate path from the conf file
    """
    found = False
    certificatePath = ""
    try:
        output = subprocess.check_output(cmd, shell=True)
        output = output.decode("utf-8")
        output_lines = output.split('\n')
        for line in output_lines:
            if not line.strip().startswith("#"):
                if searchString in line:
                    certificatePath = line.split(' - ')[1].strip()
                    logger.debug("mfa-agent - " + str(searchString) + " is set to" + certificatePath + " in mfa-agent conf file")
                    found = True

        if found == False:
            logger.error("mfa-agent - " + str(searchString) + " is not found in conf file")
            exit(1)        

    except FileNotFoundError as e:
        logger.warn("mfa-agent - " + "File " + filePath + " not present")

    return certificatePath


def move_to_edir_cert():
    if(certificatePath == EDIR_ECDSA_SERVER_CERT_PATH):
        logger.info("mfa-agent services are already using eDirectory ECCert")

    elif(certificatePath == EDIR_RSA_SERVER_CERT_PATH):
        logger.info("mfa-agent services are already using eDirectory RSA Cert")

    else:
        if(os.path.exists(EDIR_ECDSA_SERVER_CERT_PATH)):
            logger.info("mfa-agent - Reconfiguring mfa-agent service to use eDirectory ECCert")
            """
            it will open conf file and replace the path to edirectory path.
            """
            command = f"mfa-agent-cli mfa-agent --clientCertPath={EDIR_ECDSA_SERVER_CERT_PATH} --clientCertKeyPath={EDIR_ECDSA_SSL_KEY_FILE_PATH}"
            result = subprocess.run(command, shell=True)
            status = result.returncode
            if(status == 0):
                logger.info("mfa-agent - Configured mfa-agent service to use eDirectory EC certificate")
            else:
                logger.error("mfa-agent - Failed to configure the service to use eDirectory EC certificate")
                exit(1)     

        elif(os.path.exists(EDIR_RSA_SERVER_CERT_PATH)):
            logger.info("mfa-agent - Reconfiguring mfa-agent service to use eDirectory RSA Cert")
            """
            it will open conf file and replace the path to edirectory path.
            """
            command = f"mfa-agent-cli mfa-agent --clientCertPath={EDIR_RSA_SERVER_CERT_PATH} --clientCertKeyPath={EDIR_RSA_SSL_KEY_FILE_PATH}"
            result = subprocess.run(command, shell=True)
            status = result.returncode
            if(status == 0):
                logger.info("mfa-agent - Reconfigured mfa-agent service to use eDirectory RSA Cert")
            else:
                logger.error("mfa-agent - Failed to reconfigure the service to use eDirectory RSA certificate")
                exit(1)      
        else:
            logger.error("mfa-agent - eDirectory server certificate not found")
            exit(1)


def handle_edir_cert_change():
    logger.info("mfa-agent - Handling change in eDirectory server certificate")

    if(certificatePath == EDIR_ECDSA_SERVER_CERT_PATH or certificatePath == EDIR_RSA_SERVER_CERT_PATH):
        logger.info("mfa-agent - service is using either EDIRECCERT or EDIRRSACERT")

    elif(os.path.exists(EDIR_ECDSA_SERVER_CERT_PATH)) and (os.path.exists(EDIR_ECDSA_SSL_KEY_FILE_PATH)):
        privateKeyPath = get_cert_path(CMD,SSL_KEY_SEARCH_STRING)
        backup_file(certificatePath)
        backup_file(privateKeyPath)
        shutil.copy(EDIR_ECDSA_SERVER_CERT_PATH,certificatePath)
        shutil.copy(EDIR_ECDSA_SSL_KEY_FILE_PATH,privateKeyPath)
        logger.info("mfa-agent - the path of certificate is " + str(certificatePath) + ".Handling change in eDirectory EC server certificate is done")

    elif(os.path.exists(EDIR_RSA_SERVER_CERT_PATH)) and (os.path.exists(EDIR_RSA_SSL_KEY_FILE_PATH)):
        privateKeyPath = get_cert_path(CMD,SSL_KEY_SEARCH_STRING)
        backup_file(certificatePath)
        backup_file(privateKeyPath)
        shutil.copy(EDIR_RSA_SERVER_CERT_PATH,certificatePath)
        shutil.copy(EDIR_RSA_SSL_KEY_FILE_PATH,privateKeyPath)
        logger.info("mfa-agent - the path of certificate is " + str(certificatePath) + ".Handling change in eDirectory RSA server certificate is done")

    else:
        logger.info("mfa-agent - service is not using eDirectory certificate")
        exit(1)


def backup_file(filePath):
    #Take backup of certficate being replaced and its private key file
    backup_file_path = filePath + ".cert-mgmt-bak"
    shutil.copy(filePath,backup_file_path)
   
        
def config_with_new_cert():
    logger.info("mfa-agent - Reconfiguring service with new certificate")

    try:
        if(os.path.exists(certificate)) and (os.path.exists(privateKey)):
            privateKeyPath = get_cert_path(CMD,SSL_KEY_SEARCH_STRING)

            if((certificate) == certificatePath and (privateKey) == privateKeyPath):
                logger.info("mfa-agent service is already using same certificate path")

            elif((certificate) != certificatePath and (privateKey) != privateKeyPath):
                if (args.operation == CERT_CHANGE):
                    backup_file(certificatePath)
                    backup_file(privateKeyPath)
                    shutil.copy(certificate, certificatePath)
                    shutil.copy(privateKey, privateKeyPath)
                    logger.info("mfa-agent - certificate is changed into new certificate")
                else:
                    command = f"mfa-agent-cli mfa-agent --clientCertPath={certificate} --clientCertKeyPath={privateKey}"
                    result = subprocess.run(command, shell=True)
                    status = result.returncode
                    if(status == 0):
                        logger.info("mfa-agent - Reconfigured service with new certificate")
                    else:
                        logger.error("mfa-agent - Failed to reconfigure the service with new certificate")
                        exit(1)      
            else:
                logger.error("mfa-agent - Invalid paths are given")
                logger.info("mfa-agent - certificate value is " + str(args.certificate) + " privateKey value is " + str(args.privatekey))
                exit(1)
        else:
            logger.error("mfa-agent - certificate path not found")
            exit(1)

    except FileNotFoundError as e:
        logger.error("mfa-agent - File not present \n" f"{e}")
        exit(1)


def restart_service():
    try:
        logger.info("mfa-agent - Restarting service")
        os.popen("systemctl restart mfa-agent")
        logger.info("mfa-agent - service restarted successfully")

    except:
        logger.error("mfa-agent - failed to restart service")
        exit(1)


def main():
    initialize_logger()
    logger.info("mfa-agent - Reconfiguration of services")

    #Only root can execute
    if (os.geteuid() != 0):
        print("mfa-agent - Only root can reconfigure the services")
        exit(1)

    #Parse and validate CLI args
    parse_cli_args()
    validate_cli_args()

    global certificatePath
    certificatePath = get_cert_path(CMD,CRT_SEARCH_STRING)

    if (args.operation == RECONFIG or args.operation == CERT_CHANGE):
        config_with_new_cert()
    elif (args.operation == MOVE_TO_EDIR):
        move_to_edir_cert()
    elif (args.operation == EDIR_CERT_CHANGE):
        handle_edir_cert_change()
    else:
        logging.error("mfa-agent - Invalid Operation")
        print("Invalid Operation")
        exit(1)

    if (args.restart):
        if (args.restart).lower():
            if (args.restart == "yes"):
               restart_service()
            else:
              exit(0)
    exit()

#Main code starts here
if __name__ == '__main__':
    main()

