#!/usr/bin/python3.11
# ------------------------------------------------------------------------------
# Copyright [2023-2025] 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 oes_cert_mgmt_utils
import json
import subprocess


#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-cis-infra-reconfig --operation 3 (Change in eDirectory server certificate)
#3. oes-cert-mgmt-cis-infra-reconfig --operation 1 --certificate /tmp/newcert.pem --cacertificate /tmp/newncacert.pem --privatekey /tmp/newcertprivatekey.pem


#def setupcliargs():
args = []
EDIRECCERT = "/etc/ssl/servercerts/servercert.pem"
CERTCHANGE = 1
RECONFIG = 2
MOVETOEDIR = 4
EDIRCERTCHANGE = 3
certificate = ""
cacertificate = ""
privatekey = ""
CONF_FILE_PATH = "/etc/opt/novell/cis/config"
CRT_SEARCH_STRING = "CERTS_PATH"
SSLKEY_FILE_PATH= "/etc/ssl/servercerts/serverkey.pem"
LOG_PATH = "/var/opt/novell/log/oes-cert-mgmt/oes-cert-mgmt.log"
CA_CERT_PATH = "/etc/opt/novell/certs/SSCert.pem"

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 parsecli():
    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 validatecliargs():
    #Operation is mandatory
    if args.operation:
        operation = args.operation
        if (operation != CERTCHANGE  and operation != RECONFIG and operation != EDIRCERTCHANGE and operation != MOVETOEDIR):
            logger.info("cis infra: Invalid Operation passed")
            exit(200)
    else:
        logger.info("cisinfra: Operation argument is missing")
        exit(200)

    #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 == CERTCHANGE or args.operation == RECONFIG):
        if len(sys.argv) < 4:
            logger.info("One or more arguments are missing for reconfiguration")
            exit(200)

        if (len(args.certificate) == 0  or len(cacertificate) == 0 or len(privatekey) == 0):
            logger.info("One or more certificate details of new certificate are missing")
            exit(200)
def get_cert_path(filePath, searchString):
    """
    Get the certificate path from the conf file
    """
    found = False
    certificatePath = ""
    try:
        with open(filePath,'r') as file:
            for line in file.readlines():
                if not line.strip().startswith("#"):
                    if searchString in line:
                        elements = line.strip().split("=")
                        if len(elements) >= 2:
                            certificatePath = elements[1].split('\t')[-1].split(' ')[-1].strip("\"")
                            found = True
                            break

            if found == False:
                logger.warn ("cisinfra: "+ str(searchString)+ " is not found in conf file")
                exit(200)

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

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


