#!/usr/share/ucs-test/runner python
## desc: Basic email functions
## tags: [apptest]
## exposure: dangerous
## packages: [univention-mail-server]
## bugs: []

from __future__ import print_function
from univention.config_registry import handler_set
import socket
import subprocess
import netifaces
import univention.testing.ucr as ucr_test
import univention.testing.utils as utils


def get_ext_ip():
	ifaces = netifaces.interfaces()
	print('interfaces: {!r}'.format(ifaces))
	ifaces = [i for i in ifaces if i not in ('docker', 'docker0', 'lo') and not i.startswith('veth')]
	# ignore ipv6 only
	ifaces = [netifaces.ifaddresses(iface)[netifaces.AF_INET] for iface in ifaces if netifaces.AF_INET in netifaces.ifaddresses(iface)]
	print('ifaces: {!r}'.format(ifaces))
	ifaces = ifaces[0]
	addrs = [i['addr'] for i in ifaces]
	print('addrs: {!r}'.format(addrs))
	return addrs[0]


def set_postfix_networks(networks='127.0.0.0/8'):
	handler_set(['main/postfix/mynetworks=%s' % networks])


def restart_postfix():
	print('\n* restarting postfix')
	cmd = ['service', 'postfix', 'restart']
	subprocess.Popen(cmd, stderr=open('/dev/null', 'w')).communicate()


def reverse_dns_name(ip):
	reverse = ip.split('.')
	reverse.reverse()
	return '%s.in-addr.arpa' % '.'.join(reverse)


def print_header(section_string):
	print('info', 40 * '+', '\n%s\ninfo' % section_string, 40 * '+')


ucr = ucr_test.UCSTestConfigRegistry()
ucr.load()
fqdn = '{}.{}'.format(ucr['hostname'], ucr['domainname'])
sender_ip = get_ext_ip()
smtp_port = 25


def create_bad_mailheader(mailfrom, rcptto):

	def get_return_code(s):
		try:
			return int(s[:4])
		except ValueError:
			return -1

	def get_reply(s):
		buff_size = 1024
		reply = ''
		while True:
			part = s.recv(buff_size)
			reply += part
			if len(part) < buff_size:
				break
		return reply

	def send_message(s, message):
		print('OUT: {!r}'.format(message))
		s.send('{}\r\n'.format(message))

	def send_and_receive(s, message):
		send_message(s, message)
		reply = [reply.strip() for reply in get_reply(s).split('\n') if reply.strip()]
		r = get_return_code(reply[-1])
		for line in reply[:-1]:
			print('IN : {!r}'.format(line))
		print('IN : {!r} (return code: {!r})'.format(reply[-1], r))
		return r

	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((sender_ip, smtp_port))
	s.settimeout(0.2)
	reply = get_reply(s)
	r = get_return_code(reply)
	print('IN : {!r} (return code: {!r})'.format(reply, r))
	send_and_receive(s, 'EHLO %s' % fqdn)
	if mailfrom:
		send_and_receive(s, 'MAIL FROM: %s' % mailfrom)
	send_and_receive(s, 'RCPT TO: %s' % rcptto)
	send_and_receive(s, 'DATA')
	send_and_receive(s, 'SPAMBODY')
	retval = send_and_receive(s, '.')
	send_and_receive(s, 'QUIT')
	s.close()

	if retval == 250:
		return 0
	else:
		return 1


def main():
	header0 = "The classic open-relay test:"
	header1 = "The classic open-relay test with the \"\" (Sendmail 8.8 and others MTAs, much used by the spammers):"
	header2 = "The non-RFC821 compliant test (MS Exchange and SLmail betas):"
	header3 = "No sender-domain vulnerability (Post.Office, Intermail and Sendmail 8.8 misconfigurated):"
	header4 = "A heavily exploited vulnerability (Lotus Notes/Domino, Novell Groupwise, badly secured Sendmails and others):"
	header5 = "A variation of the vulnerability above (using @ instead %, but less popular among spammers):"
	header6 = "Mixed UUCP and Internet addressing (common with Sendmails with FEATURE(nouucp) set):"
	header7 = "A heavily exploited vulnerability using the ':' character:"
	header8 = "An old UUCP style vulnerability:"
	header9 = "NULL sender vulnerability:"

	test_cases = [
		('%s@%s' % ('spambag', get_ext_ip()), 'victim@mailinator.com', header0),
		('%s@[%s]' % ('spambag', get_ext_ip()), 'victim@mailinator.com', header0),
		('%s@%s' % ('spambag', reverse_dns_name(get_ext_ip())), 'victim@mailinator.com', header0),

		("spambag@mailinator.com", "victim@mailinator.com", header1),

		("spambag@mailinator.com", "victim@mailinator.com", header2),

		("spambag", "victim@mailinator.com", header3),

		("spambag@mailinator.com", "victim%mailinator.com@$SENDERIP", header4),
		("spambag@mailinator.com", "victim%mailinator.com@[$SENDERIP]", header4),
		("spambag@mailinator.com", "victim%mailinator.com@$REVERSENAME", header4),

		("spambag@mailinator.com", "victim@mailinator.com@$SENDERIP", header5),
		("spambag@mailinator.com", "victim@mailinator.com@[$SENDERIP]", header5),
		("spambag@mailinator.com", "victim@mailinator.com@$REVERSENAME", header5),

		("spambag@mailinator.com", "mailinator.com!victim@$SENDERIP", header6),
		("spambag@mailinator.com", "mailinator.com!victim@[$SENDERIP]", header6),
		("spambag@mailinator.com", "mailinator.com!victim@$REVERSENAME", header6),

		("spambag@mailinator.com", "@$SENDERIP:victim@mailinator.com", header7),
		("spambag@mailinator.com", "@[$SENDERIP]:victim@mailinator.com", header7),
		("spambag@mailinator.com", "@$REVERSENAME:victim@mailinator.com", header7),

		("spambag@mailinator.com", "mailinator.com!victim", header8),

		("", "victim@mailinator.com", header9),
		("<>", "victim@mailinator.com", header9),
	]

	for (mailfrom, rcpt, header) in test_cases:
		print_header(header)
		if create_bad_mailheader(mailfrom, rcpt) != 1:
			utils.fail('*** Open relay found ***')


if __name__ == '__main__':
	main()
