/***************************************************************************
begin                : Mon Feb 4 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.                                   *
 *                                                                         *
 ***************************************************************************/
//project includes
#include "iptrule.h"
// #include "iptruleoption.h"
#include "iptchain.h"
#include "iptable.h"
#include "kmfdoc.h"
#include "kmferror.h"
#include "kmfcheckinput.h"
#include "xmlnames.h"

//qt includes
#include "tqstring.h"
#include "tqdict.h"

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

namespace KMF {

IPTRule::IPTRule( IPTChain* tmp_chain, const char *name, const TQString& tmp_name, const TQString& tmp_target ) : NetfilterObject( tmp_chain, name ) {
//	m_object_type = NetfilterObject::RULE;
	setName( XML::Undefined_Value );
	m_target = XML::Undefined_Value;
	m_check_input = new KMFCheckInput();
	m_err = new KMFError();
	setChain( tmp_chain );
	setTable( tmp_chain->table() );
	setName( tmp_name );
	setTarget( tmp_target );
	setCustomRule( false );
	m_enabled = true;
	m_log_rule = false;
	m_options.setAutoDelete( false );
	ipt_cmd = "$IPT";
	tab = "-t";
	ap = "-A";
	ws = " ";
	post = "-j";
}

IPTRule::~IPTRule() {
	// 	kdDebug() << "\nIPTRule::~IPTRule()" << endl;
	delete m_check_input;
	delete m_err;
	clear();
}

int IPTRule::type() {
// 	kdDebug() << "IPTRule::type()" << endl;
	return NetfilterObject::RULE;
}

void IPTRule::clear() {
	m_options.setAutoDelete( true );
	m_options.clear();
	m_options.setAutoDelete( false );
}

KMFError* IPTRule::setRuleName( const TQString& tmp_name ) {
	kdDebug() << "IPTRule::setName( const TQString& tmp_name )" << endl;
	TQString inp = tmp_name;
	m_check_input->checkInput( inp, "RULENAME", m_err );
	if ( m_err->errType() != KMFError::OK ) {
		kdDebug() << "-- Name unchanged: invalid name" << tmp_name << endl;
		return m_err;
	}
	NetfilterObject::setName( tmp_name );
	changed();
	return m_err;
}

void IPTRule::setChain( IPTChain* ch ) {
	setParent( ch );
	m_chain = ch;
	changed();
}

void IPTRule::setTable( IPTable* tmp_table ) {
	m_table = tmp_table;
	changed();
	//		kdDebug() << "Rule Table: " << *m_table->name() << endl;
}

void IPTRule::setEnabled( bool on ) {
	m_enabled = on;
	changed();
}

void IPTRule::setLogging( bool on ) {
	m_log_rule = on;
	changed();
}

void IPTRule::setTarget( const TQString& tmp_target ) {
	if ( !tmp_target.isNull() ) {
		m_target = tmp_target;
	}

	TQPtrList<TQString>* available_options = IPTRuleOption::getAvailableOptionTypes();
	for ( uint j = 0;j < available_options->count();j++ ) {
		TQString type = *available_options->at( j );
		IPTRuleOption* opt = 0;
		opt = getOptionForName( type );
		if ( opt && opt->isTargetOption() ) {
			TQStringList args;
			opt->loadValues( args );
		}
	}
	changed();
}

void IPTRule::setCustomRule( bool on ) {
	m_custom_rule = on;
	changed();
}

int IPTRule::ruleNum() const {
	return chain()->indexOf( *this );
	//return m_rule_num;
}

const TQString& IPTRule::target() const {
	return m_target;
}

bool IPTRule::isForward() const {
	//FIXME: Is that sematically correct???
	if ( m_target == "ACCEPT" || m_target == "DROP" ||
	        m_target == "LOG" || m_target == "QUEUE" ||
	        m_target == "RETURN" || m_target == "REJECT" ||
	        m_target == "MIRROR" || m_target == "SNAT" ||
	        m_target == "DNAT" || m_target == "REDIRECT" ||
	        m_target == "MASQUERADE" || m_target == "MARK" ||
	        m_target == "TOS" ) {
		return true;
	} else {
		return false;
	}
}

TQStringList IPTRule::availableTargets() const {
	kdDebug() << "TQStringList availableTargets() const {" << endl;
	TQStringList targets;
	TQString table = chain() ->table() ->name();
	TQString name = chain() ->name();
	if ( table.isEmpty() || name.isEmpty() ) {
		kdDebug() << "KMFRuleEdit::slotAddValidTargets( const IPTChain& chain ): ERROR: name or table not found in table" << endl;
		return *( new TQStringList() );
	}
	// general Targets
	targets << "ACCEPT" << "DROP" << "LOG" << "QUEUE" << "RETURN";
	if ( name == Constants::InputChain_Name || name == Constants::OutputChain_Name || name == Constants::ForwardChain_Name )
		targets << "REJECT";

	if ( name == Constants::InputChain_Name || name == Constants::OutputChain_Name || name == Constants::PreRoutingChain_Name )
		targets << "MIRROR";

	if ( table == Constants::NatTable_Name && name == Constants::PostRoutingChain_Name )
		targets << "SNAT";

	if ( table == Constants::NatTable_Name && ( name == Constants::PreRoutingChain_Name || name == Constants::OutputChain_Name ) ) {
		targets << "DNAT" << "REDIRECT";
	}

	if ( table == Constants::NatTable_Name && name == Constants::PostRoutingChain_Name )
		targets << "MASQUERADE";

	if ( table == Constants::MangleTable_Name ) {
		targets << "MARK" << "TOS";
	}

	if ( table == Constants::FilterTable_Name || table == Constants::NatTable_Name || table == Constants::MangleTable_Name ) {
		TQPtrList<IPTChain> tmp_chains = chain() ->table() ->chains();
		TQPtrListIterator<IPTChain> it( tmp_chains );
		while ( it.current() ) {
			IPTChain * tmp_ch = it.current();
			++it;
			if ( !tmp_ch->isBuildIn() && tmp_ch->name() != name ) {
				// 				kdDebug() << "Adding Chain: " << tmp_ch->name() << "  to Targets. " << endl;
				targets << tmp_ch->name();
			}
		}
	}
	return targets;
}

IPTRuleOption* IPTRule::getOptionForName( const TQString& type ) {
	IPTRuleOption * option_obj;
	option_obj = m_options.find( type );
	if ( option_obj == 0 && !type.stripWhiteSpace().isEmpty()  ) {
		// 		kdDebug() << "Creating new IPTRuleOption( IPTRule* )" << endl;
		option_obj = new IPTRuleOption( this, type.latin1() );
		option_obj->setOptionType( type );
		m_options.insert( type, option_obj );
		return option_obj;
	} else {
		return option_obj;
	}
}

bool IPTRule::addRuleOption( TQString& par_name, TQPtrList<TQString>& cmds ) {
	//############# start new implementation ##################//
	TQString new_par_name = "";
	if ( par_name == "src_ip" || par_name == "dest_ip" ) {
		// 		kdDebug() << "Translating to new option name: " << par_name << " -> ip_opt" << endl;
		new_par_name = "ip_opt";
	} else if ( par_name == "mac" ) {
		// 		kdDebug() << "Translating to new option name: " << par_name << " -> mac_opt" << endl;
		new_par_name = "mac_opt";

	} else {
		new_par_name = par_name;
	}
	if ( new_par_name.stripWhiteSpace().isEmpty() ) {
		return false;
	}
	IPTRuleOption * option_obj;
	option_obj = m_options.find( new_par_name );
	if ( option_obj == 0 ) {
		// 		kdDebug() << "Creating new IPTRuleOption( IPTRule* )" << endl;
		option_obj = new IPTRuleOption( this, new_par_name.latin1() );
		m_options.insert( new_par_name, option_obj );
	} else {
		// 		kdDebug() << "Option: " << new_par_name << "Allready in Dict" << endl;
	}
	kdDebug() << "+ Adding Opiton: " << new_par_name << endl;
	option_obj->setOptionType( new_par_name );
	if ( ! cmds.isEmpty() ) {
		TQStringList args;
		for ( uint i = 0; i < cmds.count(); i++ )
			args << *( new TQString( *cmds.at( i ) ) );
		option_obj->loadValues( args );
	} else {
		option_obj->reset();
	}
	// 	kdDebug() << "Rule->getXMLSniplet(): " << getXMLSniplet() << endl;
	changed();
	return true;
}

bool IPTRule::addTargetOption( TQString& par_name, TQPtrList<TQString>& cmds ) {
	//############# start new implementation ##################//
	TQString new_par_name = "";
	new_par_name = par_name;

	if ( new_par_name.stripWhiteSpace().isEmpty() ) {
		return false;
	}
	
	IPTRuleOption * option_obj;
	option_obj = m_options.find( new_par_name );
	if ( option_obj == 0 ) {
		// 		kdDebug() << "Creating new IPTRuleOption( IPTRule* )" << endl;
		option_obj = new IPTRuleOption( this, par_name.latin1() );
		m_options.insert( new_par_name, option_obj );
		option_obj->setTargetOption( true );
	} else {
		// 		kdDebug() << "Option: " << new_par_name << "Allready in Dict" << endl;
		option_obj->setTargetOption( true );
	}
	option_obj->setOptionType( new_par_name );
	option_obj->setTargetOption( true );
	if ( ! cmds.isEmpty() ) {
		TQStringList args;
		for ( uint i = 0; i < cmds.count(); i++ )
			args << *( new TQString( *cmds.at( i ) ) );
		option_obj->loadValues( args );
	} else {
		option_obj->reset();
	}
	// 	kdDebug() << "Rule->getXMLSniplet(): " << getXMLSniplet() << endl;
	changed();
	return true;
}


const TQString& IPTRule::toString() {
	// 	kdDebug() << "\nconst TQString& IPTRule::toString()" << endl;
	option_cmd = "";
	option_cmd.append( ipt_cmd ); // $IPT
	option_cmd.append( ws );
	option_cmd.append( tab );  // -t
	option_cmd.append( ws );
	option_cmd.append( m_table->name() ); // <TABLE>
	option_cmd.append( ws );
	option_cmd.append( ap ); // -A
	option_cmd.append( ws );
	option_cmd.append( m_chain->name() ); // <CHAIN>
	option_cmd.append( ws );
	// append all normal rule options
	TQPtrList<TQString>* known_types = 0;
	known_types = IPTRuleOption::getAvailableOptionTypes();
	if ( known_types == 0 ) {
		kdDebug() << "ERROR: IPTRuleOption::getAvailableOptionTypes() == 0\n" << endl;
	} else {
		// 		kdDebug() << "Found Number of Types: " << known_types->count() << endl;
		for ( uint i = 0; i < known_types->count(); i++ ) {
			TQString* option_name = 0;
			option_name = known_types->at( i );
			if ( option_name == 0 ) {
				kdDebug() << "ERROR: option_name == 0" << endl;
			} else {
				// 				kdDebug() << "Searching for Option: " << *option_name << endl;
				IPTRuleOption* opt = 0;
				opt = m_options.find( *option_name );
				if ( opt && ! opt->isEmpty() &&  ! opt->isTargetOption() ) {
					TQString option = opt->toString();
					// 					kdDebug() << "Add Option: " << opt->toString() << endl;
					if ( !option.isEmpty() ) {
						option_cmd.append( opt->toString() );
						option_cmd.append( ws );
					}
				}
			}
		}
	}
	option_cmd. append( post );
	option_cmd. append( ws );
	option_cmd.simplifyWhiteSpace();
	if ( logging() ) {
		TQString new_line = option_cmd;
		new_line. append( "LOG" );
		new_line. append( " --log-prefix \"Rule " + name() + ": \"" );
		new_line.simplifyWhiteSpace();
		new_line.append( "\n" );
		option_cmd.prepend( new_line );
	}

	option_cmd. append( m_target );
	option_cmd. append( ws );

	TQString target_options = "";
	if ( known_types == 0 ) {
		kdDebug() << "ERROR: IPTRuleOption::getAvailableOptionTypes() == 0\n" << endl;
	} else {
		// 		kdDebug() << "Found Number of Types: " << known_types->count() << endl;
		for ( uint i = 0; i < known_types->count(); i++ ) {
			TQString* option_name = 0;
			option_name = known_types->at( i );
			if ( option_name == 0 ) {
				kdDebug() << "ERROR: option_name == 0" << endl;
			} else {
				// 				kdDebug() << "Searching for Option: " << *option_name << endl;
				IPTRuleOption* opt = 0;
				opt = m_options.find( *option_name );
				if ( opt && opt->isTargetOption() && ! opt->isEmpty() ) {
					// 					kdDebug() << "Add Option: " << opt->toString() << endl;
					TQString option = "";
					option = opt->toString();
					if ( !option.isEmpty() ) {
						target_options.append( opt->toString() );
						target_options.append( ws );
						target_options.simplifyWhiteSpace();
					}
				}
			}
		}
	}
	option_cmd.append( target_options );

	// 	kdDebug() << "Rule Cmd Line for Rule " << m_name << ": " << option_cmd << endl;
	if ( ! logging() )
		option_cmd.simplifyWhiteSpace();
	return *( new TQString( option_cmd ) );
}


const TQDomDocument& IPTRule::getDOMTree( ) {
	// 	kdDebug() << "const TQString& IPTRule::getDOMTree( )" << endl;
	TQDomDocument doc;
	TQDomElement root = doc.createElement( XML::Rule_Element );
	NetfilterObject::saveUuid( root );
	root.setAttribute( XML::Num_Attribute, ruleNum() );
	root.setAttribute( XML::Name_Attribute, name() );
	root.setAttribute( XML::Target_Attribute, m_target );
	root.setAttribute( XML::Description_Attribute, description() );
	if ( enabled() ) {
		root.setAttribute( XML::Enabled_Attribute, XML::Yes_Value );
	} else {
		root.setAttribute( XML::Enabled_Attribute, XML::No_Value );
	}

	if ( customRule() ) {
		root.setAttribute( XML::CustomRule_Attribute, XML::Yes_Value );
	} else {
		root.setAttribute( XML::CustomRule_Attribute, XML::No_Value );
	}

	if ( logging() ) {
		root.setAttribute( XML::Logging_Attribute, XML::Yes_Value );
	} else {
		root.setAttribute( XML::Logging_Attribute, XML::No_Value );
	}

	TQPtrList<TQString>* available_options = 0;
	available_options = IPTRuleOption::getAvailableOptionTypes();
	if ( available_options ) {
		TQPtrListIterator<TQString> it ( *available_options );
		TQString *type = 0;
		while ( ( type = it.current() ) != 0 ) {
			++it;
			if ( type ) {
				IPTRuleOption * opt = 0;
				opt = m_options.find( *type );
				if ( opt ) {
					root.appendChild( opt->getDOMTree( ) );
					// 					kdDebug() << "Got XML for Rule Option Type: " << *type << endl;
				} else {
					// 					kdDebug() << "No option with type: " << *type << " found. " << endl;
				}
			}
		}
	}
	doc.appendChild( root );
	return *( new TQDomDocument( doc ) );
}

void IPTRule::loadXML( const TQDomDocument& doc, TQStringList& errors ) {
	kdDebug() << "void IPTRule::loadXML( const TQDomDocument& )" << endl;
 	TQDomElement root = doc.documentElement();
 	loadXML( root, errors );
}
void IPTRule::loadXML( TQDomNode root, TQStringList& errors ) {
// void IPTRule::loadXML( const TQDomDocument& doc ) {
	// 	kdDebug() << "void IPTRule::loadXML( const TQDomDocument& doc )" << endl;
	// 	kdDebug() << "Parsing XML:\n" << doc.toString() << endl;
// 	TQDomElement root = doc.documentElement();
	NetfilterObject::loadUuid ( root, errors );
	TQString name = "";
	TQString num = "";
	TQString logging = "";
	TQString desc = "";
	TQString target = "";
	TQString custom = "";
	TQString enabled = "";
	name = root.toElement().attribute( XML::Name_Attribute );
	num = root.toElement().attribute( XML::Num_Attribute );
	logging = root.toElement().attribute( XML::Logging_Attribute );
	custom = root.toElement().attribute( XML::CustomRule_Attribute );
	target = root.toElement().attribute( XML::Target_Attribute );
	desc = root.toElement().attribute( XML::Description_Attribute );
	enabled = root.toElement().attribute( XML::Enabled_Attribute );

	if ( logging == XML::Yes_Value ) {
		setLogging( true );
	} else {
		setLogging( false );
	}
	if ( enabled == XML::Yes_Value ) {
		setEnabled( true );
	} else {
		setEnabled( false );
	}

	if ( custom == XML::Yes_Value ) {
		setCustomRule( true );
	} else {
		setCustomRule( false );
	}

	setTarget( *( new TQString( target ) ) );
	setDescription( *( new TQString( desc ) ) );
	setName( *( new TQString( name ) ) );
/*	bool ok;
	int rule_number = num.toInt( &ok );
	if ( ok )
		setRuleNum( rule_number );*/
	TQPtrList<TQString> *avail_opts = IPTRuleOption::getAvailableOptionTypes();
	TQPtrListIterator<TQString> it ( *avail_opts );
	while ( it.current() ) {
		TQString name = *it.current();
		++it;
		IPTRuleOption *opt = getOptionForName( name );
		if ( opt )
			opt->reset();
	}

	TQDomNode curr = root.firstChild();
	while ( !curr.isNull() ) {
		// 		kdDebug() << "IPTRule: Parsing Node: " << curr.nodeName() << endl;
		if ( curr.isElement() && curr.nodeName() == XML::RuleOption_Element ) {
			TQString type = curr.toElement().attribute( XML::Type_Attribute );
			// 			kdDebug() << "IPTRule: Start Parsing Option: " <<  type << endl;
			TQDomDocument opt_xml;
			opt_xml.appendChild( curr.cloneNode( true ) );
			IPTRuleOption* opt = 0;
			opt = m_options.find( *( new TQString( type ) ) );
			if ( opt == 0 ) {
				TQPtrList<TQString> list;
				list.append( new TQString( "" ) );
				addRuleOption( type, list );
				opt = m_options.find( *( new TQString( type ) ) );
				if ( ! opt ) {
					kdDebug() << "ERROR: Couldn't create Option: " << type << endl;
					return ;
				}
				// 				kdDebug() << "IPTRule: Created Option: " << type << endl;
			}
			opt->loadXML( opt_xml, errors );
			// 			kdDebug() << "IPTRule: Finished Parsing Option: " <<  type  << endl;
		}
		curr = curr.nextSibling();
	}
	changed();
}

void IPTRule::createRuleClone( IPTRule* new_rule ) {
	// 	kdDebug() << "IPTRule& IPTRule::createRuleClone()" << endl;
	TQString na = name();
	if ( na.length() > 15 ) {
		na = na.left( 15 ) ;
	}
	new_rule->setCustomRule( m_custom_rule );
	new_rule->setDescription( description() );
	new_rule->setLogging( logging() );
	new_rule->setEnabled( enabled() );
	new_rule->setTarget( target() );
	TQPtrList<TQString>* available_types = IPTRuleOption::getAvailableOptionTypes();
	TQString type = "";
	TQPtrListIterator<TQString> it( *available_types );
	while ( it.current() ) {
		type = *it.current();
		++it;
		IPTRuleOption* opt = getOptionForName( type );
		IPTRuleOption* clone_opt = new_rule->getOptionForName( type );
		// FIXME: Check For Error
		TQStringList *errors = new TQStringList();
		clone_opt->loadXML( opt->getDOMTree(), *errors );
	}
}

}
