#!/usr/bin/python2
import os
import getopt
import socket
import sys
import md5
import subprocess
import xml.etree.ElementTree as ET
from roles import (PublicNetworkRole, PrivateNetworkRole, RoleFactory)
from iface import NetworkInterface
from iputils import validate_ip, get_ipv4_network_addr
from constants import FIREWALL_ZONES_DIR, FIREWALL_CMD, NMCLI_CMD

def log_error(f_arg, *argv) :
	print >> sys.stderr, f_arg
	for arg in argv:
		print >> sys.stderr, arg

def test_dependencies():
	executable = [ FIREWALL_CMD, NMCLI_CMD ]
	for e in executable:
		if not os.path.isfile(e) or not os.access(e, os.X_OK):
			log_error("Can't find executable %s" % e)
			sys.exit(1)

def test_firewalld_state():
	p = subprocess.Popen([ FIREWALL_CMD, '--state'], stdout=subprocess.PIPE)
	p.wait()
	if p.returncode != 0:
		log_error('Firewalld service is not running')
		sys.exit(1)

def firewalld_reload_cfg():
	try:
		subprocess.check_call([FIREWALL_CMD, '--reload'])
	except:
		log_error("Firewalld failed to reload new configuration")
		sys.exit(4)

def set_zone(iface, zone_name):
	try:
		subprocess.check_call([NMCLI_CMD, 'c', 'm',  iface.get_mn_uuid(), 'connection.zone', zone_name])
	except:
		log_error("Can't set zone for connection '%s'" % iface.get_name())
		sys.exit(4)

usage = """
Firewall configuration tool for VStorage

Usage:
 firewall-cfg -i,--interface DEV [-r,--role ROLE,...] [-n,--network ADDR/MASK,...]

Options:
  -i, --interface DEV      name of network interface to configure
  -r, --role ROLE,...      network roles for interface delimited by comma
  -n, --network ADDR/MASk  IPv4 addresses of trusted network delimited by comma
  -p, --private            get trusted networks from interface configuration
  -P, --ports              ports to be opened (-P 8001,1210,4400)
  -h, --help               this help 
"""

def normalize_zone_name(zone_name):
        # length of zone name is limited by 17 bytes
        if len(zone_name) < 17:
                return zone_name
        return zone_name[:10] + md5.md5(zone_name).hexdigest()[:6]

def validate_network_addr(addr):
	pair = addr.split('/')
	if len(pair) != 2 or not validate_ip(pair[0], socket.AF_INET):
		log_error( "Invalid format of network address: '%s'" % addr )
		sys.exit(1)

def main():
	ports = [ ]
	roles = [ ]
	rnames = [ ]
	iface_private = False
	iface_name = None
	networks = set()
	test_dependencies()
	os.putenv('LANG', 'C')

        try:
		opts, args = getopt.getopt(sys.argv[1:], 'i:r:n:P:ph',
				['interface=', 'role=', 'network=', 'ports=', 'private', 'help'])
        except getopt.GetoptError as err:
                log_error( str(err) )
                sys.exit(2)

        for opt, arg in opts:
                if opt in ('-h', '--help'):
                        print >> sys.stderr, usage
                        return
                if opt in ('-i', '--interface'):
                	iface_name = arg
                elif opt in ('-r', '--role'):
			f = RoleFactory()
			for rn in arg.split(','):
				try:
					if rn:
						r = f.get(rn)
						roles.append(r)
						rnames.append(rn)
				except:
					log_error( 'Unknown role %s' % rn )
					log_error( 'Please use these values:', f.get_list() )
					sys.exit(2)
		elif opt in ('-P', '--ports'):
			ports = arg.split(',') if arg else []
		elif opt in ('-p', '--private'):
			iface_private = True
                elif opt in ('-n', '--network'):
			for n in arg.split(','):
				validate_network_addr(n.strip())
        	        	networks.add(n.strip())
                else:
                        assert False, 'unhandled option'

	if not iface_name:
		log_error( 'Name of network interface (-i,--interface) should be specified' )
		sys.exit(2)

	test_firewalld_state()

	public, private, zone_name = (False, iface_private, '')

	iface = NetworkInterface(iface_name)

	if ports:
		iface.add_role(
			PublicNetworkRole(name='custom', tcp_ports=ports, udp_ports=ports))

	for r in roles:
		iface.add_role(r)
		public = True

	for n in networks:
		iface.add_network(n)
		private = True

	if iface_private: # all networks on private interface are trusted !!!
		local = iface.get_ipv4_addrs()
		if len(local) < 1:
			log_error( 'No IPv4 addresses are assigned on on interface %s' % iface_name )
			sys.exit(1)

		for addr in local:
			pair = addr.split('/')
			net = get_ipv4_network_addr(pair[0], int(pair[1]))
			print 'add network %s in trusted list' % net
			iface.add_network(net)
		private = True

	if private and public:
		iface.add_role(PrivateNetworkRole())
		zone_name = 'mix_' + iface_name.replace('.', '_')
	elif private:
		iface.add_role(PrivateNetworkRole())
		zone_name = 'pri_' + iface_name.replace('.', '_')
	else:
		zone_name = 'pub_' + iface_name.replace('.', '_')

	zone_name = normalize_zone_name(zone_name)

	description = 'Automatically generated zone for roles=%s networks=%s' % (rnames, list(networks))

	# always enable dhcpv6-client
	iface.add_role( PublicNetworkRole(name='dhcpv6-client', services=('dhcpv6-client',)) )

	zone = iface.get_zone(zone_name, description)
	zone_file = (FIREWALL_ZONES_DIR  + '/%s.xml') % zone_name
	try:
		tree = ET.ElementTree(zone)
		tree.write(zone_file)
		print 'Configuration saved in %s' % zone_file
		old_zone_name = iface.get_configured_zone()
		if old_zone_name and old_zone_name != zone_name:
			old_zone_file = (FIREWALL_ZONES_DIR + '/%s.xml') % old_zone_name
			print 'Remove old configuration %s' % old_zone_file
			try:
				os.remove(old_zone_file)
			except:
				pass
	except:
		log_error( "Can't save zone configuration in %s" % zone_file )
		sys.exit(3)

	firewalld_reload_cfg();
	set_zone(iface, zone_name)
	sys.exit(0)

if __name__ == "__main__":
	main()

