#!/usr/share/ucs-test/runner python3
## desc: Check authentication via SMTP, IMAP, POP3, sieve, testsaslauthd
## tags: [apptest]
## exposure: dangerous
## packages: [univention-mail-server]

import imaplib
import poplib
import subprocess
import time

import univention.testing.strings as uts
import univention.testing.ucr as ucr_test
import univention.testing.udm as udm_test
import univention.testing.utils as utils

from essential.mail import send_mail


class AuthTests:
	def __init__(self, ucr, udm):
		self.ucr = ucr
		self.udm = udm
		self.domain = self.ucr.get('domainname')

		self.username = uts.random_name()
		self.email = '%s@%s' % (self.username, self.domain)
		self.password = 'univention'
		self.userdn, self.username = self.udm.create_user(
			username=self.username,
			set={
				'password': self.password,
				'mailHomeServer': '%s.%s' % (self.ucr.get('hostname'), self.domain),
				'mailPrimaryAddress': self.email,
			}
		)

	def test_smtp_auth(self, user):
		try:
			send_mail(
				sender=self.email,
				recipients=['noreply@univention.de'],
				port=587,
				tls=True,
				ssl=False,
				username=self.email,
				password=self.password)
			print(f'>>> test_smtp_auth({user}): successfully sent mail - auth is ok')
			return True
		except Exception as exc:
			print(f'>>> test_smtp_auth({user}): sending mail failed - {exc}')
			return False

	def test_imap_auth(self, user):
		try:
			imapcon = imaplib.IMAP4_SSL(host='localhost')
			print(imapcon.login(user, self.password))
			print(f'>>> test_imap_auth({user}): successful IMAP auth')
			return True
		except Exception as exc:
			print(f'>>> test_imap_auth({user}): IMAP auth failed - {exc}')
			return False

	def test_pop3_auth(self, user):
		try:
			pop3con = poplib.POP3_SSL(host='localhost')
			print(pop3con.user(user))
			print(pop3con.pass_(self.password))
			print(f'>>> test_pop3_auth({user}): successful POP3 auth')
			return True
		except Exception as exc:
			print(f'>>> test_pop3_auth({user}): POP3 auth failed - {exc}')
			return False

	def test_saslauthd(self, user):
		if not self.ucr.is_true('mail/cyrus'):
			return None
		exitcode = subprocess.call(['testsaslauthd', '-u', self.username, '-p', self.password])
		print('>>> test_saslauthd(%s): exitcode=%r' % (user, exitcode))
		return exitcode == 0

	def test_sieve(self, user):
		proc = subprocess.Popen([
			'sieve-connect', '--nosslverify', '--notlsverify',
			'--user', user,
			'--authzid', user,
			'--server', '{}.{}'.format(self.ucr.get('hostname'), self.ucr.get('domainname')),
			'--port', '4190', '-4',
			'--passwordfd', '0',
			'--list'], stdin=subprocess.PIPE)
		command = '%s\n' % (self.password,)
		proc.communicate(command.encode('UTF-8'))
		exitcode = proc.returncode
		print('>>> test_sieve(%s): exitcode=%r' % (user, exitcode))
		return exitcode == 0

	def restart_services(self):
		subprocess.call(['service', 'nscd', 'restart'])
		subprocess.call(['service', 'saslauthd', 'restart'])
		subprocess.call(['service', 'postfix', 'restart'])
		if self.ucr.is_true('mail/cyrus'):
			subprocess.call(['service', 'cyrus-imapd', 'restart'])
		if self.ucr.is_true('mail/dovecot'):
			subprocess.call(['service', 'dovecot', 'restart'])
		utils.wait_for_replication()  # _and_postrun()
		time.sleep(8)   # postrun (15s) is too much

	def set_userPassword(self, value):
		lo = utils.get_ldap_connection()
		password = lo.get(self.userdn, ['userPassword'])['userPassword']
		print("password ", password)
		lo.modify(self.userdn, [('userPassword', password, [value.encode('UTF-8')])])

	def run_tests(self):
		result = {}
		checks = (
			('smtp_mailPrimaryAddress', self.email, self.test_smtp_auth),
			('smtp_uid', self.username, self.test_smtp_auth),
			('imap_mailPrimaryAddress', self.email, self.test_imap_auth),
			('imap_uid', self.username, self.test_imap_auth),
			('pop3_mailPrimaryAddress', self.email, self.test_pop3_auth),
			('pop3_uid', self.username, self.test_pop3_auth),
			('testsaslauthd_mailPrimaryAddress', self.email, self.test_saslauthd),
			('testsaslauthd_uid', self.username, self.test_saslauthd),
			('sieve_mailPrimaryAddress', self.email, self.test_sieve),
			('sieve_uid', self.username, self.test_sieve),
		)
		self.restart_services()

		for (key, user, func) in checks:
			result[f'pre_change_{key}'] = func(user)

		self.set_userPassword('{K5KEY}')
		self.restart_services()

		for (key, user, func) in checks:
			result[f'post_change_{key}'] = func(user)

		print('RESULT:')
		print('=' * 55)
		results = sorted(result.items(), key=lambda x: x[0], reverse=True)
		for key, val in results:
			print('{:<45}: {}'.format(key, {True: 'OK', False: 'FAILED', None: 'SKIPPED'}[val]))

		if any(x is False for x in result.values()):
			utils.fail('Not all authentication were okay!')


def main():
	with udm_test.UCSTestUDM() as udm:
		with ucr_test.UCSTestConfigRegistry() as ucr:
			authtest = AuthTests(ucr, udm)
			authtest.run_tests()


if __name__ == '__main__':
	main()
# vim: ft=python:ts=4:sw=4:noet:
