/*
 * Sniff network traffic to guess network data, and print it out as
 * an /etc/network/interfaces configuration profile
 *
 * Copyright (C) 2003  Enrico Zini <enrico@debian.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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#define APPNAME PACKAGE
#else
#warning No config.h found: using fallback values
#define APPNAME __FILE__
#define VERSION "unknown"
#endif

#include "scanner/TrafficScanner.h"
#include "Environment.h"
#include "IFace.h"

#include <wibble/sys/mutex.h>

#include <stdio.h>
#include <ctype.h>
#include <errno.h>	/* errno */

#include <string.h>     // memcpy
#include <sys/types.h>  // socket
#include <sys/socket.h> // socket
#include <sys/ioctl.h>  // ioctl
#include <net/if.h>
#include <unistd.h>     // close

#include <set>
#include <iostream>
#include <iomanip>
#include <sstream>

#include <wibble/commandline/parser.h>

namespace wibble {
namespace commandline {

struct GuessnetOptions : public StandardParserWithManpage
{
public:
	BoolOption* verbose;
	BoolOption* debug;
	IntOption* timeout;
	IntOption* inittime;

	GuessnetOptions() 
		: StandardParserWithManpage(APPNAME, VERSION, 8, "enrico@enricozini.org")
	{
		usage = "[options] [iface]";
		description = "Guess the current network location";

		verbose = add<BoolOption>("verbose", 'v', "verbose", "",
						"enable verbose output");
		debug = add<BoolOption>("debug", 0, "debug", "",
						"enable debugging output (including verbose output)");
		timeout = add<IntOption>("timeout", 't', "timeout", "seconds",
						"timeout (in seconds) used to wait for response packets"
						" (defaults to 5 seconds)");
		inittime = add<IntOption>("inittime", 0, "init-timeout", "seconds",
						"time (in seconds) to wait for the interface to initialize"
						" when not found already up (defaults to 3 seconds)");
	}
};

}
}

using namespace std;
using namespace wibble::sys;

class MainScanner
{
protected:
	Mutex waitMutex;
	Condition waitCond;
	string name;

	// Scanning services
	NetSender sender;
	NetWatcher watcher;

	TrafficScanner trafficScanner;

	int cand_count;

	inline string fmt_ip(unsigned int ip) throw ()
	{
		unsigned char ipfmt[4];
		memcpy(ipfmt, &ip, 4);
		stringstream str;
		str << (int)ipfmt[0] << '.' << (int)ipfmt[1] << '.' << (int)ipfmt[2] << '.' << (int)ipfmt[3];
		return str.str();
	}

	inline string fmt_mac(long long int mac) throw ()
	{
		unsigned char macfmt[6];
		memcpy(macfmt, &mac, 6);
		stringstream str;
		str << hex << setfill('0') << setw(2)
			<< (int)macfmt[0] << ':' << (int)macfmt[1] << ':' << (int)macfmt[2] << ':'
			<< (int)macfmt[3] << ':' << (int)macfmt[4] << ':' << (int)macfmt[5];
		return str.str();
	}

	unsigned int netaddr_to_netmask(unsigned int na) throw ()
	{
		unsigned int res = 0;
		na = ntohl(na);

		// Count the trailing zeros
		int i = 0;
		for ( ; i < 32 && (na & (1 << i)) == 0; i++)
			;

		// Add 1s to the start of res
		for ( ; i < 32; i++)
			res |= (1 << i);

		return htonl(res);
	}

public:
	MainScanner() :
		sender(Environment::get().iface()),
		watcher(Environment::get().iface()),
		trafficScanner(sender),
		cand_count(0)
	{
		watcher.addEthernetListener(&trafficScanner);
	}

	int candidateCount() const throw () { return cand_count; }
	
	void shutdown()
	{
		watcher.shutdown();
	}

