#!/usr/bin/python3
#
# Copyright 2009-2025 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

import os
import sys
import base64
import datetime
import M2Crypto
import univention.uldap
import univention.config_registry

configRegistry = univention.config_registry.ConfigRegistry()
configRegistry.load()

ldap_base = configRegistry.get('ldap/base', False)
domainname = configRegistry.get('domainname', False)
ldap_host = configRegistry.get('ldap/master', False)
certpath = configRegistry.get('ssl/usercert/certpath', False)
hostCertPath = configRegistry.get('ssl/windowscert/certpath', False)

if not ldap_base or not domainname or not ldap_host or not certpath:
	sys.stderr.write("need proper ucr settings for 'ldap/base', 'domainname', 'ldap/master' and 'ssl/usercert/certpath'\n")
	sys.exit(1)

certificates = {}

# get crl and revoked certs
revoked = {}
sslBase = configRegistry.get('ssl/usercert/sslbase', '/etc/univention/ssl/')
ca = configRegistry.get('ssl/usercert/ca', 'ucsCA')
crlFile = os.path.join(sslBase, ca, "crl", "crl.pem")
crl = M2Crypto.X509.load_crl(crlFile)
for i in crl.as_text().split('\n'):
	i = i.strip()
	if i.startswith("Serial Number: "):
		s = i.split(":")[-1].strip()
		if s:
			revoked[int(s, 16)] = 1


# get pem file or certificate string
# return "valid/invalid", cn and not_after date
def parse_certificate(certificate):
	if os.path.isfile(certificate):
		x509 = M2Crypto.X509.load_cert(certificate)
	else:
		x509 = M2Crypto.X509.load_cert_string(certificate)

	subject = x509.get_subject()
	serial = x509.get_serial_number()
	not_after = x509.get_not_after()

	date_not_after = not_after.get_datetime().replace(tzinfo=None)
	date_now = datetime.datetime.now()
	delta = date_not_after - date_now

	if serial in revoked:
		valid = "invalid per crl"
	elif date_not_after < date_now:
		valid = "invalid per date"
	else:
		valid = "valid"

	try:
		cn = subject.CN
	except Exception:
		cn = str(subject)

	return (cn, {
		'valid': valid,
		'cn_cert': cn,
		'not_after': date_not_after.isoformat(),
		'delta': delta.days,
		'serial': serial,
	})


# ldap certificates
try:
	lo = univention.uldap.getMachineConnection()
except Exception as exc:
	print('ERROR: authentication error: %s' % (exc,))
	sys.exit(1)

results = lo.search(filter='(&(userCertificate;binary=*)(uid=*)(cn=*))', attr=['userCertificate;binary', 'uid', 'cn'])
for dn, attrs in results:
	cert = attrs['userCertificate;binary'][0]
	cert = '-----BEGIN CERTIFICATE-----\n%s-----END CERTIFICATE-----\n' % base64.encodestring(cert)
	(cn_cert, rec) = parse_certificate(cert)
	rec['cn_ldap'] = attrs['cn'][0]
	rec['uid_ldap'] = attrs['uid'][0]
	certificates[cn_cert] = rec

# pem files in usercert directory
for path in [certpath, hostCertPath]:
	if path and os.path.isdir(path):
		for root, dirs, files in os.walk(path):
			for file in files:
				if file != "cert.pem":
					continue
				file = os.path.join(root, file)
				(cn_cert, rec) = parse_certificate(file)
				if cn_cert not in certificates:
					rec['cn_ldap'] = ""
					rec['uid_ldap'] = ""
					certificates[cn_cert] = rec

FMT = '{cn_cert:20}{cn_ldap:20}{uid_ldap:20}{not_after:20}{delta:>23}{serial:>7} {valid:8}'
print(FMT.format(
	cn_cert="CN (cert)",
	cn_ldap="CN (ldap)",
	uid_ldap="UID",
	not_after="Valid until",
	delta="Days until expiration",
	serial="Serial",
	valid="Valid",
))
for cn, cert in sorted(certificates.items()):
	print(FMT.format(**cert))
