#!/bin/bash
#
# Automated Let's Encrypt/Certbot certificate request and deploy
# Copyright (c) 2009-2025 by SIP-Online
# https://www.sip-online.fr
#

CURRENT_PATH=$(realpath "$0")
LOG_PATH="/var/log"
ZCSP_OS_USER="zimbra"
ZCSP_OS_GROUP="zimbra"
ZCSP_PATH="/opt/zimbra"
ZCSPCONF_PATH="$ZCSP_PATH/conf"
ZCSPNGINX_TEMPLATES_PATH="$ZCSPCONF_PATH/nginx/templates"
ZCSPSSL_PATH="$ZCSP_PATH/ssl"
ZCSPSSL_ZM_PATH="$ZCSPSSL_PATH/zimbra"
ZCSPLETSENCRYPT_PATH="$ZCSPSSL_PATH/letsencrypt"
ZCSPLETSENCRYPT_DOMAINS_LIST_PATH="$ZCSPLETSENCRYPT_PATH/domains.txt"
ZCSPLETSENCRYPT_ROOT_CAS_PATH="$ZCSPLETSENCRYPT_PATH/root_cas"
ZCSPLETSENCRYPT_DEPLOY_PATH="$ZCSPLETSENCRYPT_PATH/deploy"
CERTBOT_STG="0"
CERTBOT_DIR_PATH="/opt/certbot"
CERTBOT_BIN_PATH="$CERTBOT_DIR_PATH/bin/certbot"
CERTBOT_CONFIG_PATH="/etc/letsencrypt/zcsplus"
CERTBOT_WORK_PATH="/var/lib/letsencrypt/zcsplus"
CERTBOT_LOGS_PATH="$LOG_PATH/letsencrypt/zcsplus"
CERTBOT_WEBROOT_PATH="/opt/zimbra/data/nginx/letsencrypt"
CERTBOT_CRON_PATH="/etc/cron.daily/zcsp-letsencrypt"

# Info: https://letsencrypt.org/certificates/
# Subject: O = Internet Security Research Group, CN = ISRG Root X2
# Key type: ECDSA P-384
# Validity: until 2035-09-04 (generated 2020-09-04)
CERTBOT_PDT_ROOT_URL="https://letsencrypt.org/certs/isrg-root-x2.pem"
# Info: https://letsencrypt.org/docs/staging-environment/
# Pretend Pear X1
# Subject: O = (STAGING) Internet Security Research Group, CN = (STAGING) Pretend Pear X1
# Key type: RSA 4096
CERTBOT_STG_ROOT_URL="https://letsencrypt.org/certs/staging/letsencrypt-stg-root-x1.pem"

if [ "$CERTBOT_STG" = "1" ]; then
	CERTBOT_PC=("(STAGING) Pretend Pear X1" --staging)
else
	CERTBOT_PC=("ISRG Root X2")
fi

Message_Print() {
	echo -e "### $1 ###"

	if [ -n "${2-}" ]; then
		exit 1;
	fi
}

ZCSPlus_Check_Certbot() {
	if [ ! -f "$CERTBOT_BIN_PATH" ]; then
		Message_Print "[ERROR]\nCertbot in the Ubuntu repositories is too old and cannot be used for ZCSPlus. The newer version can be installed via snap or pip. Run below commands to install Cerbot and obtain a certificate:\napt update\napt install -y python3 python3-venv\npython3 -m venv $CERTBOT_DIR_PATH\n$CERTBOT_DIR_PATH/bin/pip install --upgrade pip\n$CERTBOT_DIR_PATH/bin/pip install --upgrade certbot\n" 1
	fi
}

ZCSPlus_Hostname() {
	ZMHN=$(su - $ZCSP_OS_USER -c "echo -n \$(zmhostname)")
}

ZCSPlus_Proxy_Restart() {
	Message_Print "[ZCSPlus] NGINX Proxy Restart"

	su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmproxyctl restart"
}

ZCSPlus_NGINX_Patch() {
	NCWHMRT_PATH="$ZCSPNGINX_TEMPLATES_PATH/nginx.conf.web.http.mode-redirect.template"

	if [ ! -d "$CERTBOT_WEBROOT_PATH" ]; then
		mkdir -m 0755 -p "$CERTBOT_WEBROOT_PATH"
		chown $ZCSP_OS_USER:$ZCSP_OS_GROUP "$CERTBOT_WEBROOT_PATH"
	fi

	if [ -f "$NCWHMRT_PATH" ]; then
		ZNP_CHECK=$(grep "zcsp_no_redirect" "$NCWHMRT_PATH")

		if [ ! "$ZNP_CHECK" ]; then
			Message_Print "[NGINX Patch] $NCWHMRT_PATH"

			sed -i "
				s|^[[:blank:]]*\$||;
				s|\(set \$sslhost \$host;\)|\1\n\t\t\tset \$zcsp_no_redirect 0;\n\n\t\t\tif (\$request_uri ~* \"^/\.well-known/acme-challenge/\") {\n\t\t\t\tset \$zcsp_no_redirect 1;\n\t\t\t}|;
				s|\(rewrite ^/(.*)\$ https://\$sslhost/\$1 redirect;\)|if (\$zcsp_no_redirect = 0) {\n\t\t\t\t\1\n\t\t\t}\n\n\t\t\tlocation ~* ^/\.well-known/acme-challenge/ {\n\t\t\t\troot $CERTBOT_WEBROOT_PATH;\n\t\t\t\tallow all;\n\t\t\t\ttry_files \$uri =404;\n\t\t\t}|;
			" "$NCWHMRT_PATH"
			chmod 0644 "$NCWHMRT_PATH"
			chown $ZCSP_OS_USER:$ZCSP_OS_GROUP "$NCWHMRT_PATH"

			su - $ZCSP_OS_USER -c "zmprov ms $ZMHN zimbraReverseProxyMailMode redirect"

			ZCSPlus_Proxy_Restart
		else
			Message_Print "Template \"$NCWHMRT_PATH\" is already patched."
		fi
	else
		Message_Print "[ERROR] Template \"$NCWHMRT_PATH\" could not be found." 1
	fi
}

ZCSPlus_Add_Domains_Certbot() {
	CERTBOT_DOMAINS=""
	CERTBOT_CERTIFICATES=$($CERTBOT_BIN_PATH certificates --config-dir "$CERTBOT_CONFIG_PATH" --work-dir "$CERTBOT_WORK_PATH" --logs-dir "$CERTBOT_LOGS_PATH")
	ZMHOSTNAME_GET=$(echo "$CERTBOT_CERTIFICATES" | grep " $ZMHN")
	DOMAINS_LIST="$ZMHN"

	#if [ ! "$ZMHOSTNAME_GET" ]; then
	# Disabling SNI support.
	if [ "1" = "1" ]; then
		Message_Print "Added Hostname \"$ZMHN\" to \"Certbot\" list."
		CERTBOT_DOMAINS+=" -d $ZMHN"
	else
		Message_Print "Hostname \"$ZMHN\" is already enabled in \"Certbot\"."
	fi

	for GAD_DOMAIN in $(su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmprov gad"); do {
		GDI_ATTR="zimbraPublicServiceHostname"
		GDI_DOMAIN=$(su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmprov gdi virtualHostname $GAD_DOMAIN $GDI_ATTR" | grep "$GDI_ATTR: ")

		if [ "$GDI_DOMAIN" ]; then
			GDI_VALUE=$(echo $GDI_DOMAIN | cut -f 2 -d ":" | tr -d "[:space:]")
			GDI_DOMAIN_GET=$(echo "$CERTBOT_CERTIFICATES" | grep " $GDI_VALUE")
			DOMAINS_LIST+=" $GAD_DOMAIN"

			#if [ ! "$GDI_DOMAIN_GET" ]; then
			# Disabling SNI support.
			if [ "1" = "1" ]; then
				Message_Print "Added Domain \"$GDI_VALUE\" to \"Certbot\" list."
				CERTBOT_DOMAINS+=" -d $GDI_VALUE"
			else
				Message_Print "Domain \"$GDI_VALUE\" is already enabled in \"Certbot\"."
			fi
		else
			Message_Print "[ERROR] $GAD_DOMAIN domain does not contain any value in \"$GDI_ATTR\" attribute, please provide a Hostname for your mail connections. For example: mail.$GAD_DOMAIN or $GAD_DOMAIN" 1
		fi
	}
	done

	if [ "$CERTBOT_DOMAINS" ]; then
		ADMIN_ACCOUNT_GET=$(su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmprov gaaa | head -n 1")

		if [ -d "$CERTBOT_CONFIG_PATH" ]; then
			rm -r "$CERTBOT_CONFIG_PATH"
		fi

		if [ -d "$CERTBOT_WORK_PATH" ]; then
			rm -r "$CERTBOT_WORK_PATH"
		fi

		if [ -d "$CERTBOT_LOGS_PATH" ]; then
			rm -r "$CERTBOT_LOGS_PATH"
		fi

		$CERTBOT_BIN_PATH certonly --config-dir "$CERTBOT_CONFIG_PATH" --work-dir "$CERTBOT_WORK_PATH" --logs-dir "$CERTBOT_LOGS_PATH" --preferred-chain "${CERTBOT_PC[@]}" --email "$ADMIN_ACCOUNT_GET" --agree-tos --non-interactive --deploy-hook "$CURRENT_PATH \"deploy-cert\"" --webroot -w "$CERTBOT_WEBROOT_PATH"$CERTBOT_DOMAINS
		echo -n "$DOMAINS_LIST" > "$ZCSPLETSENCRYPT_DOMAINS_LIST_PATH"
		chmod 0644 "$ZCSPLETSENCRYPT_DOMAINS_LIST_PATH"
		chown $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_DOMAINS_LIST_PATH"

		if [ -f "$CERTBOT_CRON_PATH" ]; then
			rm "$CERTBOT_CRON_PATH"
		fi

		echo -e "#!/bin/sh\n#\n\n$CERTBOT_BIN_PATH renew --config-dir \"$CERTBOT_CONFIG_PATH\" --work-dir \"$CERTBOT_WORK_PATH\" --logs-dir \"$CERTBOT_LOGS_PATH\" --deploy-hook \"$CURRENT_PATH \\\"deploy-cert\\\"\" > \"$LOG_PATH/cron-$ZMHN-renew-cert.log\" 2>&1\n\n# Testing deploy-hook with a real renewal\n#$CERTBOT_BIN_PATH renew --config-dir \"$CERTBOT_CONFIG_PATH\" --work-dir \"$CERTBOT_WORK_PATH\" --logs-dir \"$CERTBOT_LOGS_PATH\" --force-renewal --deploy-hook \"$CURRENT_PATH \\\"deploy-cert\\\"\" > \"$LOG_PATH/cron-$ZMHN-renew-cert.log\" 2>&1" > "$CERTBOT_CRON_PATH"
		chmod 0755 "$CERTBOT_CRON_PATH"
	fi
}

ZCSPlus_Deploy_Certbot() {
	if [ -z "$RENEWED_LINEAGE" ] || [ -z "$RENEWED_DOMAINS" ]; then
		Message_Print "[ERROR] Certbot variables not defined." 1
	fi

	ZCSPlus_Check_Certbot

	if [ -d "$RENEWED_LINEAGE" ]; then
		DOMAINS=($RENEWED_DOMAINS)
		CERTBOT_PDT_PATH="$ZCSPLETSENCRYPT_ROOT_CAS_PATH/$(basename $CERTBOT_PDT_ROOT_URL)"
		CERTBOT_STG_PATH="$ZCSPLETSENCRYPT_ROOT_CAS_PATH/$(basename $CERTBOT_STG_ROOT_URL)"
		ZCSPLINEAGE_PATH="$ZCSPLETSENCRYPT_DEPLOY_PATH/$ZMHN"
		CERT_PATH="$RENEWED_LINEAGE/cert.pem"
		CHAIN_PATH="$RENEWED_LINEAGE/chain.pem"
		FULLCHAIN_PATH="$RENEWED_LINEAGE/fullchain.pem"
		PRIVKEY_PATH="$RENEWED_LINEAGE/privkey.pem"
		CERT_LINEAGE_PATH="$ZCSPLINEAGE_PATH/cert.pem"
		CHAIN_LINEAGE_PATH="$ZCSPLINEAGE_PATH/chain.pem"
		FULLCHAIN_LINEAGE_PATH="$ZCSPLINEAGE_PATH/fullchain.pem"
		PRIVKEY_LINEAGE_PATH="$ZCSPLINEAGE_PATH/privkey.pem"
		ZCSPCHAIN_PATH="$ZCSPLINEAGE_PATH/zcspchain.pem"
		ZCSPBUNDLE_PATH="$ZCSPLINEAGE_PATH/zcspbundle.pem"
		PRIVKEY_COMMERCIAL_PATH="$ZCSPSSL_ZM_PATH/commercial/commercial.key"

		if [ -d "$ZCSPLINEAGE_PATH" ]; then
			rm -r "$ZCSPLINEAGE_PATH"
		fi

		if [ ! -d "$ZCSPLETSENCRYPT_ROOT_CAS_PATH" ]; then
			mkdir -m 0755 -p "$ZCSPLETSENCRYPT_ROOT_CAS_PATH"
			chown -R $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_PATH"
		fi

		if [ ! -d "$ZCSPLINEAGE_PATH" ]; then
			mkdir -m 0755 -p "$ZCSPLINEAGE_PATH"
			chown -R $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_PATH"
		fi

		if [ ! -f "$CERTBOT_PDT_PATH" ]; then
			wget -q -O "$CERTBOT_PDT_PATH" "$CERTBOT_PDT_ROOT_URL"
			chown -R $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_ROOT_CAS_PATH"
		fi

		if [ ! -f "$CERTBOT_STG_PATH" ]; then
			wget -q -O "$CERTBOT_STG_PATH" "$CERTBOT_STG_ROOT_URL"
			chown -R $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_ROOT_CAS_PATH"
		fi

		if [ -f "$PRIVKEY_COMMERCIAL_PATH" ]; then
			rm "$PRIVKEY_COMMERCIAL_PATH"
		fi

		cp "$CERT_PATH" "$CERT_LINEAGE_PATH"
		cp "$CHAIN_PATH" "$CHAIN_LINEAGE_PATH"
		cp "$FULLCHAIN_PATH" "$FULLCHAIN_LINEAGE_PATH"
		cp "$PRIVKEY_PATH" "$PRIVKEY_LINEAGE_PATH"
		cp "$PRIVKEY_LINEAGE_PATH" "$PRIVKEY_COMMERCIAL_PATH"

		if [ "$CERTBOT_STG" = "1" ]; then
			cat "$CHAIN_LINEAGE_PATH" "$CERTBOT_STG_PATH" > "$ZCSPCHAIN_PATH"
		else
			cat "$CHAIN_LINEAGE_PATH" "$CERTBOT_PDT_PATH" > "$ZCSPCHAIN_PATH"
		fi

		cat "$CERT_LINEAGE_PATH" "$ZCSPCHAIN_PATH" > "$ZCSPBUNDLE_PATH"

		if [[ "$DOMAINS[@]" =~ "$ZMHN" ]]; then
			chmod 0644 "$PRIVKEY_COMMERCIAL_PATH"
			chown $ZCSP_OS_USER:$ZCSP_OS_GROUP "$PRIVKEY_COMMERCIAL_PATH"
			chown -R $ZCSP_OS_USER:$ZCSP_OS_GROUP "$ZCSPLETSENCRYPT_PATH"

			#su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmcertmgr verifycrt comm \"$PRIVKEY_COMMERCIAL_PATH\" \"$CERT_LINEAGE_PATH\" \"$ZCSPCHAIN_PATH\""
			su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmcertmgr deploycrt comm \"$CERT_LINEAGE_PATH\" \"$ZCSPCHAIN_PATH\""
		fi

		#
		# Disabling SNI support, despite certificate compliance, private key detection is misleading in ZCSPlus/Zimbra domain administrator management.
		# Unfortunately, we prefer to disable this support to avoid being able to make changes in domain management.
		#
		#if [ -f "$ZCSPLETSENCRYPT_DOMAINS_LIST_PATH" ]; then
		#	for DOMAIN in $(cat "$ZCSPLETSENCRYPT_DOMAINS_LIST_PATH"); do {
		#		if [ "$DOMAIN" != "$ZMHN" ]; then
		#			su zimbra -c "$ZCSP_PATH/bin/zmprov md $DOMAIN zimbraVirtualHostName $DOMAIN"
		#			su zimbra -c "$ZCSP_PATH/libexec/zmdomaincertmgr savecrt $DOMAIN \"$ZCSPBUNDLE_PATH\" \"$PRIVKEY_LINEAGE_PATH\""
		#			su zimbra -c "$ZCSP_PATH/libexec/zmdomaincertmgr deploycrts"
		#		fi
		#	}
		#	done
		#fi

		Message_Print "ZCSPlus Restart"
		su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmcontrol restart"
	fi
}

su - $ZCSP_OS_USER -c "$ZCSP_PATH/bin/zmmailboxdctl status" > "/dev/null"

if [ $? -ne 0 ]; then
	Message_Print "[ERROR] Mailbox is not running..." 1
else
	ZCSPlus_Hostname

	if [ -n "${1-}" ]; then
		if [ "$1" = "nginx-patch" ]; then
			ZCSPlus_NGINX_Patch
		elif [ "$1" = "deploy-cert" ]; then
			ZCSPlus_Deploy_Certbot
		fi
	else
		ZCSPlus_Check_Certbot
		ZCSPlus_NGINX_Patch
		ZCSPlus_Add_Domains_Certbot
	fi
fi

exit 0;
