/***************************************************************************
begin                : Tue Aug 20 2002
copyright            : (C) 2002 by Christian Hubinger
email                : chubinger@irrsinnig.org
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "iptable.h"

// qt includes
#include <tqstring.h>
#include <tqregexp.h>

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

// project includes
#include "kmfcheckinput.h"
#include "iptchain.h"
#include "kmferror.h"
#include "kmfiptdoc.h"
#include "xmlnames.h"

namespace KMF {

IPTable::IPTable( KMFIPTDoc* doc, const char* name, const TQString& tableName ) : NetfilterObject( doc, name ) {
	//  kdDebug() << "IPTable::IPTable(TQString& name)" << endl;
	kmfdoc = doc;
	// m_object_type = NetfilterObject::TABLE;
	m_err = new KMFError();
	m_chains.setAutoDelete( false );
	setName( tableName );
}

IPTable::~IPTable() {
	kdDebug() << "\n\nIPTable::~IPTable() : " << name() << endl;
	clear();
}

int IPTable::type() {
// 	kdDebug() << "IPTable::type()" << endl;
	return NetfilterObject::TABLE;
}
void IPTable::clear() {
	m_chains.setAutoDelete( true );
	m_chains.clear();
	m_chains.setAutoDelete( false );
}
void IPTable::reset() {
	// 	kdDebug() << "void IPTable::resetTable()" << endl;
	TQPtrListIterator<IPTChain> it ( m_chains );
	while ( it.current() ) {
		IPTChain* chain = it.current();
		if ( chain->isBuildIn() ) {
			chain->reset();
			++it;
		} else {
			m_chains.remove( chain );
		}
	}
	changed();
}

void IPTable::settupDefaultChains() {
	//	kdDebug() << "void IPTable::settupDefaultChains()" << endl;
	TQString target = "ACCEPT";
	IPTChain* chain = 0;
	if ( name() == Constants::FilterTable_Name ) {

		setDescription( i18n("This table is the main table for filtering\n"
		                     "packets. Here you may define your\n"
		                     "access control rules") );


		chain = addChain( Constants::InputChain_Name, target, true, m_err );
// 		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can filter packets that\n"
			                             "are addressed directly to this computer." ) );
// 		}
		chain = addChain( Constants::OutputChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can decide which\n"
			                             "packets are allowed to be sent away\n"
			                             "from this computer." ) );
		}
		chain = addChain( Constants::ForwardChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can filter the packets\n"
			                             "that are routed to other hosts by this\n"
			                             "computer." ) );
		}

	} else if ( name() == Constants::NatTable_Name ) {
		setDescription( i18n("This table is made for every kind of\n"
		                     "NAT (Network Address Translation).") );

		chain = addChain( Constants::OutputChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can decide which\n"
			                             "packets are allowed to be sent away\n"
			                             "from this computer."  ) );
		}
		chain = addChain( Constants::PreRoutingChain_Name, target, true, m_err );
		if ( chain )
			chain->setDescription( i18n( "..." ) );

		chain = addChain( Constants::PostRoutingChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}

	} else if ( name() == Constants::MangleTable_Name ) {
		setDescription( i18n("This table is made for altering packets.") );

		chain = addChain( Constants::InputChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can filter packets that\n"
			                             "are addressed directly to this compter."  ) );
		}
		chain = addChain( Constants::OutputChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can decide which\n"
			                             "packets are allowed to be sent away\n"
			                             "from this computer." ) );
		}
		chain = addChain( Constants::ForwardChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "In this chain you can filter the packets\n"
			                             "that are routed to other hosts by this\n"
			                             "computer." ) );
		}
		chain = addChain( Constants::PreRoutingChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}
		chain = addChain( Constants::PostRoutingChain_Name, target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}
	}
}


IPTChain* IPTable::chainForName( const TQString& name ) {
	IPTChain * tmp_chain;
	for ( tmp_chain = m_chains.first();tmp_chain; tmp_chain = m_chains.next() ) {
		if ( tmp_chain->name() == name )
			return tmp_chain;
	}
	return 0;
}

IPTChain* IPTable::chainForUuid( const TQUuid& id ) {
	IPTChain * tmp_chain;
	for ( tmp_chain = m_chains.first(); tmp_chain; tmp_chain = m_chains.next() ) {
		if ( tmp_chain->uuid() == id )
			return tmp_chain;
	}
	return 0;
}

TQPtrList<IPTChain>& IPTable::chains() const {
	TQPtrList<IPTChain>* ret_val = new TQPtrList<IPTChain>;
	*ret_val = m_chains;
	return *ret_val;
}

IPTChain* IPTable::addChain( const TQString& chain_name, const TQString& chain_target, bool builtin, KMFError *err ) {
	// 	kdDebug() << "KMFError* IPTable::addChain( TQString& chain_name,TQString& chain_table,TQString& chain_target,bool builtin )" << endl;
	KMFCheckInput* check = new KMFCheckInput();
	check->checkInput( chain_name, "CHAINNAME", err );
	if ( err->errNum() != 0 )  {
		return 0;
	}

	TQPtrListIterator<IPTChain> it ( m_chains );
	while ( it.current() ) {
		IPTChain* tmp_chain = it.current();
		++it;
		TQString found_name = tmp_chain->name();
		if ( found_name == chain_name ) {
			const TQString msg = i18n( "<qt><p>Chain: <b>%1</b> already exists in table: <b>%2</b>.<br>"
			                          "Please try again with another name. A chain name must be unique in a table.</p></qt>" ).arg( found_name ).arg( name() );
			err->setErrType( KMFError::NORMAL );
			err->setErrMsg( msg );
			kdDebug() << "\n\nWARNING: Couldn't add chain because of dupilcate name\n\n" << endl;
			return 0;
		}
	}

	IPTChain * chain = new IPTChain( this, chain_name.latin1(), chain_name, builtin );
	if ( builtin && chain_target != TQString() )
		chain->setDefaultTarget( chain_target );
//	kdDebug() << "Adding Chain: " << chain->name() << endl;
	m_chains.append( chain );
	changed();
	err->setErrType( KMFError::OK );
	return chain;
}

KMFError* IPTable::delChain( IPTChain *chain ) {
	// 	kdDebug() << "KMFError* IPTable::delChain( IPTChain *chain )" << endl;
	m_err = new KMFError();
	TQString name = chain->name();
	if ( chain->isBuildIn() ) {
		const TQString msg = i18n( "Cannot delete built-in chain: %1" ).arg( name );
		m_err->setErrMsg( msg );
		m_err->setErrType( KMFError::NORMAL );
		return m_err;
	}
	int index = m_chains.find( chain );
	// 	kdDebug() << "fount chain nr: " << index << endl;
	if ( index < 0 ) {
		chain->deleteLater();
		const TQString msg = i18n( "Cannot delete nonexistent chain" );
		m_err->setErrMsg( msg );
		m_err->setErrType( KMFError::NORMAL );
		
		return m_err;
	} else {
		m_chains.remove( index );
		chain->deleteLater();
		const TQString msg = "";
		m_err->setErrMsg( msg );
		m_err->setErrType( KMFError::OK );
		changed();
		return m_err;
	}
}

KMFError* IPTable::moveRuleToChain( IPTRule* rule, IPTChain *target_chain ) {
	// 	kdDebug() << "KMFError* IPTable::moveRuleToChain(IPTRule* rule, IPTChain *target_chain )" << endl;
	if ( rule == 0 ) {
		m_err->setErrType( KMFError::FATAL );
		const TQString& msg = i18n( "IPTable::moveRuleToChain(IPTRule* rule, IPTChain *target_chain"
		                           "rule == 0. This is a bug." );
		m_err->setErrMsg( msg );
		return m_err;
	}
	if ( target_chain == 0 ) {
		m_err->setErrType( KMFError::FATAL );
		const TQString& msg = i18n( "IPTable::moveRuleToChain(IPTRule* rule, IPTChain *target_chain"
		                           "target_chain == 0. This is a bug." );
		m_err->setErrMsg( msg );
		return m_err;
	}
	// 	kdDebug() << "Move Rule: " << rule->name() <<" from Chain: " <<  rule->chain() ->name() << "to chain" <<  target_chain->name()  << endl;

	IPTRule* new_rule = target_chain->addRule( i18n( "%1_Copy" ).arg( rule->name() ), m_err );
	if ( m_err->errNum() == 0 ) {
		rule->createRuleClone( new_rule );
		IPTRule* ru = rule;
		rule->chain()->delRule( ru );
	}
	return m_err;
}

KMFError* IPTable::copyRuleToChain( IPTRule* rule, IPTChain *target_chain ) {
	// 	kdDebug() << "KMFError* IPTable::copyRuleToChain(IPTRule* rule, IPTChain *target_chain )" << endl;
	if ( rule == 0 ) {
		m_err->setErrType( KMFError::FATAL );
		const TQString& msg = i18n( "IPTable::moveRuleToChain(IPTRule* rule, IPTChain *target_chain"
		                           "rule == 0. This is a bug " );
		m_err->setErrMsg( msg );
		return m_err;
	}
	if ( target_chain == 0 ) {
		m_err->setErrType( KMFError::FATAL );
		const TQString& msg = i18n( "IPTable::moveRuleToChain(IPTRule* rule, IPTChain *target_chain"
		                           "target_chain == 0. This is a bug " );
		m_err->setErrMsg( msg );
		return m_err;
	}
	// 	kdDebug() << "Copy Rule: " << rule->name() << " from Chain: " <<  rule->chain() ->name() << "to chain" <<  target_chain->name()  << endl;

	IPTRule* new_rule = target_chain->addRule( i18n( "%1_Copy" ).arg( rule->name() ), m_err );
	if ( m_err->errNum() == 0 ) {
		rule->createRuleClone( new_rule );
	}
	return m_err;
}

const TQDomDocument& IPTable::getDOMTree() {
	// 	kdDebug() << "const TQString& IPTChain::getIDOMree( )" << endl;
	TQDomDocument doc;
	TQDomElement root = doc.createElement( XML::Table_Element );
	NetfilterObject::saveUuid( root );
	root.setAttribute(  XML::Name_Attribute, name() );
	root.setAttribute( XML::Description_Attribute, description() );

	TQPtrListIterator<IPTChain> it ( m_chains );
	IPTChain *chain = 0;
	while ( ( chain = it.current() ) != 0 ) {
		++it;
		if ( chain ) {
			root.appendChild( chain->getDOMTree( ) );
			// 				kdDebug() << "Got XML for Rule:  " << *chain->name() << endl;
		} else {
			kdDebug() << "Rule == 0"<< endl;
		}
	}
	doc.appendChild( root );
	return *( new TQDomDocument( doc ) );
}

void IPTable::loadXML( const TQDomDocument& doc, TQStringList& errors ) {
	kdDebug() << "void IPTable::loadXML( const TQDomDocument& )" << endl;
 	TQDomElement root = doc.documentElement();
 	loadXML( root, errors );
}
void IPTable::loadXML( TQDomNode root, TQStringList& errors ) {
	setName( name() );
	NetfilterObject::loadUuid ( root, errors );
//	TQDomElement root = doc.documentElement();
	TQDomNode curr = root.firstChild();
	TQPtrList<IPTChain> used_chains;
	while ( !curr.isNull() ) {
		// 		kdDebug() << "Parsing Node: " << curr.nodeName() << endl;
		if ( curr.isElement() && curr.nodeName() == XML::Chain_Element ) {
			TQString name = curr.toElement().attribute( XML::Name_Attribute );
			TQString uuid = curr.toElement().attribute( XML::Uuid_Attribute );
			TQUuid chain_uuid( uuid );
			
			// 			kdDebug() << "IPTable: Start Parsing Chain: " <<  name << endl;
			TQDomDocument chain_xml;
			chain_xml.appendChild( curr.cloneNode(true) );
			IPTChain* chain = 0;
			chain = chainForUuid( chain_uuid );
			if ( ! chain ) {
				chain = chainForName( name );
				if ( ! chain ) {
					chain = addChain( name ,*( new TQString( "ACCEPT" ) ), false, m_err );
					if ( m_err->errNum() != 0 ) {
						kdDebug() << "ERROR: Couldn't create Chain: " << name << endl;
						return;
					}
				}
			}
			chain->loadXML( chain_xml, errors );
			used_chains.append( chain );
			// 			kdDebug() << "IPTable: Finished Parsing Chain: " <<  name  << endl;
		}
		curr = curr.nextSibling();
	}

	TQPtrListIterator<IPTChain> it ( m_chains );
	while ( it.current() ) {
		// 		kdDebug() << "IPTChain::xloadXML() cleanup loop." << endl;
		IPTChain *chain = it.current();
		TQPtrListIterator<IPTChain> it2 ( used_chains );
		bool found = false;
		while ( it2.current() ) {
			IPTChain *chain2 = it2.current();
			++it2;
			if ( chain2 == chain )
				found = true;
		}
		if ( ! found ) {
			m_err = delChain( chain );
			if ( m_err->errNum() != 0 )
				++it;
		} else {
			++it;
		}
	}
	changed();
}

}