	void printResults() throw ()
	{
		string s;
		cout << "iface <name> inet static" << endl;
		cout << "\taddress <addr>" << endl;

		unsigned int network = trafficScanner.guessed_netaddr;
		s = fmt_ip(network);
		cout << "\tnetwork " << s << endl;

		unsigned int netmask = netaddr_to_netmask(network);
		s = fmt_ip(netmask);
		cout << "\tnetmask " << s << endl;

		s = fmt_ip(network | ~netmask);
		cout << "\tbroadcast " << s << endl;

		for (set<TrafficScanner::mac_key>::const_iterator i = trafficScanner.guessed_gateways.begin();
				i != trafficScanner.guessed_gateways.end(); i++)
		{
			TrafficScanner::HostData& od = trafficScanner.scanData[*i];

			s = fmt_ip(od.addr);
			cout << "\tgateway " << s << endl;
		
			string m = fmt_mac(*i);
			cout << "\ttest-peer address " << s << " mac " << m << endl;
		}
	}
};

int main (int argc, const char *argv[])
{
	// Access the interface
	try {
	wibble::commandline::GuessnetOptions opts;

	// Process the commandline
	if (opts.parse(argc, argv))
		return 0;

	// Set verbosity
	Environment::get().verbose(opts.verbose->boolValue());
	Environment::get().debug(opts.debug->boolValue());

	// Check user id
	if (geteuid() != 0)
		fatal_error("You must run this command as root.");

	// Find out the interface to be tested
	if (opts.hasNext())
		Environment::get().iface(opts.next());

	// Find out the test timeout
	if (opts.timeout->boolValue())
		Environment::get().timeout(opts.timeout->intValue());

	// Find out the init timeout
	if (opts.inittime->boolValue())
		Environment::get().initTimeout(opts.inittime->intValue());


	IFace iface(Environment::get().iface());

	bool iface_was_down;
	if_params saved_iface_cfg;

	try {
		// Install the handler for unexpected exceptions
		wibble::exception::InstallUnexpected installUnexpected;
		//FILE* input = 0;

		/* Check if we have to bring up the interface; if yes, do it */
		//iface_was_down = iface_init(Environment::get().iface(), op_init_time);
		iface.update();
		iface_was_down = !iface.up();
		if (iface_was_down)
			saved_iface_cfg = iface.initBroadcast(Environment::get().initTimeout());

		// Let the signals be caught by some other process
		sigset_t sigs, oldsigs;
		sigfillset(&sigs);
		sigdelset(&sigs, SIGFPE);
		sigdelset(&sigs, SIGILL);
		sigdelset(&sigs, SIGSEGV);
		sigdelset(&sigs, SIGBUS);
		sigdelset(&sigs, SIGABRT);
		sigdelset(&sigs, SIGIOT);
		sigdelset(&sigs, SIGTRAP);
		sigdelset(&sigs, SIGSYS);
		// Don't block the termination signals: we need them
		sigdelset(&sigs, SIGTERM);
		sigdelset(&sigs, SIGINT);
		sigdelset(&sigs, SIGQUIT);
		pthread_sigmask(SIG_BLOCK, &sigs, &oldsigs);

		// Scanning methods
		MainScanner scanner;

		debug("Started test subsystems\n");

		sleep(Environment::get().timeout());

		scanner.printResults();

		/*
		// Wait for the first result from the tests
		string profile;
		if (scanner.candidateCount() > 0)
			profile = scanner.getResult(Environment::get().timeout() * 1000);
		*/

		// Shutdown the tests
		scanner.shutdown();

		// We've shutdown the threads: restore original signals
		pthread_sigmask(SIG_SETMASK, &oldsigs, &sigs);
	} catch (std::exception& e) {
		fatal_error("%s", e.what());
		return 1;
	}

	/* Bring down the interface if we need it */
	if (iface_was_down)
		iface.setConfiguration(saved_iface_cfg);

	} catch (std::exception& e) {
		fatal_error("%s", e.what());
		return 1;
	}

	return 0;
}

// vim:set ts=4 sw=4:
