# SPDX-License-Identifier: LGPL-2.1-or-later

# Copyright (c) 2019 Red Hat, Inc.
# Copyright (c) 2019 Tomáš Mráz <tmraz@fedoraproject.org>

from subprocess import call, CalledProcessError
from tempfile import mkstemp

import os

from .configgenerator import ConfigGenerator


class LibreswanGenerator(ConfigGenerator):
	CONFIG_NAME = 'libreswan'

	RELOAD_CMD = 'systemctl try-restart ipsec.service 2>/dev/null || :\n'

	group_map = {
		'X448':'',
		'X25519':'',
		# Disabled for now as it cannot be prioritized over others
		# 'X25519':'dh31',
		'SECP256R1':'dh19',
		'SECP384R1':'dh20',
		'SECP521R1':'dh21',
		'FFDHE-6144':'',
		'FFDHE-1536':'dh5',
		'FFDHE-2048':'dh14',
		'FFDHE-3072':'dh15',
		'FFDHE-4096':'dh16',
		'FFDHE-8192':'dh18'
	}

	cipher_map = {
		'AES-256-CBC':'aes256',
		'AES-128-CBC':'aes128',
		'AES-256-GCM':'aes_gcm256',
		'AES-128-GCM':'aes_gcm128',
		'CHACHA20-POLY1305':'chacha20_poly1305'
		# Unused for IKEv2
		# '3DES-CBC':'3des'
	}

	cipher_prf_map = {
		'AES-256-CBC-HMAC-SHA2-512':'sha2_512',
		'AES-256-CBC-HMAC-SHA2-256':'sha2_256',
		'AES-128-CBC-HMAC-SHA2-256':'sha2_256',
		# Not needed for IKEv2
		# 'AES-256-CBC-HMAC-SHA1':'sha1',
		# 'AES-128-CBC-HMAC-SHA1':'sha1',
		'AES-256-GCM-HMAC-SHA2-512':'sha2_512',
		'AES-256-GCM-HMAC-SHA2-256':'sha2_256',
		'AES-128-GCM-HMAC-SHA2-512':'sha2_512',
		'AES-128-GCM-HMAC-SHA2-256':'sha2_256',
		'CHACHA20-POLY1305-HMAC-SHA2-512':'sha2_512',
		'CHACHA20-POLY1305-HMAC-SHA2-256':'sha2_256'
		# '3DES-CBC-HMAC-SHA1':'sha1'
	}

	cipher_mac_map = {
		'AES-256-CBC-HMAC-SHA2-512':'sha2_512',
		'AES-256-CBC-HMAC-SHA2-256':'sha2_256',
		'AES-128-CBC-HMAC-SHA2-256':'sha2_256',
		'AES-256-CBC-HMAC-SHA1':'sha1',
		'AES-128-CBC-HMAC-SHA1':'sha1',
		'AES-256-GCM-AEAD':'',
		'AES-128-GCM-AEAD':'',
		'CHACHA20-POLY1305-AEAD':''
		# '3DES-CBC-HMAC-SHA1':'3des-sha1'
	}

	protocol_map = {
		'IKEv1':'ikev2=never',
		'IKEv2':'ikev2=insist'
	}

	mac_ike_prio_map = {
		'AEAD':0,
		'HMAC-SHA2-512':1,
		'HMAC-SHA2-256':2,
		'HMAC-SHA1':3
	}

	mac_esp_prio_map = {
		'AEAD':0,
		'HMAC-SHA2-512':1,
		'HMAC-SHA1':2,
		'HMAC-SHA2-256':3
	}

	@classmethod
	def __get_ike_prio(cls, key):
		if key not in cls.mac_ike_prio_map:
			return 99
		return cls.mac_ike_prio_map[key]

	@classmethod
	def __get_esp_prio(cls, key):
		if key not in cls.mac_esp_prio_map:
			return 99
		return cls.mac_esp_prio_map[key]

	@classmethod
	def generate_config(cls, policy):
		cfg = 'conn %default\n'
		sep = ','
		p = policy.props

		s = ''
		nproto = len(p['ike_protocol'])
		if nproto > 1:
			s = 'ikev2=permit'
		elif nproto > 0:
			s = cls.protocol_map[p['ike_protocol'][0]]
		if s:
			cfg += '\t' + s + '\n'

		cfg += '\tpfs=yes\n'

		sorted_macs = sorted(p['mac'],
			key=cls.__get_ike_prio)

		tmp = ''
		for cipher in p['cipher']:
			try:
				cm = cls.cipher_map[cipher]
			except KeyError:
				continue
			combo = cm + '-'
			s = ''
			for mac in sorted_macs:
				try:
					mm = cls.cipher_prf_map[cipher + '-' + mac]
				except KeyError:
					continue
				s = cls.append(s, mm, '+')
			if not s:
				continue
			combo += s
			s = ''
			for i in p['group']:
				try:
					group = cls.group_map[i]
				except KeyError:
					continue
				s = cls.append(s, group, '+')
			combo = cls.append(combo, s, '-')
			tmp = cls.append(tmp, combo, sep)

		if tmp:
			cfg += '\tike=' + tmp + '\n'

		sorted_macs = sorted(p['mac'],
			 key=cls.__get_esp_prio)

		tmp = ''
		for cipher in p['cipher']:
			try:
				cm = cls.cipher_map[cipher]
			except KeyError:
				continue
			combo = cm + '-'
			s = ''
			for mac in sorted_macs:
				try:
					mm = cls.cipher_mac_map[cipher + '-' + mac]
				except KeyError:
					continue
				if not mm:
				# Special handling for AEAD
					combo = cm
					break
				s = cls.append(s, mm, '+')
			combo += s
			if combo[-1:] == '-':
				continue
			tmp = cls.append(tmp, combo, sep)

		if tmp:
			cfg += '\tesp=' + tmp + '\n'

		return cfg

	@classmethod
	def test_config(cls, config):
		if not os.access('/usr/sbin/ipsec', os.X_OK):
			return True

		fd, path = mkstemp()

		ret = 255
		try:
			with os.fdopen(fd, 'w') as f:
				f.write(config)
			try:
				ret = call('/usr/sbin/ipsec readwriteconf --config ' + path +
					' >/dev/null',
					shell=True)
			except CalledProcessError:
				cls.eprint("/usr/sbin/ipsec: Execution failed")
		finally:
			os.unlink(path)

		if ret:
			cls.eprint("There is an error in libreswan generated policy")
			cls.eprint("Policy:\n%s" % config)
			return False
		return True
