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

/***************************************************************************
 *                                                                         *
 *   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 <qstring.h>
#include <qregexp.h>

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

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

IPTable::IPTable( KMFIPTDoc* doc, const QString& name ) : NetfilterObject( doc ) {
	//  kdDebug() << "IPTable::IPTable(QString& name)" << endl;
	kmfdoc = doc;
	m_object_type = NetfilterObject::TABLE;
	m_err = new KMFError();
	m_chains.setAutoDelete( true );
	setName( name );
	m_parent = 0;
}

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

void IPTable::reset() {
	// 	kdDebug() << "void IPTable::resetTable()" << endl;
	QPtrListIterator<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;
	QString target = "ACCEPT";
	IPTChain* chain = 0;
	if ( m_name == "filter" ) {

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


		chain = addChain(  *( new QString("INPUT") ), 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(  *( new QString( "OUTPUT" ) ), 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(  *( new QString("FORWARD") ), 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 ( m_name == "nat" ) {
		setDescription( i18n("This table is made for every kind of\n"
		                     "NAT (Network Address Translation).") );

		chain = addChain(  *( new QString("OUTPUT") ), 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(  *( new QString("PREROUTING") ), target, true, m_err );
		if ( chain )
			chain->setDescription( i18n( "..." ) );

		chain = addChain(  *( new QString("POSTROUTING") ), target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}

	} else if ( m_name == "mangle" ) {
		setDescription( i18n("This table is made for altering packets.") );

		chain = addChain( *( new QString("INPUT") ), 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(  *( new QString("OUTPUT") ), 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(  *( new QString("FORWARD") ), 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(  *( new QString("PREROUTING") ), target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}
		chain = addChain(  *( new QString("POSTROUTING") ), target, true, m_err );
		if ( chain ) {
			chain->setDescription( i18n( "..." ) );
		}
	}
}


IPTChain* IPTable::chainForName( QString& 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::chainForID( int id ) {
	IPTChain * tmp_chain;
	for ( tmp_chain = m_chains.first();tmp_chain; tmp_chain = m_chains.next() ) {
		if ( tmp_chain->objectID() == id )
			return tmp_chain;
	}
	return 0;
}

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

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

	QPtrListIterator<IPTChain> it ( m_chains );
	while ( it.current() ) {
		IPTChain* tmp_chain = it.current();
		++it;
		QString found_name = tmp_chain->name();
		if ( found_name == chain_name ) {
			const QString 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( m_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,builtin );
	if ( builtin && chain_target != QString::null )
		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();
	QString name = chain->name();
	if ( chain->isBuildIn() ) {
		const QString 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 ) {
		const QString msg = i18n( "Cannot delete nonexistent chain" );
		m_err->setErrMsg( msg );
		m_err->setErrType( KMFError::NORMAL );
		return m_err;
	} else {
		m_chains.remove( index );
		const QString 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 QString& 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 QString& 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 );
		changed();
	}
	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 QString& 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 QString& 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 );
		changed();
	}
	return m_err;
}

const QDomDocument& IPTable::getDOMTree() {
	// 	kdDebug() << "const QString& IPTChain::getIDOMree( )" << endl;
	QDomDocument doc;
	QDomElement root = doc.createElement( "table" );
	root.setAttribute( "id", m_object_id );

	root.setAttribute( "name", m_name );
	root.setAttribute( "description", m_desc );

	QPtrListIterator<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 QDomDocument( doc ) );
}

void IPTable::loadXML( const QDomDocument& doc ) {
	// 	kdDebug() << "void IPTable::loadXML( const QDomDocument& doc )" << endl;
	// 	kdDebug() << "Parsing XML:\n" << doc.toString() << endl;
	setName(m_name);
	QDomElement root = doc.documentElement();
	QDomNode curr = root.firstChild();
	QPtrList<IPTChain> used_chains;
	while ( !curr.isNull() ) {
		// 		kdDebug() << "Parsing Node: " << curr.nodeName() << endl;
		if ( curr.isElement() && curr.nodeName() == "chain" ) {
			QString name = curr.toElement().attribute( "name" );
			QString id = curr.toElement().attribute( "id" );
			bool ok;
			int chain_id = id.toInt(&ok);
			if ( ! ok )
				return;

			// 			kdDebug() << "IPTable: Start Parsing Chain: " <<  name << endl;
			QDomDocument chain_xml;
			chain_xml.appendChild( curr.cloneNode(true) );
			IPTChain* chain = 0;
			chain = chainForID( chain_id );
			if ( ! chain ) {
				chain = chainForName( name );
				if ( ! chain ) {
					chain = addChain( name ,*( new QString( "ACCEPT" ) ), false, m_err );
					if ( m_err->errNum() != 0 ) {
						kdDebug() << "ERROR: Couldn't create Chain: " << name << endl;
						return;
					}
				}
			}
			chain->loadXML( chain_xml );
			used_chains.append( chain );
			// 			kdDebug() << "IPTable: Finished Parsing Chain: " <<  name  << endl;
		}
		curr = curr.nextSibling();
	}

	QPtrListIterator<IPTChain> it ( m_chains );
	while ( it.current() ) {
		// 		kdDebug() << "IPTChain::xloadXML() cleanup loop." << endl;
		IPTChain *chain = it.current();
		QPtrListIterator<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();
}