def configwithnewcert():
    logger.info("cisinfra: Reconfiguring cis infra services with new certificate")

    try:
            if (os.path.exists(args.certificate)) and (os.path.exists(args.cacertificate)) and (os.path.exists(args.privatekey)):

                cert_dir = get_cert_path(CONF_FILE_PATH , CRT_SEARCH_STRING)
                privatekeyPath = cert_dir + "/serverkey.pem"
                cacertificatePath = cert_dir + "/rootCAs" + "/SSCert.pem"

                if((args.certificate)==certificatePath  and args.privatekey==privatekeyPath):
                    logger.info("cisinfra: the certificate already exists")
                    exit(200)
                elif((args.certificate)!=certificatePath  and args.privatekey!=privatekeyPath):
                    shutil.copy(args.certificate, certificatePath)
                    shutil.copy(args.privatekey, privatekeyPath)
                    shutil.copy(args.cacertificate, cacertificatePath)

                    if (deployment_type == "infraHA"):
                        password = "changeit"
                    else:
                        config_file="/etc/opt/novell/cis/kafka/config"
                        out = subprocess.Popen("head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo ''", stdout=subprocess.PIPE, shell=True)
                        (output, err) = out.communicate()
                        p_status = out.wait()
                        password = output.decode()
                        password = password.strip("\n")
                    os.system("rm -f /etc/opt/novell/cis/kafka/certs/* > /dev/null 2>&1")
                    javaversion = os.system("/opt/novell/cis/bin/cismigrate --javaversion")
                    cmd = "openssl pkcs12 -export -in /etc/opt/novell/cis/certs/servercert.pem -inkey /etc/opt/novell/cis/certs/serverkey.pem -out /etc/opt/novell/cis/kafka/certs/server.p12 -name localhost -CAfile /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -caname rootca -password pass:"+password+"; > /dev/null 2>&1"
                    os.system(cmd)

                    if (javaversion == 0):
                        cmdimport="keytool -J-Dkeystore.pkcs12.legacy -importkeystore -deststorepass " + password + " -destkeypass " + password + " -destkeystore /etc/opt/novell/cis/kafka/certs/kafka.keystore.jks -srckeystore /etc/opt/novell/cis/kafka/certs/server.p12 -srcstoretype PKCS12 -srcstorepass " + password + " -alias localhost > /dev/null 2>&1"
                        cmdkeystore="keytool -J-Dkeystore.pkcs12.legacy -keystore /etc/opt/novell/cis/kafka/certs/kafka.truststore.jks -storepass " + password + " -alias CARoot -import -file /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -noprompt > /dev/null 2>&1"
                    else:
                        cmdimport="keytool -importkeystore -deststorepass " +  password + " -destkeypass " + password + " -destkeystore /etc/opt/novell/cis/kafka/certs/kafka.keystore.jks -srckeystore /etc/opt/novell/cis/kafka/certs/server.p12 -srcstoretype PKCS12 -srcstorepass " + password + " -alias localhost > /dev/null 2>&1"
                        cmdkeystore="keytool -keystore /etc/opt/novell/cis/kafka/certs/kafka.truststore.jks -storepass " + password + " -alias CARoot -import -file /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -noprompt > /dev/null 2>&1"
                    os.system(cmdimport)
                    os.system(cmdkeystore)
                    os.system("/usr/bin/chmod 0777 /etc/opt/novell/cis/kafka/certs/server.p12")
                    if (deployment_type != "infraHA"):
                        cmd = "sed -i -e\"s/^MQ_KEYSTORE_PLAINTEXT_PASSWORD=.*/MQ_KEYSTORE_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                        os.system(cmd)
                        cmd = "sed -i -e\"s/^MQ_KEY_PLAINTEXT_PASSWORD=.*/MQ_KEY_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                        os.system(cmd)
                        cmd = "sed -i -e\"s/^MQ_TRUSTSTORE_PLAINTEXT_PASSWORD=.*/MQ_TRUSTSTORE_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                        os.system(cmd)
                    logger.info("cisinfra: Reconfiguring cis infra services with new certificate")
                else:
                    logger.error("cisinfra: invalid paths are given")
                    logger.info("cisinfra: certificate value is "+str(args.certificate)+"privatekey value is"+str(args.privatekey))
                    exit(200)
            else:
                logger.error("cisinfra: certificate path not found")

    except FileNotFoundError as e:
        logger.error("cisinfra: File not present \n" f"{e}")

def handleedircertchange():
    logger.info("cisinfa: Handling change in eDirectory server certificate")

    if (certificatePath == EDIRECCERT):
        logger.info("cisinfra: the service is using eDirectory certificate")
    else:
        if(os.path.exists(EDIRECCERT)) and (os.path.exists(SSLKEY_FILE_PATH)) :
            cert_dir = get_cert_path(CONF_FILE_PATH , CRT_SEARCH_STRING)
            privatekeyPath = cert_dir + "/serverkey.pem"
            cacertificatePath = cert_dir + "/rootCAs" + "/SSCert.pem"
            logger.info("cisinfra: private key path " + privatekeyPath)
            shutil.copy(EDIRECCERT, certificatePath)
            shutil.copy(SSLKEY_FILE_PATH, privatekeyPath)
            shutil.copy(CA_CERT_PATH, cacertificatePath)
            if (deployment_type == "infraHA"):
                password = "changeit"
            else:
                config_file="/etc/opt/novell/cis/kafka/config"
                out = subprocess.Popen("head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo ''", stdout=subprocess.PIPE, shell=True)
                (output, err) = out.communicate()
                p_status = out.wait()
                password = output.decode()
                password = password.strip("\n")

            os.system("rm -f /etc/opt/novell/cis/kafka/certs/* > /dev/null 2>&1")
            javaversion = os.system("/opt/novell/cis/bin/cismigrate --javaversion")
            cmd = "openssl pkcs12 -export -in /etc/opt/novell/cis/certs/servercert.pem -inkey /etc/opt/novell/cis/certs/serverkey.pem -out /etc/opt/novell/cis/kafka/certs/server.p12 -name localhost -CAfile /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -caname rootca -password pass:"+password+"; > /dev/null 2>&1"
            os.system(cmd)

            if (javaversion == 0):
                cmdimport="keytool -J-Dkeystore.pkcs12.legacy -importkeystore -deststorepass " + password + " -destkeypass " + password + " -destkeystore /etc/opt/novell/cis/kafka/certs/kafka.keystore.jks -srckeystore /etc/opt/novell/cis/kafka/certs/server.p12 -srcstoretype PKCS12 -srcstorepass " + password + " -alias localhost > /dev/null 2>&1"
                cmdkeystore="keytool -J-Dkeystore.pkcs12.legacy -keystore /etc/opt/novell/cis/kafka/certs/kafka.truststore.jks -storepass " + password + " -alias CARoot -import -file /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -noprompt > /dev/null 2>&1"
            else:
                cmdimport="keytool -importkeystore -deststorepass " +  password + " -destkeypass " + password + " -destkeystore /etc/opt/novell/cis/kafka/certs/kafka.keystore.jks -srckeystore /etc/opt/novell/cis/kafka/certs/server.p12 -srcstoretype PKCS12 -srcstorepass " + password + " -alias localhost > /dev/null 2>&1"
                cmdkeystore="keytool -keystore /etc/opt/novell/cis/kafka/certs/kafka.truststore.jks -storepass " + password + " -alias CARoot -import -file /etc/opt/novell/cis/certs/rootCAs/SSCert.pem -noprompt > /dev/null 2>&1"
            os.system(cmdimport)
            os.system(cmdkeystore)

            os.system("/usr/bin/chmod 0777 /etc/opt/novell/cis/kafka/certs/server.p12")
            if (deployment_type != "infraHA"):
                cmd = "sed -i -e\"s/^MQ_KEYSTORE_PLAINTEXT_PASSWORD=.*/MQ_KEYSTORE_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                os.system(cmd)
                cmd = "sed -i -e\"s/^MQ_KEY_PLAINTEXT_PASSWORD=.*/MQ_KEY_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                os.system(cmd)
                cmd = "sed -i -e\"s/^MQ_TRUSTSTORE_PLAINTEXT_PASSWORD=.*/MQ_TRUSTSTORE_PLAINTEXT_PASSWORD=\\\"" + password + "\\\"/\" " + config_file
                os.system(cmd)
            logger.info("cisinfra: the path of certificate is "+ str(certificatePath) +". Handling change in eDirectory server certificate")
        else:
            logger.error("cisinfra: eDirectory server certificate not found")

