//
// C++ Implementation: kmfiptablesdocumentconverter
//
// Description:
//
//
// Author: Christian Hubinger <chubinger@gmail.com>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
// License: GPL
//
#include "kmfiptablesdocumentconverter.h"

// QT includes

// KDE includes
#include <kdebug.h>
#include <klocale.h>

#include "../../version.h"
#include "../../core/kmfgenericdoc.h"
#include "../../core/kmfiptdoc.h"
#include "../../core/kmfnetzone.h"
#include "../../core/kmfnethost.h"
#include "../../core/kmfprotocol.h"
#include "../../core/iptable.h"
#include "../../core/iptchain.h"
#include "../../core/iptrule.h"
#include "../../core/iptruleoption.h"
#include "../../core/kmferror.h"
#include "../../core/kmferrorhandler.h"
#include "../../core/kmfconfig.h"




KMFIPTablesDocumentConverter::KMFIPTablesDocumentConverter() {

	m_errorHandler = new KMFErrorHandler( "KMFIPTablesDocumentConverter" );
	m_err = new KMFError();

}


KMFIPTablesDocumentConverter::~KMFIPTablesDocumentConverter() {}


KMFIPTDoc* KMFIPTablesDocumentConverter::compileToIPTDoc( KMFGenericDoc* doc ) {
	kdDebug() << "const QString& KMFIPTablesCompiler::compileToIPTDoc( KMFGenericDoc* doc )" << endl;
	if ( ! doc ) {
		kdDebug() << "No document Available to compile" << endl;
		return 0;
	}
	//	kdDebug() << "Doc XLM:\n" << doc->getXMLSniplet() << endl;
	KMFIPTDoc *iptdoc = new KMFIPTDoc( 0, "iptdoc" );
	KMFNetZone *zone = 0;
	IPTable *table = 0;
	IPTChain *chain = 0;

	setupInAndOutHosts( iptdoc, doc->trustedHostsZone(), "ACCEPT" );
	setupInAndOutHosts( iptdoc, doc->maliciousHostsZone(), "DROP" );
	setupForbiddenHosts( iptdoc, doc->badClientsHostsZone(), "in" );
	setupForbiddenHosts( iptdoc, doc->badServersHostsZone(), "out" );
	
	setupICMPRules( doc, iptdoc );

	if ( doc->allowIncomingConnections() ) {
		zone = doc->incomingZone();
		table = iptdoc->table( "filter" );
		chain  = table->chainForName( *(new QString("INPUT") ) );
		addToChains(  zone, iptdoc, chain, "INPUT");
	}

	if ( doc->restrictOutgoingConnections() ) {
		zone = doc->outgoingZone();
		chain  = table->chainForName( *(new QString("OUTPUT") ) );
		addToChains(  zone, iptdoc, chain, "OUTPUT" );
	}

	setupConnectionTracking( iptdoc );
	setupPolicies( doc, iptdoc );

	setupNatRules( doc, iptdoc );
	setupLogging( doc, iptdoc );

	return iptdoc;
}

void KMFIPTablesDocumentConverter::setupConnectionTracking( KMFIPTDoc* doc ) {
	kdDebug() << "void KMFIPTablesCompiler::setupConnectionTracking( KMFIPTDoc* doc )" << endl;

	IPTable *table = doc->table( "filter" );
	IPTChain *chain  = table->chainForName( *(new QString("INPUT") ) );

	IPTRule *rule = chain->addRule( "CONNTRACK", m_err );
	if ( ! m_errorHandler->showError( m_err ) ) {
		return;
	}
	QPtrList<QString> args;
	args.append( new QString("bool:on") );
	args.append( new QString("RELATED,ESTABLISHED") );
	QString  opt = "state_opt";
	rule->addRuleOption( opt, args );
	rule->setTarget( "ACCEPT" );
	rule->setDescription( i18n( "This rule enables connection tracking\n"
	                            "in your firewall.\n"
	                            "It simply allows all traffic reaching\n"
	                            "your host, which is somehow related to\n"
	                            "connections you established e.g. answers\n"
	                            "others send you to your requests.") );
}

