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

//qt includes
#include "qstring.h"
#include "qdict.h"

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

IPTRule::IPTRule( IPTChain* tmp_chain, const QString& tmp_name, const QString& tmp_target ) : NetfilterObject( tmp_chain ) {
	m_object_type = NetfilterObject::RULE;
	m_name = "UNDEFINED";
	m_target = "UNDEFINED";
	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( true );
	ipt_cmd = "$IPT";
	tab = "-t";
	ap = "-A";
	ws = " ";
	post = "-j";
}

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

KMFError* IPTRule::setName( const QString& tmp_name ) {
	kdDebug() << "IPTRule::setName( const QString& tmp_name )" << endl;
	QString 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;
	}
	m_name = tmp_name;
	changed();
	return m_err;
}

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

void IPTRule::setTable( IPTable* tmp_table ) {
	m_table = tmp_table;
	//		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 QString& tmp_target ) {
	if ( !tmp_target.isNull() ) {
		m_target = tmp_target;
	}

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

// void IPTRule::setRuleNum( int num ) {
// 	//	kdDebug() << "IPTRule::setRuleNum(int num)" << endl;
// 	m_rule_num = num;
// }

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

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

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

bool IPTRule::isForward() const {
	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;
}

QStringList IPTRule::availableTargets() const {
	kdDebug() << "QStringList availableTargets() const {" << endl;
	QStringList targets;
	QString table = chain() ->table() ->name();
	QString 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 QStringList() );
	}
	// general Targets
	targets << "ACCEPT" << "DROP" << "LOG" << "QUEUE" << "RETURN";
	if ( name == "INPUT" || name == "OUTPUT" || name == "FORWARD" )
		targets << "REJECT";

	if ( name == "INPUT" || name == "OUTPUT" || name == "PREROUTING" )
		targets << "MIRROR";

	if ( table == "nat" && name == "POSTROUTING" )
		targets << "SNAT";

	if ( table == "nat" && ( name == "PREROUTING" || name == "OUTPUT" ) ) {
		targets << "DNAT" << "REDIRECT";
	}

	if ( table == "nat" && name == "POSTROUTING" )
		targets << "MASQUERADE";

	if ( table == "mangle" ) {
		targets << "MARK" << "TOS";
	}

	if ( table == "filter" || table == "nat" || table == "mangle" ) {
		QPtrList<IPTChain> tmp_chains = chain() ->table() ->chains();
		QPtrListIterator<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 QString& 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 );
		option_obj->setOptionType( type );
		m_options.insert( type, option_obj );
		return option_obj;
	} else {
		return option_obj;
	}
}

bool IPTRule::addRuleOption( QString& par_name, QPtrList<QString>& cmds ) {
	//############# start new implementation ##################//
	QString 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 );
		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() ) {
		QStringList args;
		for ( uint i = 0; i < cmds.count(); i++ )
			args << *( new QString( *cmds.at( i ) ) );
		option_obj->loadValues( args );
	} else {
		option_obj->reset();
	}
	// 	kdDebug() << "Rule->getXMLSniplet(): " << getXMLSniplet() << endl;
	changed();
	return true;
}

bool IPTRule::addTargetOption( QString& par_name, QPtrList<QString>& cmds ) {
	//############# start new implementation ##################//
	QString 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 );
		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() ) {
		QStringList args;
		for ( uint i = 0; i < cmds.count(); i++ )
			args << *( new QString( *cmds.at( i ) ) );
		option_obj->loadValues( args );
	} else {
		option_obj->reset();
	}
	// 	kdDebug() << "Rule->getXMLSniplet(): " << getXMLSniplet() << endl;
	changed();
	return true;
}


const QString& IPTRule::toString() {
	// 	kdDebug() << "\nconst QString& 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
	QPtrList<QString>* 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++ ) {
			QString* 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() ) {
					QString 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() ) {
		QString new_line = option_cmd;
		new_line. append( "LOG" );
		new_line. append( " --log-prefix \"Rule " + m_name + ": \"" );
		new_line.simplifyWhiteSpace();
		new_line.append( "\n" );
		option_cmd.prepend( new_line );
	}

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

	QString 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++ ) {
			QString* 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;
					QString 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 QString( option_cmd ) );
}


const QDomDocument& IPTRule::getDOMTree( ) {
	// 	kdDebug() << "const QString& IPTRule::getDOMTree( )" << endl;
	QDomDocument doc;
	QDomElement root = doc.createElement( "rule" );
	root.setAttribute( "id", m_object_id );
	root.setAttribute( "num", ruleNum() );
	root.setAttribute( "name", m_name );
	root.setAttribute( "target", m_target );
	root.setAttribute( "description", m_desc );
	if ( enabled() ) {
		root.setAttribute( "enabled", "yes" );
	} else {
		root.setAttribute( "enabled", "no" );
	}

	if ( customRule() ) {
		root.setAttribute( "custom_rule", "yes" );
	} else {
		root.setAttribute( "custom_rule", "no" );
	}

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

	QPtrList<QString>* available_options = 0;
	available_options = IPTRuleOption::getAvailableOptionTypes();
	if ( available_options ) {
		QPtrListIterator<QString> it ( *available_options );
		QString *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 QDomDocument( doc ) );
}

void IPTRule::loadXML( const QDomDocument& doc ) {
	// 	kdDebug() << "void IPTRule::loadXML( const QDomDocument& doc )" << endl;
	// 	kdDebug() << "Parsing XML:\n" << doc.toString() << endl;
	QDomElement root = doc.documentElement();
	QDomNode curr = root.firstChild();
	QString name = "";
	QString num = "";
	QString logging = "";
	QString desc = "";
	QString target = "";
	QString custom = "";
	QString enabled = "";
	name = root.toElement().attribute( "name" );
	num = root.toElement().attribute( "num" );
	logging = root.toElement().attribute( "logging" );
	custom = root.toElement().attribute( "custom_rule" );
	target = root.toElement().attribute( "target" );
	desc = root.toElement().attribute( "description" );
	enabled = root.toElement().attribute( "enabled" );

	if ( logging == "yes" ) {
		setLogging( true );
	} else {
		setLogging( false );
	}
	if ( enabled == "yes" ) {
		setEnabled( true );
	} else {
		setEnabled( false );
	}

	if ( custom == "yes" ) {
		setCustomRule( true );
	} else {
		setCustomRule( false );
	}

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

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

void IPTRule::createRuleClone( IPTRule* new_rule ) {
	// 	kdDebug() << "IPTRule& IPTRule::createRuleClone()" << endl;
	QString 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() );
	QPtrList<QString>* available_types = IPTRuleOption::getAvailableOptionTypes();
	QString type = "";
	QPtrListIterator<QString> it( *available_types );
	while ( it.current() ) {
		type = *it.current();
		++it;
		IPTRuleOption* opt = getOptionForName( type );
		IPTRuleOption* clone_opt = new_rule->getOptionForName( type );
		clone_opt->loadXML( opt->getDOMTree() );
	}
}

