/***************************************************************************
 begin                : Mon Jan 28 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 "qptrlist.h"
#include "qstring.h"
#include "qmessagebox.h"

// kde includes
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
//my includes
#include "kmfdoc.h"
#include "iptchain.h"
#include "iptrule.h"
#include "iptable.h"
#include "kmferror.h"

IPTChain::IPTChain( IPTable* table, const QString& name,  bool buildin ) : NetfilterObject( table ) {
	// 	kdDebug() << "IPTChain::IPTChain(const QString& name,IPTable* table,bool buildin)" << endl;
	m_err = new KMFError();
	m_name = "UNDEFINED";
	m_object_type = NetfilterObject::CHAIN;
	setName( name );
	setTable( table );
	is_build_in_chain = false;
	has_default_target = false;
	setBuildIn( buildin );
	enable_log = false;
	m_ruleset.setAutoDelete( true );
	m_cmd_default_target = "UNDEFINED";
	m_cmd_chain_definition = "UNDEFINED";
	m_log_limit = "UNDEFINED";
	m_log_prefix = "UNDEFINED";
	m_log_burst = "UNDEFINED";
	setUsed( true );
	// 	kdDebug() << "Setup Chain:" << name << " with Object ID: " << m_object_id << endl;
}

IPTChain::~IPTChain() {
	kdDebug() << "\n\nIPChain::~IPTChain()" << endl;
	m_ruleset.clear();
	delete m_err;
}

void IPTChain::reset() {
	m_ruleset.clear();
}

void IPTChain::setDefaultTarget( const QString& target ) {
	//	kdDebug() << "void IPTChain::setDefaultTarget(const QString& target) " << endl;
	m_default_target = target;
	has_default_target = true;
	changed();
}

void IPTChain::setTable( IPTable* table ) {
	setParent( table );
	m_table = table;
}

IPTable* IPTChain::table() const {
	return m_table;
}

QPtrList<IPTRule>& IPTChain::chainRuleset() const {
	QPtrList<IPTRule>* ret_list = new QPtrList<IPTRule>;
	*ret_list = m_ruleset;
	return *ret_list;
}

void IPTChain::setBuildIn( bool build_in ) {
	is_build_in_chain = build_in;
	if ( is_build_in_chain ) {
		const QString target = "DROP";
		setDefaultTarget( target );
		hasCustomDefaultTarget( false );
	}
}

void IPTChain::hasCustomDefaultTarget( bool has ) {
	if ( is_build_in_chain )
		has_default_target = true;
	else
		has_default_target = has;
	changed();
}

void IPTChain::setUsed( bool valid ) {
	is_used = valid;
	changed();
}

int IPTChain::indexOf( const IPTRule& rule ) {
//	kdDebug() << "int IPTChain::indexOf( IPTRule* " << rule.name() << " ) " << endl;
	return  m_ruleset.find( &rule );
}

IPTRule* IPTChain::addRule( const QString& rule_name, KMFError *err, int index ) {
	kdDebug() << "IPTRule* IPTChain::addRule(QString& " << rule_name << ", KMFError *err) " << endl;

	QString target = "DROP";
	const QString& new_name = rule_name;
	QString name = new_name;

	for ( uint i = 0; i < m_ruleset.count(); i++ ) {
		QString tmp_name = m_ruleset.at( i ) ->name();
		if ( tmp_name == name ) {
			err->setErrType(KMFError::NORMAL);
			err->setErrMsg( i18n( "<qt><p>Unable to add rule: <b>%1</b> into chain: <b>%2</b>.<br>There is already a rule defined with that name. Please try again with another name (must be unique in that chain).</p></qt>" ).arg( tmp_name ).arg( m_name ) );
			return 0;
		}
	}

	IPTRule* new_rule = new IPTRule( this, new_name, target );
	if ( ! new_rule ) {
		err->setErrType(KMFError::FATAL);
		err->setErrMsg( i18n("Sorry, a bad internal error occurred. This is a bug ;-(") );
		return 0;
	}
	err->setErrType(KMFError::OK);
	if ( index == -1 ) {
		kdDebug() << "IPTChain::addRule: appending rule" << endl;
		m_ruleset.append( new_rule );
	} else {
		if ( index < 0 || index > (int) chainRuleset().count() - -1 ) {
			err->setErrType(KMFError::FATAL);
			err->setErrMsg( i18n("Cannot insert rule at %1 This is a bug ;-(").arg( index ) );
			return 0;
		}
		kdDebug() << "IPTChain::addRule: inserting rule at: " << index << endl;
		m_ruleset.insert( index, new_rule  );
	}
// 	regenerateRuleNumbers();
	changed();
	return new_rule;
}


KMFError* IPTChain::delRule( IPTRule* rule ) {
	kdDebug() << "KMFError* IPTChain::delRule( IPTRule* rule )" << endl;
	if ( m_ruleset.count() < 1 )
		return false;

	QString name = rule->name();
	if ( m_ruleset.find( rule ) != -1 ) {
		// 		kdDebug() << "Deleteing Rule: " << rule->name() << " ..." <<  endl;
		m_err->setErrType(KMFError::OK );
		if ( ! m_ruleset.remove( rule ) ) {
			rule = 0;
			m_err->setErrType( KMFError::FATAL );
			const QString& msg = i18n( "Sorry, can't find Rule: %1 in the Ruleset of this chain." ).arg( rule->name() );
			m_err->setErrMsg( msg );
			return m_err;
		}
// 		regenerateRuleNumbers();
		changed();
		kdDebug() << "DONE." << endl;
		return m_err;
	} else {
		m_err->setErrType( KMFError::FATAL );
		const QString& msg = i18n( "Sorry, can't find Rule: %1 in the Ruleset of this chain." ).arg( rule->name() );
		m_err->setErrMsg( msg );
		return m_err;
	}
	kdDebug() << "Sorry can't delete Rule: " << rule->name() << ".\nRule not found in Chain !!!" << endl;
	return m_err;
}

bool IPTChain::moveRule( IPTRule* rule, int how_much ) {
	// 	kdDebug() << "void IPTChain::moveRule(IPTRule* rule,int how_much)" << endl;

	int num = m_ruleset.find( rule );
	int new_pos = num + how_much;
	if ( new_pos < 0 || new_pos > ( int ) m_ruleset.count() - 1 ) {
		// 		kdDebug() << "New Position is not Valid: " << new_pos << endl;
		return false;
	} else {
		// 		kdDebug() << "Moving rule to position: " << new_pos << endl;
		IPTRule *tmp_rule = m_ruleset.take( num );
		m_ruleset.insert( new_pos, tmp_rule );
		// regenerateRuleNumbers();
		changed();
		return true;
	}
}

void IPTChain::setDropLogging( bool enabled, QString& limit, QString& burst, QString& prefix ) {
	enable_log = enabled;

	if ( !limit.isEmpty() )
		m_log_limit = limit;
	else
		m_log_limit = "";
	if ( !prefix.isEmpty() )
		m_log_prefix = prefix;
	else
		m_log_prefix = "";
	if ( !burst.isEmpty() )
		m_log_burst = burst;
	else
		m_log_burst = "";
	changed();
}


QPtrList<IPTRule>& IPTChain::chainFwds() {
	// 	kdDebug() << "QPtrList<IPTRule> IPTChain::chainFwds()" << endl;
	QPtrList<IPTRule>* fwds = new QPtrList<IPTRule>;
	QPtrListIterator<IPTRule> it( m_ruleset );
	IPTRule* rule = 0;
	while ( it.current() ) {
		rule = it.current();
		++it;
		QString target = rule->target();
		// FIXME: lots of targets missing !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		if ( !target.isEmpty() && target != "ACCEPT" && target != "DROP" && target != "LOG"
		        && target != "REJECT" && target != "RETURN" && target != "DNAT" && target != "SNAT"
		        && target != "QUEUE" && target != "MIRROR" && target != "REDIRECT" && target != "MASQUERADE" ) {
			//kdDebug() << "Found Forward to chain: " << target << endl;
			fwds->append( rule );
		}
	}
//	kdDebug() << "EXIT: QPtrList<IPTRule>& IPTChain::chainFwds()" << endl;
	return *fwds;
}

QPtrList<IPTRule>& IPTChain::chainFeeds() {
//	kdDebug() << "QPtrList<IPTRule> IPTChain::chainFeeds()" << endl;
	QPtrList<IPTRule>* feeds = new QPtrList<IPTRule>;
	IPTChain* tmp_chain;
	QPtrList<IPTChain> table_chains = table() ->chains();
	QPtrListIterator<IPTChain> it( table_chains );
	while ( it.current() ) {
		tmp_chain = it.current();
		++it;
		//		kdDebug() << "Searching in chain: "  << tmp_chain->name() << endl;
		if ( !tmp_chain->chainRuleset().isEmpty() ) {
			IPTRule * tmp_rule;
			QPtrList<IPTRule> rules = tmp_chain->chainRuleset();
			QPtrListIterator<IPTRule> it2( rules );
			while ( it2.current() ) {
				tmp_rule = it2.current();
				++it2;
				QString target = tmp_rule->target();
				//kdDebug() << "Found Rule: " << tmp_rule->name() << " with target: " << target << endl;
				if ( target == m_name ) {
					feeds->append( tmp_rule );
					//kdDebug() << "Found feed: " << tmp_rule->name() << " from chain: " << tmp_chain->name() << endl;
				}
			}
		}
	}
	// 	kdDebug() << "EXIT: QPtrList<IPTRule>& IPTChain::chainFeeds()" << endl;
	return *feeds;
}

IPTRule* IPTChain::ruleForName( const QString& name ) {
	IPTRule * tmp_rule = 0;
	for ( tmp_rule = m_ruleset.first();tmp_rule; tmp_rule = m_ruleset.next() ) {
		if ( tmp_rule->name() == name ) {
			return tmp_rule;
		}
	}
	return 0;
}

IPTRule* IPTChain::ruleForID( int id ) {
	IPTRule * tmp_rule = 0;
	for ( tmp_rule = m_ruleset.first();tmp_rule; tmp_rule = m_ruleset.next() ) {
		if ( tmp_rule->objectID() == id ) {
			return tmp_rule;
		}
	}
	return 0;

}

QString IPTChain::createIPTablesChainDefinition() {
	QString chainDef = "";
	if ( is_build_in_chain ) {
		return chainDef;
	} else {
		m_cmd_chain_definition = "$IPT -t ";
		m_cmd_chain_definition.append( m_table->name() );
		m_cmd_chain_definition.append( " -N " );
		m_cmd_chain_definition.append( m_name );
		return m_cmd_chain_definition;
	}
}

QString IPTChain::createIPTablesChainDefaultTarget() {
	// 	kdDebug() << "IPTChain::createIPTablesChainDefaultTarget()" << endl;
	m_cmd_default_target = "$IPT -t ";
	m_cmd_default_target.append( m_table->name() );
	if ( is_build_in_chain ) {
		m_cmd_default_target.append( " -P " );
		m_cmd_default_target.append( m_name );
		m_cmd_default_target.append( " " );
	} else {
		m_cmd_default_target.append( " -A " );
		m_cmd_default_target.append( m_name );
		m_cmd_default_target.append( " -j " );
	}
	m_cmd_default_target.append( m_default_target );
	//kdDebug() << "Default Target Cmd: " << m_cmd_default_target << endl;
	return m_cmd_default_target;
}

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

	root.setAttribute( "name", m_name );
	if ( hasDefaultTarget() )
		root.setAttribute( "default_target", m_default_target );

	root.setAttribute( "description", description() );

	if ( isBuildIn() ) {
		root.setAttribute( "builtin", "yes" );
	} else {
		root.setAttribute( "builtin", "no" );
	}

	if ( logging() ) {
		QDomElement logging = doc.createElement( "logging" );
		root.appendChild( logging );
		logging.setAttribute( "prefix", m_log_prefix );
		logging.setAttribute( "limit", m_log_limit );
		logging.setAttribute( "burst", m_log_burst );
	}

	QPtrListIterator<IPTRule> it ( m_ruleset );
	IPTRule *rule = 0;
	while ( ( rule = it.current() ) != 0 ) {
		++it;
		if ( rule) {
			root.appendChild( rule->getDOMTree( ) );
			// 				kdDebug() << "Got XML for Rule:  " << rule->name() << endl;
		} else {
			kdDebug() << "Rule == 0"<< endl;
		}
	}
	doc.appendChild( root );
	return *( new QDomDocument( doc ) );
}

void IPTChain::loadXML( const QDomDocument& doc ) {
	kdDebug() << "\nvoid IPTChain::loadXML( const QDomDocument& doc )" << endl;
	QDomElement root = doc.documentElement();
	QDomNode curr = root.toElement();
	QString target = "";
	QString builtin = "";
	QString desc = "";
	kdDebug() << "Parsing node: " << curr.nodeName() << endl;
	builtin = curr.toElement().attribute( "builtin" );
	if ( !builtin.isEmpty() && builtin == "yes" ) {
		setBuildIn( true );
	} else if ( !builtin.isEmpty() && builtin == "no"  ) {
		setBuildIn( false );
	}

	target = curr.toElement().attribute( "default_target" );
	if ( !target.isEmpty() ) {
		if ( ! isBuildIn() )
			hasCustomDefaultTarget( true );
		setDefaultTarget( *(new QString( target ) ) );
	}


	desc = curr.toElement().attribute( "description" );
	// if ( !desc.isEmpty() ) {
		setDescription( *(new QString( desc ) ) );
		kdDebug() << "Loading chain description: " << desc << endl;
	// }
	kdDebug() << "Loading Chain: " << name() << endl;
	kdDebug() << "Is built in: " << builtin << endl;
	kdDebug() << "Description chain description: " << desc << endl;
	
	curr = curr.firstChild();
	QPtrList<IPTRule> used_rules;
	while ( !curr.isNull() ) {
		kdDebug() << "Parsing Node: " << curr.nodeName() << endl;
		if ( curr.isElement() && curr.nodeName() == "rule" ) {
			QString name = curr.toElement().attribute( "name" );
			QString id = curr.toElement().attribute( "id" );
			bool ok;
			int rule_id = id.toInt(&ok);
			if ( ! ok )
				return;
			// 			kdDebug() << "IPTChain: Start Parsing Rule: " <<  name << endl;
			QDomDocument rule_xml;
			rule_xml.appendChild( curr.cloneNode(true) );
			IPTRule* rule = 0;
			rule = ruleForID( rule_id );
			if ( ! rule ) {
				rule = ruleForName( name );
				if ( ! rule ) {
					rule = addRule( name, m_err );
					if ( ! rule || m_err->errNum() != 0 ) {
						kdDebug() << "ERROR: Couldn't create Rule: " << name << endl;
						kdDebug() << "Error Message: " << m_err->errMsg() << endl;
						return;
					}
				}
				// 				kdDebug() << "IPTChain: Created Rule: " << name << endl;
			}
			rule->loadXML( rule_xml );
			used_rules.append( rule );
			// 			kdDebug() << "IPTChain: Finished Parsing Rule: " <<  name  << endl;
		} else if( curr.isElement() && curr.nodeName() == "logging"  ) {
			QString log_limit = "";
			QString log_burst = "";
			QString log_prefix = "";

			enable_log  = true;
			log_limit = curr.toElement().attribute( "limit" );
			if( !log_limit.isEmpty() )
				m_log_limit = log_limit;

			log_burst = curr.toElement().attribute( "burst" );
			if( !log_burst.isEmpty() )
				m_log_burst = log_burst;

			log_prefix = curr.toElement().attribute( "prefix" );
			if( !log_prefix.isEmpty() )
				m_log_prefix = log_prefix;
		}
		curr = curr.nextSibling();
	}


	QPtrListIterator<IPTRule> it ( m_ruleset );
	while ( it.current() ) {
		// 		kdDebug() << "IPTChain::xloadXML() cleanup loop." << endl;
		IPTRule *rule = it.current();
		QPtrListIterator<IPTRule> it2 ( used_rules );
		bool found = false;
		while ( it2.current() ) {
			IPTRule *rule2 = it2.current();
			++it2;
			if ( rule2 == rule )
				found = true;
		}
		if ( ! found ) {
			m_err = delRule( rule );
			if ( m_err->errNum() != 0 )
				++it;
		} else {
			++it;
		}
	}
// 	regenerateRuleNumbers();
	changed();
}

QPtrList<QStringList>& IPTChain::createIPTablesChainRules() {
	// 	kdDebug() << "QPtrList<QStringList>& IPTChain::createIPTablesChainRules()" << endl;
	QPtrList<QStringList>* all_rules = new QPtrList<QStringList>;
	if ( !m_ruleset.isEmpty() ) {
		//kdDebug() << "------- Chain Rules --------"<< endl;
		IPTRule * rule;
		for ( rule = m_ruleset.first(); rule; rule = m_ruleset.next() ) {
			QString rule_name = rule->name();
			QString rule_cmd = rule->toString();
			QStringList* chainDefs = new QStringList();
			chainDefs->append( rule_name );
			if ( rule->enabled() ) {
				chainDefs->append( rule_cmd );
			} else {
				QString warning = "if [ \"$verbose\" = \"1\" ]; then\n"
				                  "  echo \"Skipping Disabled Rule " + rule_name + "!!!\"\n"
				                  "fi\n true";
				chainDefs->append( warning );
			}
			all_rules->append( chainDefs );
		}
	}
	if ( enable_log ) {
		//kdDebug() << "------- Chain Logging --------"<< endl;
		QString chain_log = "";
		chain_log.append( "$IPT -t " );
		chain_log.append( m_table->name() );
		chain_log.append( " -A " );
		chain_log.append( m_name );
		if ( !m_log_limit.isEmpty() ) {
			chain_log.append( " -m limit --limit " );
			chain_log.append( m_log_limit );
			if ( !m_log_burst.isEmpty() ) {
				chain_log.append( " --limit-burst " );
				chain_log.append( m_log_burst );
			}
		}
		chain_log.append( " -j LOG" );
		if ( !m_log_prefix.isEmpty() ) {
			chain_log.append( " --log-prefix \"" );
			chain_log.append( m_log_prefix );
			chain_log.append( "\"" );
		}
		kdDebug() << chain_log << endl;
		QStringList* chainDefs = new QStringList();
		QString rule_name = i18n( "Chain: %1 Drop Logging" ).arg( m_name );
		chainDefs->append( rule_name );
		chainDefs->append( chain_log );
		all_rules->append( chainDefs );
	}
	//kdDebug() << "------- Chain Default Target --------"<< endl;
	if ( has_default_target && !m_cmd_default_target.isEmpty() ) {
		QString deftg = createIPTablesChainDefaultTarget();
		QStringList* chainDefs = new QStringList();
		QString rule_name = i18n( "Chain: %1 Default Target" ).arg( m_name );
		chainDefs->append( rule_name );
		chainDefs->append( deftg );
		all_rules->append( chainDefs );
	}
	// 	kdDebug() << "Leaving IPTChain::createIPTablesChainRules()" << endl;
	return *all_rules;
}


