#!/usr/bin/python3.11
# ------------------------------------------------------------------------------
# Copyright 2023 - 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
import logging.handlers
from pathlib import Path
import shutil
import configparser
import oes_cert_mgmt_utils

"""
Arguments
1 - operation with below valid values
CERT_CHANGE = 1
RECONFIG = 2
MOVE_TO_EDIR = 4
EDIR_CERT_CHANGE = 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-nrm-reconfig --operation 3 (Change in eDirectory server certificate)
2. oes-cert-mgmt-nrm-reconfig --operation 4 (Move nrm service to eDirectory server certificate)
3. oes-cert-mgmt-nrm-reconfig --operation 1 --certificate /tmp/newcert.pem --cacertificate /tmp/newncacert.pem --privatekey /tmp/newcertprivatekey.pem

"""

EDIR_RSA_SERVER_CERT_PATH = "/etc/ssl/servercerts/servercert.pem"
EDIR_ECDSA_SERVER_CERT_PATH = "/etc/ssl/servercerts/serverECcert.pem"
CERT_CHANGE = 1
RECONFIG = 2
MOVE_TO_EDIR = 4
EDIR_CERT_CHANGE = 3
CONF_FILE_PATH = "/etc/opt/novell/httpstkd.conf"
CRT_SEARCH_STRING = "certfile="
EDIR_ECDSA_SSL_KEY_FILE_PATH = "/etc/ssl/servercerts/serverECkey.pem"
EDIR_RSA_SSL_KEY_FILE_PATH= "/etc/ssl/servercerts/serverkey.pem"
SSL_KEY_SEARCH_STRING="keyfile="
LOG_PATH = "/var/opt/novell/log/oes-cert-mgmt/oes-cert-mgmt.log"

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

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():
    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():
    global certificate, caCertificate, privateKey
    #Operation is mandatory
    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("NRM - Invalid Operation passed")
            exit(1)
    else:
        print("Operation argument is missing")
        logger.info("NRM - 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(filePath, searchString):
    """
    Get the certificate path from the conf file

    """
    certificatePath = ""
    found = False
    try:
        with open(filePath,'r') as file:
            for line in file.readlines():
                line=line.strip()
                if not line.startswith(";") and not line.startswith("#"):
                    if searchString in line:
                        length=len(line)
                        start_index=line.find(searchString)
                        extracted_string= line[start_index:start_index+length]
                        string=(extracted_string.strip())
                        splitting = string.split("=")
                        certificatePath = (splitting[1].strip())
                        certificatePath = (certificatePath.split(' '))
                        certificatePath = (certificatePath[0].strip())
                        found = True

                        """
                        If it is a symlink then we will get the corrsponding value.
                        """
                        if os.path.islink(certificatePath):
                            certificatePath = os.readlink(certificatePath)

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

    except FileNotFoundError as e:
        logger.error("NRM - " + "File " + filePath + " not present")
        exit(1)

    finally:
        if(file != None):
            file.close()

    return certificatePath

def to_check_symlink(path) :
    """
    To search if Certificate File Path is symlink or not.
    """
    return os.path.islink(path)

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 move_to_edir_cert():
    logger.info("NRM - Configuring NRM service to use eDirectory server certificate")

    if(certificatePath == EDIR_ECDSA_SERVER_CERT_PATH):
        logger.info("NRM - NRM services are already using eDirectory ECCert ")
    elif(certificatePath == EDIR_RSA_SERVER_CERT_PATH):
        logger.info("NRM - NRM services are already using RSA Cert ")
    else:
        if(os.path.exists(EDIR_ECDSA_SERVER_CERT_PATH)):
            logger.info("NRM - Reconfiguring NRM service to use eDirectory ECCert")
            """
            it will open conf file and replace the path to edirectory path .
            """
            rewriting_conf_file(CONF_FILE_PATH,CRT_SEARCH_STRING,EDIR_ECDSA_SERVER_CERT_PATH)
            rewriting_conf_file(CONF_FILE_PATH,SSL_KEY_SEARCH_STRING,EDIR_ECDSA_SSL_KEY_FILE_PATH)
            logger.info("NRM - Configured NRM service to use eDirectory server certificate is done.")

        elif(os.path.exists(EDIR_RSA_SERVER_CERT_PATH)):
             logger.info("NRM - Reconfiguring NRM service to use eDirectory RSA Cert")
             """
             it will open conf file and replace the path to edirectory path and restart the service.
             """
             rewriting_conf_file(CONF_FILE_PATH,CRT_SEARCH_STRING, EDIR_RSA_SERVER_CERT_PATH)
             rewriting_conf_file(CONF_FILE_PATH,SSL_KEY_SEARCH_STRING,EDIR_RSA_SSL_KEY_FILE_PATH)
             logger.info("NRM - Reconfigured NRM service to use eDirectory RSA Cert is done.")
        else:
             logger.error("NRM - eDirectory server certificate not found")
             exit(1)

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

    if (certificatePath == EDIR_ECDSA_SERVER_CERT_PATH or certificatePath == EDIR_RSA_SERVER_CERT_PATH):
        logger.info("NRM - the service is either EDIR_ECDSA_SERVER_CERT_PATH or EDIR_ECDSA_SERVER_CERT_PATH ")
    elif(os.path.exists(EDIR_ECDSA_SERVER_CERT_PATH)) and (os.path.exists(EDIR_ECDSA_SSL_KEY_FILE_PATH)):

                privateKeyPath = get_cert_path(CONF_FILE_PATH , 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("NRM - the path of certificate is "+ str(certificatePath) +" .Handling change in eDirectory 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(CONF_FILE_PATH , 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("NRM - the path of certificate is "+ str(certificatePath) +" .Handling change in eDirectory server certificate is done")
    else:
        logger.info("NRM - the service is not using either EDIR_ECDSA_SERVER_CERT_PATH or EDIR_ECDSA_SERVER_CERT_PATH")
        exit(1)

def config_with_new_cert():
    logger.info("NRM - Reconfiguring NRM service with new certificate")

    try:
        if (os.path.exists(certificate)) and (os.path.exists(privateKey)):

            privateKeyPath = get_cert_path(CONF_FILE_PATH , SSL_KEY_SEARCH_STRING)

            if((certificate) == certificatePath and (privateKey) == privateKeyPath):
                logger.info("NRM - NRM service is already using the 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("NRM - the certificate is changed to new certificate")
                else:
                    rewriting_conf_file(CONF_FILE_PATH, CRT_SEARCH_STRING, certificate)
                    rewriting_conf_file(CONF_FILE_PATH, SSL_KEY_SEARCH_STRING, privateKey)
                    logger.info("NRM - Reconfigured service with new certificate")
            else:
                logger.error("NRM - Invalid paths are given")
                logger.info("NRM - certificate value is "+str(certificate)+"privatekey value is"+str(privateKey))
                exit(1)
        else:
            logger.error("NRM - certificate path not found")
            exit(1)

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

def restart_nrm_service():
    try:
        logger.info("NRM - Restarting NRM service")
        os.popen("systemctl restart novell-httpstkd.service")
        logger.debug("NRM - NRM service restarted successfully...")
    except:
        logger.error("NRM - failed to start NRM service")
        exit(1)

def rewriting_conf_file(filePath, searchString,newPath):
    """
    To search for certificate path and replace it with new path.
    """
    found = False
    try:
        with open(filePath,'r') as file:
            for line in file.readlines():
                line=line.strip()
                if not line.startswith(";") and not line.startswith("#"):
                    if searchString in line:
                        length=len(line)
                        start_index=line.find(searchString)
                        extracted_string= line[start_index:start_index+length]
                        string=(extracted_string.strip())
                        splitting = string.split("=")
                        certificatePath = (splitting[1].strip())
                        certificatePath = (certificatePath.split(' '))
                        certificatePath = (certificatePath[0].strip())
                        found = True

                        """
                         If it is a symlink then we will get the corrsponding value.
                        """
                        if os.path.islink(certificatePath):
                            certificate = os.readlink(certificatePath)

                            path = os.readlink(certificatePath)
                            unlink=os.unlink(certificatePath)
                            newlink=os.symlink(newPath, certificatePath)

                        else:
                            data = line.replace(certificatePath,path)
                            files = Path(CONF_FILE_PATH)
                            files.write_text(files.read_text().replace(line, data))

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

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

    finally:
        if(file != None):
            file.close()

def main():

    initialize_logger()
    logger.info("NRM - Reconfiguration of NRM service")

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

    #Parse and validate CLI args

    parse_cli()
    validate_cli_args()

    global certificatePath
    certificatePath = get_cert_path(CONF_FILE_PATH , CRT_SEARCH_STRING)

    if (args.operation == CERT_CHANGE or args.operation == RECONFIG):
        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:
        logger.error("NRM - user entered invalid operation")
        print("Invalid operation")
        exit(1)
    if(args.restart):
        if (args.restart).lower():
            if(args.restart == "yes"):
               restart_nrm_service()
            else:
              exit(0)

    exit()

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