void KMFIPTablesDocumentConverter::setupPolicies( KMFGenericDoc* gendoc, KMFIPTDoc* iptdoc ) {
	kdDebug() << "void KMFIPTablesCompiler::setupPolicies( KMFGenericDoc* gendoc, KMFIPTDoc* iptdoc )" << endl;
	IPTable *table = iptdoc->table( "filter" );
	IPTChain *chain  = table->chainForName( *(new QString("INPUT") ) );
	chain->setDefaultTarget( "DROP" );
	chain  = table->chainForName( *(new QString("OUTPUT") ) );

	if ( gendoc->restrictOutgoingConnections() ) {
		chain->setDefaultTarget( "DROP" );
	} else {
		chain->setDefaultTarget( "ACCEPT" );
	}
}

void KMFIPTablesDocumentConverter::addToChains( KMFNetZone* zone, KMFIPTDoc* doc, IPTChain* chain,  const QString& root_chain ) {
	QPtrList<KMFNetZone>& children = zone->zones();
	QPtrListIterator<KMFNetZone> it( children );
	static int i = 0;
	while( it.current() ) {
		addToChains(  it.current(), doc, chain, root_chain );
		++it;
	}
	IPTable *table = doc->table( "filter" );
	QString num = "";
	num.setNum( i );
	QString name = "";

	if ( root_chain == "INPUT" ) {
		name = "IZ_" + num;
	} else if ( root_chain == "OUTPUT" ) {
		name = "OZ_" + num;
	}

	name.stripWhiteSpace();
	QString target = "ACCEPT";
	if ( zone->address()->toString() != "0.0.0.0" ) {
		table->addChain( name, target, false, m_err );
		if ( ! m_errorHandler->showError( m_err ) )
			return;

		if ( ! chain ) {
			kdDebug() << "KMFIPTablesCompiler: WARNING Couldn't create chain: " <<  name << endl;
			return;
		}
		IPTRule* rule = 0;
		rule = chain->addRule( "Feed_" + num , m_err );
		if ( ! m_errorHandler->showError( m_err ) )
			return;
		rule->setDescription( i18n(	"This rule forwards all traffic to\n"
		                            "chain: %1 which handles traffic for\n"
		                            "zone: %2.").arg( name ).arg( zone->guiName() ) );
		i++;
		if ( ! rule ) {
			kdDebug() << "KMFIPTablesCompiler: WARNING Couldn't create rule: Feed in chain: " <<  chain->name() << endl;
			return;
		}
		IPTChain *ch  = table->chainForName( name );
		if ( ! ch )  {
			kdDebug() << "KMFIPTablesCompiler: WARNING Couldn't find chain: " <<  name << endl;
			return;
		}
		ch->setDescription( i18n("The Chain created to handle\nrules defined in zone %1.").arg( zone->guiName() ) );
		
		QPtrList<QString> args;
		if ( root_chain == "INPUT" ) {
			args.append( new QString( zone->address()->toString()+"/"+zone->mask()->toString() ) );
			args.append( new QString( "bool:off" ) );
		} else if ( root_chain == "OUTPUT" ) {
			args.append( new QString( "bool:off" ) );
			args.append( new QString( zone->address()->toString()+"/"+zone->mask()->toString() ) );
		}
		QString s ="ip_opt";
		rule->addRuleOption( s , args );
		rule->setTarget( name );
		createRules( zone, ch, root_chain );
	} else {
		createRules( zone, chain, root_chain );
	}
}