def getdeploymenttype():
    if os.path.exists('/etc/opt/novell/cis/configurationStatus.json'):
        configStatusjson = open('/etc/opt/novell/cis/configurationStatus.json')
        data = json.load(configStatusjson)
        configStatusjson.close()
        deploymenttype = data['configType']
        logger.info("cisinfra: configure type %s", deploymenttype)
        if "k3s" in data :
            isK3s = data['k3s']
            logger.info("cisinfra: is k3s %s", isK3s)
            return deploymenttype, isK3s
        else:
            return deploymenttype, ""
    else:
        return "", ""

def handlerestartcisinfraservice():
    deploymenttype, isK3s = getdeploymenttype()
    try:
        logger.info("cisinfra: Restarting cis infra services")
        if (deploymenttype == "standalone") :
            os.popen("systemctl restart oes-cis-kafka.service")
            os.popen("systemctl restart oes-cis-zk.service")
            os.popen("systemctl restart oes-cis-opensearch.service")
            logger.debug("cisinfra: cis infra services restarted successfully...")
        elif (deploymenttype == "infraHA" and isK3s == "true") :
            os.popen("systemctl restart k3s.service")
            logger.debug("cisinfra: k3s service restarted successfully...")
        elif (deploymenttype == "infraHA") :
            os.popen("systemctl restart docker.service")
            logger.debug("cisinfra: docker service restarted successfully...")
  
    except :
        logger.error("cisinfra: failed to start cis infra service")

def main():
    initialize_logger()
    logger.info("cisinfra: Reconfiguration of cis infra services")

    #Only root can execute
    if(os.geteuid() != 0):
        logger.info("cisinfra: Only root can reconfigure the services")
        exit(200)

    #Parse and validate CLI args
    parsecli()
    validatecliargs()

    global certificatePath, deployment_type
    certificatePath = get_cert_path(CONF_FILE_PATH, CRT_SEARCH_STRING)
    certificatePath = certificatePath + "/servercert.pem"

    deployment_type, isk3s = getdeploymenttype()

    if (args.operation == RECONFIG or args.operation == MOVETOEDIR):
        logger.error("cisinfra: cert RECONFIG and MOVETOEDIR is not valid for cis infra services")
        exit(205)
    elif (args.operation == CERTCHANGE):
        if (deployment_type == "standalone" or deployment_type == "infraHA"):
            configwithnewcert()
        else :
            logger.error("cisinfra: reconfigure is not applicable for this server")
    elif(args.operation == EDIRCERTCHANGE):
        handleedircertchange()
    else:
        logger.error("cisinfra: user entered invalid operation")
        exit(200)
    if(args.restart):
        if (args.restart).lower():
            if(args.restart == "yes"):
               handlerestartcisinfraservice()
            else:
              exit(0)

    exit(0)


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