void KMFIPTablesDocumentConverter::createRules( KMFNetZone* zone, IPTChain* chain, const QString& root_chain ) {
	QPtrList<KMFProtocol>& prots = zone->protocols();
	QPtrListIterator<KMFProtocol> it ( prots );
	while ( it.current() ) {
		KMFProtocol* prot = it.current();
		if ( ! zone->protocolInherited( prot->name() ) ) {
			createZoneProtocolRules( chain, prot );
		} else {
			kdDebug() << "Skipping inherited Portocol: " << prot->name() << " in zone: " << zone->guiName() << endl;
		}
		++it;
	}

	QPtrList<KMFNetHost>& hosts = zone->hosts();
	QPtrListIterator<KMFNetHost> it2 ( hosts );
	while ( it2.current() ) {
		KMFNetHost* host = it2.current();
		kdDebug() << "Will create rules for host: " << host->guiName() << " in zone:" << zone->guiName() << endl;
		QPtrList<KMFProtocol>& prots = host->protocols();
		QPtrListIterator<KMFProtocol> it3 ( prots );
		while ( it3.current() ) {
			KMFProtocol* prot = it3.current();
			if ( ! host->protocolInherited( prot->name() ) ) {
				kdDebug() << "Found Protocol: " << prot->name() << endl;
				createHostProtocolRules( chain, host, prot, root_chain );
			} else {
				kdDebug() << "Skipping inherited Portocol: " << prot->name() << " in host: " << host->guiName() << endl;
			}
			++it3;
		}
		++it2;
	}
}

void KMFIPTablesDocumentConverter::createZoneProtocolRules( IPTChain* chain, KMFProtocol* prot ) {
	kdDebug() << "void KMFIPTablesCompiler::createProtocolRules( ITPChain* chain, KMFProtocol* protocol )" << endl;
	QStringList tcpPorts = prot->tcpPorts();
	QStringList udpPorts = prot->udpPorts();
	if ( ! tcpPorts.empty() ) {
		createZoneProtocol( chain, prot, "tcp", tcpPorts );
	}
	if ( ! udpPorts.empty() ) {
		createZoneProtocol( chain, prot, "udp", udpPorts );
	}
}

void KMFIPTablesDocumentConverter::createZoneProtocol( IPTChain* chain, KMFProtocol* prot, const QString& option, QStringList ports ) {
	kdDebug() << "void KMFIPTablesCompiler::createProtocol( IPTChain*, const QString& option, QStringList ports )" << endl;
	QString	s;
	QPtrList<QString> args;
	args.clear();
	args.append( new QString( "bool:on" ) );
	args.append( new QString( "bool:off" ) );

	IPTRule* rule;
	rule = chain->addRule( prot->name()+ "_" + option , m_err );
	if ( ports.count() > 1 ) {
		s = option + "_multiport_opt";
	} else {
		s = option + "_opt";
	}

	if ( ! m_errorHandler->showError( m_err ) )
		return;

	rule->addRuleOption( s , args );
	rule->setDescription( prot->description() );

	QString arg = "";
	for ( QStringList::Iterator it = ports.begin(); it != ports.end(); ++it ) {
		arg += *it +",";
	}
	if ( arg.endsWith( "," ) ) {
		arg = arg.left( arg.length() -1 );
	}
	args.append( new QString( arg ) );
	rule->addRuleOption( s, args );

	if ( prot->logging() ) {
		rule->setLogging( true );
	}

	if ( prot->limit() > 0 ) {
		s = "limit_opt";
		args.clear();
		args.append( new QString("bool:on") );
		QString limit;
		limit.setNum( prot->limit() );
		limit += "/" + prot->limitInterval();
		kdDebug() << "Setting limit: " << limit << endl;
		args.append( new QString( limit ) );
		rule->addRuleOption( s, args );
	}
	rule->setTarget("ACCEPT");
}


void KMFIPTablesDocumentConverter::createHostProtocolRules( IPTChain* chain, KMFNetHost* host,  KMFProtocol* prot, const QString& root_chain ) {
	kdDebug() << "void KMFIPTablesCompiler::createProtocolRules( ITPChain* chain, KMFProtocol* protocol )" << endl;
	QStringList tcpPorts = prot->tcpPorts();
	QStringList udpPorts = prot->udpPorts();
	if ( ! tcpPorts.empty() ) {
		createHostProtocol( chain, host, prot, "tcp", tcpPorts, root_chain );
	}
	if ( ! udpPorts.empty() ) {
		createHostProtocol( chain, host, prot, "udp", udpPorts, root_chain );
	}
}

void KMFIPTablesDocumentConverter::createHostProtocol( IPTChain* chain, KMFNetHost* host, KMFProtocol* prot, const QString& option, QStringList ports, const QString& root_chain  ) {
	kdDebug() << "void KMFIPTablesCompiler::createProtocol( IPTChain*, const QString& option, QStringList ports )" << endl;
	QString	s;
	QPtrList<QString> args;
	args.clear();
	args.append( new QString( "bool:on" ) );
	args.append( new QString( "bool:off" ) );
	static int i = 0;

	IPTRule* rule;
	QString hn = "";
	hn = hn.setNum( i );
	i++;
	hn = "H" + hn;
	rule = chain->addRule( hn + "_" + prot->name() + "_" + option , m_err );
	if ( ports.count() > 1 ) {
		s = option + "_multiport_opt";
	} else {
		s = option + "_opt";
	}
	rule->setDescription( i18n(	"Rule created to apply filters for host: %1\n"
	                            "Allow Protocol: %2\n"
	                            "Protocol Description: %3" ).arg( host->guiName() ).arg( prot->name( ) ).arg( prot->description() ) );
	if ( ! m_errorHandler->showError( m_err ) )
		return;

	rule->addRuleOption( s , args );
	//rule->setDescription( prot->description() );

	QString arg = "";
	for ( QStringList::Iterator it = ports.begin(); it != ports.end(); ++it ) {
		kdDebug() << "Found " << option << " port "  <<  *it << endl;
		arg += *it +",";
	}
	if ( arg.endsWith( "," ) ) {
		arg = arg.left( arg.length() -1 );
	}
	kdDebug() << "Adding " << option << " port string: "  <<  arg << endl;
	args.append( new QString( arg ) );
	rule->addRuleOption( s, args );

	if ( prot->logging() ) {
		rule->setLogging( true );
	}

	if ( prot->limit() > 0 ) {
		s = "limit_opt";
		args.clear();
		args.append( new QString("bool:on") );
		QString limit;
		limit.setNum( prot->limit() );
		limit += "/" + prot->limitInterval();
		kdDebug() << "Setting limit: " << limit << endl;
		args.append( new QString( limit ) );
		rule->addRuleOption( s, args );
	}

	args.clear();
	if ( root_chain == "OUTPUT" ) {
		args.append( new QString( "bool:off" ) );
	}
	s = "ip_opt";
	args.append( new QString( host->address()->toString() ) );
	rule->addRuleOption( s, args );
	rule->setTarget("ACCEPT");
}

void KMFIPTablesDocumentConverter::setupInAndOutHosts( KMFIPTDoc* iptdoc, KMFNetZone* zone, const QString& target ) {
	kdDebug() << "KMFIPTablesCompiler::setupTrustedHosts( KMFNetZone* )" << endl;
	QPtrListIterator<KMFNetHost> it ( zone->hosts() );
	int i = 0;
	while ( it.current() ) {
		KMFNetHost *host = *it;
		IPTable *table = iptdoc->table( "filter" );
		IPTChain *chain;
		IPTRule *rule;
		QString ruleName = "";
		ruleName = ruleName.setNum( i );
		if ( target == "ACCEPT" ) {
			ruleName = "Trusted_" + ruleName;
		} else {
			ruleName = "Malicious_" + ruleName;
		}
		QString opt  = "ip_opt";
		QPtrList<QString> args;

		chain = table->chainForName( *(new QString("INPUT") ) );
		rule = chain->addRule( ruleName, m_err );
		if ( ! m_errorHandler->showError( m_err ) )
			return;

		args.append( new QString( host->address()->toString() ) );
		rule->addRuleOption( opt, args );
		if ( target == "ACCEPT" ) {
			rule->setDescription( i18n("This rule allows incoming packets from trusted host: %1.").arg( host->guiName() ) );
		} else {
			rule->setDescription( i18n("This rule drops incoming packets from malicious host: %1.").arg( host->guiName() ) );

		}
		rule->setTarget( target );
		if ( host->logIncoming() ) {
			rule->setLogging( true );
		}


		chain  = table->chainForName( *(new QString("OUTPUT") ) );
		rule = chain->addRule( ruleName, m_err );
		args.clear();
		args.append( new QString( "bool:off" ) );
		args.append( new QString( host->address()->toString() ) );
		rule->addRuleOption( opt, args );
		if ( ! m_errorHandler->showError( m_err ) )
			return;

		if ( target == "ACCEPT" ) {
			rule->setDescription( i18n("This rule allows outgoing packets to trusted host: %1.").arg( host->guiName() ) );
		} else {
			rule->setDescription( i18n("This rule drops outgoing packets to malicious host: %1.").arg( host->guiName() ) );

		}
		rule->setTarget( target );
		if ( host->logOutgoing() ) {
			rule->setLogging( true );
		}
		++it;
		i++;
	}
}

void KMFIPTablesDocumentConverter::setupForbiddenHosts( KMFIPTDoc* iptdoc , KMFNetZone* zone, const QString& inOut ) {
	QPtrListIterator<KMFNetHost> it ( zone->hosts() );
	int i = 0;
	while ( it.current() ) {
		KMFNetHost *host = *it;
		IPTable *table = iptdoc->table( "filter" );
		IPTChain *chain;
		IPTRule *rule;
		QString ruleName = "";
		ruleName = ruleName.setNum( i );
		if ( inOut == "in" ) {
			ruleName = "ForbiddenClient_" + ruleName;
		} else {
			ruleName = "ForbiddenServer_" + ruleName;
		}
		QString opt  = "ip_opt";
		QPtrList<QString> args;

		if ( inOut == "in" ) {
			chain = table->chainForName( *(new QString("INPUT") ) );
		} else {
			chain = table->chainForName( *(new QString("OUTPUT") ) );
		}
		rule = chain->addRule( ruleName, m_err );
		if ( ! m_errorHandler->showError( m_err ) )
			return;

		if ( inOut == "out" ) {
			args.append( new QString("bool:off") );
		}
		args.append( new QString( host->address()->toString() ) );
		rule->addRuleOption( opt, args );
		if ( inOut =="in" ) {
			rule->setDescription( i18n("This rule drops packets from forbidden client: %1.").arg( host->guiName() ) );
		} else {
			rule->setDescription( i18n("This rule drops packets to forbidden server: %1.").arg( host->guiName() ) );
		}
		rule->setTarget( "DROP" );
		if ( inOut =="in" ) {
			if ( host->logIncoming() ) {
				rule->setLogging( true );
			}
		} else {
			if ( host->logOutgoing() ) {
				rule->setLogging( true );
			}
		}
		++it;
		i++;
	}
}

void KMFIPTablesDocumentConverter::setupNatRules( KMFGenericDoc* doc, KMFIPTDoc* iptdoc ) {
	if ( ! doc->useNat() ) {
		return;
	}

	IPTable* table = iptdoc->table( "nat" );
	if ( ! table ) {
		kdDebug() << "ERROR: Couldn't find table nat!!!" << endl;
		return;
	}

	IPTChain* chain = table->chainForName( *(new QString("POSTROUTING") ) );
	if ( ! chain ) {
		kdDebug() << "ERROR: Couldn't find chain POSTROUTING!!!" << endl;
		return;
	}
	iptdoc->setUseIPFwd( true );
	iptdoc->useNat();
	IPTRule* rule = chain->addRule( "NAT_RULE", m_err );
	if ( ! m_errorHandler->showError( m_err ) ) {
		return;
	}

	rule->setDescription( i18n("Rule created for setting up\nthe nat router functionality.") );

	QString opt  = "interface_opt";
	QPtrList<QString> args;
	args.append( new QString( "bool:off" ) );
	args.append( new QString( doc->outgoingInterface() ) );
	rule->addRuleOption( opt, args );
	setupNatTarget( doc, rule );
}

void KMFIPTablesDocumentConverter::setupNatTarget( KMFGenericDoc* doc, IPTRule* rule ) {
	if ( doc->useMasquerade() ) {
		rule->setTarget( "MASQUERADE" );
	} else {
		rule->setTarget( "SNAT" );
		QString opt  = "target_snat_opt";
		QPtrList<QString> args;
		args.append( new QString( doc->natAddress()->toString() ) );
		rule->addRuleOption( opt, args );
	}
}

void KMFIPTablesDocumentConverter::setupLogging( KMFGenericDoc* doc, KMFIPTDoc* iptdoc ) {
	if ( ! doc->logDropped() ) {
		return;
	}
	IPTable* table = iptdoc->table( "filter" );
	if ( ! table ) {
		kdDebug() << "ERROR: Couldn't find table filter!!!" << endl;
		return;
	}

	IPTChain* chain = table->chainForName( *(new QString("INPUT") ) );
	if ( ! chain ) {
		kdDebug() << "ERROR: Couldn't find chain INPUT!!!" << endl;
		return;
	}

	setupLoggingRules( doc, chain );
	
	if ( doc->restrictOutgoingConnections() ) {
		chain = table->chainForName( *(new QString("OUTPUT") ) );
		if ( ! chain ) {
			kdDebug() << "ERROR: Couldn't find chain INPUT!!!" << endl;
			return;
		}
		setupLoggingRules( doc, chain );
	}
}

void KMFIPTablesDocumentConverter::setupLoggingRules( KMFGenericDoc* doc, IPTChain* chain ) {
	QString limit = "";
	QString burst = "";
	QString prefix = doc->logPrefix();

	if ( doc->limitLog() ) {
		limit = "5/second";
		burst = "5";
	}

	chain->setDropLogging( true, limit, burst, prefix );
}

void KMFIPTablesDocumentConverter::setupICMPRules( KMFGenericDoc* doc, KMFIPTDoc* iptdoc) {
	if ( ! doc->allowPingReply() ) {
		return;
	}
	IPTable* table = iptdoc->table( "filter" );
	if ( ! table ) {
		kdDebug() << "ERROR: Couldn't find table filter!!!" << endl;
		return;
	}

	IPTChain* chain = table->chainForName( *(new QString("INPUT") ) );
	if ( ! chain ) {
		kdDebug() << "ERROR: Couldn't find chain INPUT!!!" << endl;
		return;
	}
	
	IPTRule *rule = chain->addRule( "ICMP", m_err );
	if ( ! m_errorHandler->showError( m_err ) ) {
		return;
	}
	rule->setDescription( i18n("Rule to setup the ICMP Ping policy.") );
	QString opt  = "icmp_opt";
	QPtrList<QString> args;
	args.append( new QString( "bool:on" ) );
	args.append( new QString( "echo-request" ) );
	rule->addRuleOption( opt, args );
	rule->setTarget( "ACCEPT" );
	
	if ( doc->limitPingReply() ) {
		args.clear();
		QString opt  = "limit_opt";
		args.append( new QString( "bool:on" ) );
		args.append( new QString( "5/second" ) );
		args.append( new QString( "5" ) );
		rule->addRuleOption( opt, args );
	}
	
	
	if ( doc->restrictOutgoingConnections() ) {
		chain = table->chainForName( *(new QString("OUTPUT") ) );
		if ( ! chain ) {
			kdDebug() << "ERROR: Couldn't find chain INPUT!!!" << endl;
			return;
		}
		
		rule = chain->addRule( "ICMP", m_err );
		if ( ! m_errorHandler->showError( m_err ) ) {
			return;
		}
		rule->setDescription( i18n("Rule to setup the ICMP Ping policy.") );
		QString opt  = "icmp_opt";
		args.clear();
		args.append( new QString( "bool:on" ) );
		args.append( new QString( "echo-request" ) );
		rule->addRuleOption( opt, args );
		rule->setTarget( "ACCEPT" );
	}
}







