#!/usr/bin/perl
# This file is part of PWHOIS.
# #
# # The PWHOIS software provided in this Distribution is
# # Copyright 2005 VOSTROM Holdings, Inc.
# #
# # The full text of our legal notices is contained in the file called
# # COPYING, included with this Distribution
use strict;
use DBI;
use Time::Format qw(time_format time_strftime time_manip);
use Getopt::Long;
use Log::Dispatch;
use Log::Dispatch::Screen;
use Log::Dispatch::File;
use Net::Patricia;
use Net::Socket::NonBlock;
use POSIX;

package Pwhoisd;

# globals
my $DEBUG = 'false';		# force debugging on: automatically turned on if verbose>=2
my $VERSION = '1.0.1.25';	# the version number to print out at various screens
my $PROGNAME = 'Prefix WhoIs';	# title of our program
my $COPYRIGHT = 'Copyright (c) 2005 VOSTROM Holdings, Inc.'; # copyright holder

# DO NOT MODIFY UNLESS YOU KNOW WHAT YOU ARE DOING: EDIT THE CONFIG FILE IF POSSIBLE
# See: /etc/pwhois/pwhoisd.conf or -c option to specify different config file

my $DEFAULT_CONFIG = '/etc/pwhois/pwhoisd.conf';
my $DEFAULT_WHOIS_PORT=43;
my $DEFAULT_MAX_QUERIES=1000;
my $DEFAULT_PIDFILE='/var/run/pwhoisd.pid';
my $DEFAULT_DATABASE_TYPE = 'postgres';
my $DEFAULT_DATABASE_SERVER = '';
my $DEFAULT_DATABASE_USER = 'pwhois';
my $DEFAULT_DATABASE_PASSWD = '';
my $DEFAULT_DATABASE_NAME = 'pwhois';
my $DEFAULT_UID=65334;
my $DEFAULT_GID=65334;


# DO NOT EDIT BELOW THIS LINE

my $dbh;
my $dsn = 'NA';		# do not edit: these are changed later from defaults or config
my $user = 'NA';	# do not edit: these are changed later from defaults or config
my $password = 'NA';    # do not edit: these are changed later from defaults or config

my $select_from_bgp_routes_sth;
my $select_from_bgp_routes_range_sth;
my $select_from_bgp_routes_by_prefix_sth;
my $select_from_bgp_routes_by_prefix_nocidr_sth;
my $select_from_bgp_routes_by_source_as_sth;
my $select_from_pwhois_acl_sth;
my $select_from_bgp_routes_next_hops_sth;

my $server_connections_paused=0;

my %routes = ();  # routing table (hash)
my %peers = ();   # peer routers
my %config = ();  # configuration options

my $pt = new Net::Patricia;	# RIB/FIB (fast search)
my %owner = ();
my %connections = ();		# connection tracking
my %requests = ();		# request tracking (sessions)
my $socket;
my $last_table_update;		# date of last RIB update
my $program_start_date;		# date program started


# enviroment

$ENV{'TZ'} = 'GMT';

#log handle
my $log;
my %opt = ();

# install signal handlers
$SIG{HUP} = \&reload;
$SIG{INT} = \&shutdown;
$SIG{TERM} = \&shutdown;
$SIG{KILL} = \&shutdown;
$SIG{QUIT} = 'IGNORE';    # ignore SIGQUIT
$SIG{__WARN__} =\&handle_warn;
#$SIG{__DIE__} = \&handle_error;

sub new {
   my $self = {};
   my $class = shift;
   return bless _init($self), $class
}

sub _init {
   my $self = shift;

   processArgs();
  # connect to the database
   openConfigFile();
   openDatabase();
   return $self
}

sub processArgs
{
        # change our process name in the PS list
        $0 = "pwhoisd";

        Getopt::Long::Configure('no_ignore_case');
        Getopt::Long::GetOptions(\%opt, 'help|h', 'logfile|l=s', 
					'version|V', 'verbose|v+', 
					'configfile|c=s',
					'port|p=s', 'bind|b=s', 
					'uid|u=n', 'gid|g=n',
					'daemon|d', 'pidfile=s',
					'limit-max-queries|mq=n',
					'no-load'		# testing
                ) or die "Can't parse command-line options";
        usage() if $opt{help};


	# config file
	if(!defined($opt{'configfile'})) {
		$opt{'configfile'} = $DEFAULT_CONFIG;
	}

        # set version output to 0
        if(!defined($opt{verbose})) {
                $DEBUG = 'false';
		$opt{verbose} = 0;
        }
	else
	{
		if($opt{verbose} >= 2) {
                        $DEBUG = 'true';
                }
	}	

	if($opt{logfile}) {
	        #  open our log file ... nothing should be written to stdout or stderr
        	# from now on

		if(! -f $opt{logfile}) {
			system("touch ".$opt{logfile});
		}        	
		$log = Log::Dispatch::File->new( name      => 'file1',
                                       min_level => 'debug',
                                       filename  => "$opt{logfile}",
                                       mode      => 'append');
                                       
        }
        else
        {
                  $log = Log::Dispatch::Screen->new( name      => 'screen',
                                                      min_level => 'debug',
                                                      stderr    => 0 );
        }

	if(!defined($opt{'bind'})) {
		$opt{'bind'} = '';
	}

	# set the default port listenn on
	if(!defined($opt{'port'})) {
		$opt{'port'} = $DEFAULT_WHOIS_PORT;
	}

	if(!defined($opt{'uid'})) {
		$opt{'uid'} = $DEFAULT_GID;
	}

	if(!defined($opt{'gid'})) {
		$opt{'gid'} = $DEFAULT_UID;
	}

	# set the limits on maximum queries per host IP/per day
	if(!defined($opt{'limit-max-queries'})) {
		$opt{'limit-max-queries'} = $DEFAULT_MAX_QUERIES;
	}

	if($opt{version}) {
                print getVersion(1) . "\n";
                exit;
        }
	
	if($opt{daemon}) {
                 daemonize();
	}
	$program_start_date = time();
        $log->log( level => 'info', message => "$0 started at ". getDateTimeFormat(time()) ."\n") if $opt{verbose};
}


sub usage
{
        print "usage: $0 [*options*]\n\n",
              "  -h, --help         display this help and exit\n",
              "  -v, --verbose      be verbose about what you do (add more -v's to increase verbosity: above v=2 is considered debug)\n",
              "  -V, --version      output version information and exit\n",
              "  -l, --logfile f    write misc progress output to logfile instead of stdout\n",
	      "  -c, --configfile f   read startup settings from configuration file: default is $DEFAULT_CONFIG\n",
	      "  -d, --daemon       start in the background\n",
	      "  --pidfile	    use alternative PID file location: default is /var/run/pwhoisd.pid\n",
	      "  -p, --port <n>	    port number to listen on: defaults to 43\n",
	      "  --b|bind <ip>      the IP address to bind on: defaults to all interfaces (*)\n",
	      "  -u, --uid          the effective user to run as \n",
	      "  -g, --gid          the effective group to run as \n",
	      "  --limit-max-queries <n>  The maximum number of queries (per IP/per day) default is $DEFAULT_MAX_QUERIES\n";
	      "  --no-load          Do not load data -- for testing purposes\n";
        exit;
}

sub daemonize()
{
	# create default options
	if(!defined($opt{pidfile})) {
		$opt{pidfile} = $DEFAULT_PIDFILE;
	}

	# fork and save the process ID
 	defined(my $pid = fork) or die "$0: can't fork: $!";
 	if($pid) {
		# parent
		open PIDFILE, ">".$opt{pidfile}
		or die "$0: can't write to ". $opt{pidfile} .": $!\n";
		print PIDFILE "$pid\n";
		close(PIDFILE);
		exit;
	}
	# child
	POSIX::setsid or die "$0: can't start a new session: $!";
}


# error or warning occurred, bail out
sub handle_warn($)
{
        my $msg = shift;
	if(defined($log)) {
	        $log->log(level=>'warning', message=>"Warning: $msg\n") if $opt{verbose} >= 1;
	}
	else
	{
	        print STDERR "Warning: $msg\n" if $opt{verbose} >= 1;

	}
}

sub handle_error($)
{
        my $msg = shift;
	if(defined($log)) {
	        $log->log(level=>'error', message=>"Error: $msg\n") if $opt{verbose} >= 1;
	}
	else
	{
	        print STDERR "Error: $msg\n" if $opt{verbose} >= 1;

	}
        &shutdown;
}

sub shutdown
{
	if(defined($log)) {
        	$log->log(level=>'info', message=>"$0 shutdown requested.\n") if $opt{verbose};
	}
        closeDatabase();
	# close our listening socket
	foreach my $con (keys %connections) {
		$socket->Close($con);
	}
	$socket->Close() if defined($socket);
	undef $socket;
        exit(0);
}

sub reload()
{
	$log->log(level=>"info", message=>getCurrentSyslogDateTime() ." $0 refresh signal received.  Reloading database\n") if $opt{verbose} >= 1;

	# wait for any existing connections to complete?
	# pause communication

	$server_connections_paused=1;
	load_data();
	$server_connections_paused=0;
}


sub openConfigFile()
{
	if(defined($opt{'configfile'})) {
		if(-r $opt{'configfile'}) {
			open(CONFIG, $opt{'configfile'}) or die "Can't open config file: $!";
			while(<CONFIG>) {
				
				next if $_ =~ /^\s*#.*$/;  # skip comments
				next if $_ =~ /^\s+$/;	# skip blank lines
				
				my ($name,$value) = ($_ =~ /^\s*([A-z0-9_\-.]+) ?= ?"?([^"\r\n]*)"? ?$/); 
				$log->log(level=>"info", message=>"Found setting $name=$value\n") if $opt{verbose} >=1;
				
				$config{$name} = $value;				
			}
			close(CONFIG);
		}
	}
	else
	{
						
	}
	
	# configure based upon configuration settings
	
	my $database = $DEFAULT_DATABASE_TYPE;
	my $dbserver = $DEFAULT_DATABASE_SERVER;
	my $dbuser = $DEFAULT_DATABASE_USER;
	my $dbpasswd = $DEFAULT_DATABASE_PASSWD;
	my $dbname = $DEFAULT_DATABASE_NAME;
	
	if(defined($config{'db.type'})) {
		$database = $config{'db.type'};	
	}
	
	if(defined($config{'db.server'})) {
		$dbserver = $config{'db.server'};	
	}
	
	if(defined($config{'db.name'})) {
		$dbname = $config{'db.name'};	
	}
	
	if(defined($config{'db.user'})) {
		$dbuser = $config{'db.user'};
		$user = $dbuser;	
	}

	if(defined($config{'db.password'})) {
		$dbpasswd = $config{'db.password'};
		$password = $dbpasswd;
	}
	
	# configure the DSN
	
	if($database eq 'postgres' or 
	   $database eq 'pgsql' or 
	   $database eq 'postgresql')
	{
		$dsn = "dbi:Pg:dbname=$dbname";
	}
	elsif($database eq 'mysql')
	{
		$dsn = "dbi:mysql:dbname=$dbname:$dbserver";		
	}
	else
	{
		$log->log(level=>"error", message=>"Invalid database type specified '$database'\n");
		exit(-1);
	}
	
	
	# configure other settings (config-file options override command-line)
	if(defined($config{'pwhoisd.verbose'}))
	{
		if($config{'pwhoisd.verbose'} =~ /\d+/) {
			if($config{'pwhoisd.verbose'} > $opt{verbose}) {	
				$opt{'verbose'} = $config{'pwhoisd.verbose'};
			}
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.verbose'\n");
			exit(-1);
		}
	}
	
	
	if(defined($config{'pwhoisd.logfile'}))
	{
		if($config{'pwhoisd.logfile'} =~ /[A-z0-9\-_\/ \.]+/) {
			$opt{'logfile'} = $config{'pwhoisd.logfile'};

  		if($opt{logfile}) {
                	#  open our log file ... nothing should be written to stdout or stderr
                	# from now on

                if(! -f $opt{logfile}) {
                        	system("touch ".$opt{logfile});
                	}
                	$log = Log::Dispatch::File->new( name      => 'file1',
                        	               		min_level => 'debug',
                                	       		filename  => "$opt{logfile}",
                                   	 	   	mode      => 'append');

        	}
        	else
        	{
                  	$log = Log::Dispatch::Screen->new( name      => 'screen',
                        	                              min_level => 'debug',
                                	                      stderr    => 0 );
        		}
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.logfile'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.pidfile'}))
	{
		if($config{'pwhoisd.pidfile'} =~ /[A-z0-9\-_\/ \.]+/) {
			$opt{'pidfile'} = $config{'pwhoisd.pidfile'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.pidfile'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.bind'}))
	{
		if($config{'pwhoisd.bind'} =~ /\d{1,3}\.\d{1.3}\.\d{1,3}\.\d{1.3}/) {
			$opt{'bind'} = $config{'pwhoisd.bind'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.bind'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.port'}))
	{
		if($config{'pwhoisd.port'} =~ /\d+/) {
			$opt{'port'} = $config{'pwhoisd.port'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.port'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.uid'}))
	{
		if($config{'pwhoisd.uid'} =~ /\d+/) {
			$opt{'uid'} = $config{'pwhoisd.uid'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.uid'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.gid'}))
	{
		if($config{'pwhoisd.gid'} =~ /\d+/) {
			$opt{'gid'} = $config{'pwhoisd.gid'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.gid'\n");
			exit(-1);
		}
	}
	
	if(defined($config{'pwhoisd.default.queries.max'}))
	{
		if($config{'pwhoisd.default.queries.max'} =~ /\d+/) {
			$opt{'limit-max-queries'} = $config{'pwhoisd.default.queries.max'};
		}
		else
		{
			$log->log(level=>"error", message=>"Invalid configuration option value for 'pwhoisd.default.queries.max'\n");
			exit(-1);
		}
	}	
}

# open the database and create prepared statements
sub openDatabase()
{	
	$dbh = DBI->connect($dsn, $user, $password,
                  { RaiseError => 1, AutoCommit => 0 }) 
		or die "Can't open database: $DBI::errstr";

	$log->log(level=>"debug", message=>"Database opened dsn=$dsn\n") if $DEBUG eq 'true'; 

	$select_from_bgp_routes_sth = $dbh->prepare(qq(
      		SELECT * FROM bgp_routes WHERE best_route=1 AND status=1 ORDER BY network ASC
      		))
		or die "Can't prepare statement: $DBI::errstr";
		
	$select_from_bgp_routes_range_sth = $dbh->prepare(qq(
      		SELECT * FROM bgp_routes WHERE status=1 AND best_route=1 AND network >=? AND network <=? 
		ORDER BY network ASC, cidr DESC
      		))
		or die "Can't prepare statement: $DBI::errstr";
		
	$select_from_bgp_routes_by_prefix_sth = $dbh->prepare(qq(
      		SELECT next_hop,asn,asn_paths,createDate,modifyDate,best_route FROM bgp_routes
		WHERE status=1 AND network=? AND cidr=? 
		ORDER BY best_route DESC, next_hop ASC, asn ASC
      		))
		or die "Can't prepare statement: $DBI::errstr";


   	$select_from_bgp_routes_by_prefix_nocidr_sth = $dbh->prepare(qq(
                SELECT next_hop,asn,asn_paths,createDate,modifyDate,best_route FROM bgp_routes
		WHERE status=1 AND network=?
		ORDER BY best_route DESC, next_hop ASC, asn ASC
		))
		or die "Can't prepare statement: $DBI::errstr";



 	$select_from_bgp_routes_by_source_as_sth = $dbh->prepare(qq(
                SELECT network,cidr,next_hop,asn,asn_paths,createDate,modifyDate,best_route FROM bgp_routes
		WHERE status=1 AND asn=? 
		ORDER BY best_route DESC, next_hop ASC, asn_paths ASC
		))
		or die "Can't prepare statement: $DBI::errstr";

	$select_from_pwhois_acl_sth = $dbh->prepare(qq(
	                 SELECT ip,max_count FROM pwhois_acl WHERE status=1
		)) or die "Can't prepare statement: $DBI::errstr";

	# receive a list of the routers we have received routes from
	# or are announcing "active" best route prefixes from the route view feed
	$select_from_bgp_routes_next_hops_sth = $dbh->prepare(qq(
			SELECT next_hop FROM bgp_routes WHERE best_route=1 AND status=1
			GROUP BY next_hop ORDER BY next_hop ASC
		)) or die "Can't prepare statement: $DBI::errstr"; 

}

# close down the database connection
sub closeDatabase()
{
	if(defined($log)) {
		$log->log(level=>"debug", message=>"Database closed dsn=$dsn\n") if $DEBUG eq 'true';
	}
	undef $select_from_bgp_routes_sth;
	undef $select_from_bgp_routes_range_sth;
	undef $select_from_bgp_routes_by_prefix_sth;
	undef $select_from_bgp_routes_by_prefix_nocidr_sth;
	undef $select_from_bgp_routes_by_source_as_sth;
	undef $select_from_pwhois_acl_sth;
	undef $select_from_bgp_routes_next_hops_sth;
	$dbh->disconnect if defined($dbh);
}

sub getHelp()
{
return "$PROGNAME ($VERSION) $COPYRIGHT\n".
qq{
Help is here: 

DESCRIPTION

       Prefix WhoIs displays the origin-as and other interesting information
related to the most specific prefix currently advertised within the
Internet's global routing table that corresponds to the IP address in
your query.

       The only mandatory parameter is an IP address (optionally            
in CIDR notation, though pwhois assumes a /32 prefix).  You may
provide IP addresses with port numbers, though the port numbers will be 
removed and not returned to you in the result.  You may optionally use 
the "type" operator to change the display format between the default pwhois 
format, RPSL (RFC 2622), and the format used by Cymru (see www.cymru.com).


STANDARD QUERY CONSTRUCTION

       [type=pwhois|cymru|rpsl] <ip_address[/bits]>

BULK QUERIES

Prefix WhoIs supports bulk queries using optional commands.  Only
native and Cymru display types support bulk output.  When submitting a bulk 
query, simply make the term "bulk" or "begin" the first item/line sent in 
your query.  Then, you may optionally set the "type" attribute and then 
enter one IP address per line.  To signify the end of your query, simply 
provide "quit" or "end" as the last line of your query.

EXAMPLES (single query)
				               
       "1.2.3.4"  or  "type=cymru 1.2.3.4"  or  "type=rpsl 1.2.3.4"

EXAMPLES (bulk query)
					               
       begin                   begin
       type=cymru              1.2.3.4:80
       1.2.3.4                 5.6.7.8 
       5.6.7.8/32              ...
       ...                     end
       end

The "netcat" may be used to easily submi bulk queries.  To do so, simply
write your query to a file and concatenate the file contents into
netcat like so:

       \$ netcat <any-pwhois-server> 43 < ./ip_list.txt

Our "WhoB" whois client and our lightweight whois library also support bulk 
queries. Both are available at http://www.pwhois.org/

HELP AND STATUS QUERIES

       The "help" command or "?" displays this help text.

       The "version" command displays the pWhoIs server version
and other details such as the date of the last routing table cache update and 
the number of prefixes in the global table.

        The "peers" command displays the (route server's) peer IP addresses 
from which the pWhoIs server is receiving data.

        These queries are simply:

        [help|version|peers]

        Another interesting query is the "route view" query which displays 
all the active routes in the global routing table cache (at the time of the 
last routing table update) for the prefix specified.

	A query with this feature may look like:

        "routeview prefix=1.2.3.4[/8]" 

	Another form is to search by source-as, showing all the prefixes being
announced by this source.

	"routeview source-as=1245"
		
SOFTWARE

	You may download this source code and run your own pWhoIs server.  See 
http://www.pWhoIs.org/ for more details.

DISCLAIMER

	The pwhois service is provided for informational purposes only.  We do 
not guarantee its accuracy. By submitting a query, you agree to abide by the 
following terms of use: the compilation, repackaging, dissemination or other 
use of this data is expressly prohibited without our prior written consent. 
You agree not to use electronic processes that are automated to access or 
query this database except as reasonably necessary.  We reserve the right to 
restrict your access to this database in our sole discretion.  We may 
restrict or terminate your access to this database for failure to abide by 
these terms of use.  We reserve the right to modify these terms at any time.

AUTHORS AND THANKS

	The pwhois service was created and is maintained by the following
individuals and organizations, and wouldn't be possible without their time, 
energy, and on-going support. Thanks!

   Zachary Kanner, Victor Oppleman, Robb Ballard, Rob Thomas and Team Cymru,
   Rodney Joffe, and Brett Watson.
   
QUESTIONS OR COMMENTS

	Please send questions or comments about this service to:

	pWhoIs-support\@pWhoIs.org -- someone will get back to you shortly.

};
}

sub getVersion
{	
	my $cmd_line = shift;
	my $ip = shift;
	my $table_update;
	my $start_date;
	
	if(defined($last_table_update)) {
		$table_update = getDateTimeFormat($last_table_update);
	}
	else
	{
		$table_update = '(pending)';
	}
	
	if(defined($program_start_date)) {
		$start_date = getDateTimeFormat($program_start_date);
	}
	else
	{
		$start_date = '(pending)';
	}
	
	
	if(!$cmd_line) {
		if($opt{'bind'} ne '') {
	                        $ip = $opt{'bind'};
		}
	}
	else
	{
		if($opt{'bind'} ne '') {		
			$ip = $opt{'bind'};
		}
	}
	
return "$PROGNAME ($VERSION) $COPYRIGHT\n\n".
       
       "Server running on $ip:". $opt{'port'} ."\n".
       "Global BGP routing table cache last updated: $table_update\n".
       "Cache contains ". scalar(keys %routes) ." global prefixes from ". scalar(keys %peers) ." peers\n".
       scalar(keys %requests) ." unique IPs accessed this server since: ". $start_date;
       
}

sub do_query
{
	my $req = shift;
	my $client = shift;
	my $client_ip = $socket->PeerAddr($client);
	my $display_type = 'pwhois';
	my $host_ip;
	my $response;
	my $INVALID_INPUT="Sorry, I don't like your input.  You may ask for 'help'";
	my $application='unknown';
	if($connections{$client}{'bulk'}) {
		$application = $connections{$client}{'application'};
	}

	my $requestDate = time();

	$log->log(level=>"debug", message=>"Request received from $client_ip with query='$req'\n") if $opt{'verbose'} >= 4;

	return "Error: server is reloading internal datasets or it is too busy for your request.  Please try again in a few minutes."
		if $server_connections_paused;


	# check the request to make sure it is well formed

	if($req =~ /^\s?help|\?\s?$/i) {
		return getHelp();
	}
	
	if($req =~ /^\s?version\s?$/i) {
		return getVersion(0, $socket->LocalAddr($client));
	}
	if($req =~ /^\s?(begin)|(bulk)$/i) {
		$connections{$client}{'bulk'} = 1;
		$connections{$client}{'bulk_end'} = 0;
		$connections{$client}{'bulk_count'} = 0;
		$log->log(level=>"debug", message=>"bulk mode selected for client: $client_ip waiting for more data\n")		
			if $opt{'verbose'} >= 2;

		return '';
	}

	if($req =~ /^\s?(end)|(quit)$/i) {
		$connections{$client}{'bulk_end'} = 1;
		$log->log(level=>"debug", message=>"end of bulk mode found from client: $client_ip ... closing connection.\n")
			if $opt{'verbose'} >= 2;
		return;
	}

	# match type=XXX on separate line (in bulk mode only)
	if($req =~ /^\s?type=((pwhois)|(cymru)|(rpsl))\s?$/i)
	{
		my $type = $1;		
		if($connections{$client}{'bulk'})
		{
		
			if(!defined($connections{$client}{'displayType'}))
			{
				$connections{$client}{'displayType'} = $type;
				$display_type = 'cymru';
				$log->log(level=>"debug", message=>"setting the display type to ". $connections{$client}{'displayType'} ." for bulk session\n") 
						if $opt{'verbose'} >= 2; 
			}
			return;
		}
		else
		{
			return $INVALID_INPUT; 
		}

	}
	elsif($req =~ /^\s?type=((pwhois)|(cymru)|(rpsl))\s?/i)
	{	
		my $type = $1;
		$display_type = $1;
		$log->log(level=>"debug", message=>"setting the display type to $display_type\n") if $opt{'verbose'} >= 2; 
	}

	if($req =~ /^\s?\"?app=[A-z0-9\.\- ]+\"?\s?/i)
	{
		my ($app) = ($req =~ /^\s?app=\"?([A-z0-9\.\- ]+)\"?\s?/i);
		if($connections{$client}{'bulk'}) {
			$connections{$client}{'application'} = $app;
		}
		$application = $app;
		$log->log(level=>"debug", message=>"setting the application to ". $application ."\n") if $opt{'verbose'} >= 2;
		return;	
	}

  	# search for all prefixes (as stored in our database snapshot) after first finding most specific from FIB
        # and return all the routers, Source-AS, Next-Hops, and AS-Paths
        if($req =~ /^\s?routeview prefix=\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}\s?$/i)
        {
                my($ip)=($req =~ /^\s?routeview prefix=(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})\s?$/i);
		my $cidr;
              
		$log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Performing routeview prefix query for $ip src=$client_ip count=".
		                                   $requests{$client_ip}{'count'} ." limit=". $requests{$client_ip}{'limit'} ."\n") if $opt{verbose} >= 1;


                # find the most specific prefix from the FIB -- use this prefix (if no specified)
		my ($result, $network, $asn, $asn_paths, $cache_date) = findInNetwork($ip);
		if($result) {
			($ip,$cidr) = ipv4_parse($network);
		}
		else
		{
			return "Route for $ip not found in global routing table";
		}

                if(ipv4_is_valid_quaddot($ip .'/'. $cidr))
                {
			# some of the database entries do not have CIDR length, as classful routing is in Global RIB/FIB
			# out FIB has the default prefix length tacked on, but we need to search without the CIDR or we won't
			# find the data in the database -- this shouldn't matter as it is an exact network match search.
     			$log->log(level=>"debug", message=>"Performing routeview prefix db query for $ip/$cidr ...\n") if $opt{verbose} >= 1;
                        $select_from_bgp_routes_by_prefix_nocidr_sth->execute(ipv4_quaddot_to_decimal($ip));

                        my $count=0;
                        while(my($next_hop, $asn, $asn_paths, $createDate, $modifyDate, $best_route)
                                                = $select_from_bgp_routes_by_prefix_nocidr_sth->fetchrow_array())
                        {
                                my $line;

                                if($count == 0) {
                                        $response = "\n".
                                                    "Origin-AS: $asn\n".
                                                    "   Prefix: $ip/$cidr\n\n".

                                                    "    Create-Date             | Modify-Date              | Next-Hop        | AS-Path\n";

                                }

                                if($best_route)
                                {
                                        $line = sprintf("*> %+24s | %+24s | %+15s | %-s\n",
                                                        getDateTimeFormat($createDate),
                                                        getDateTimeFormat($modifyDate),
                                                        ipv4_decimal_to_quaddot($next_hop) , $asn_paths);
                                }
                      		else
                                {
                                        $line = sprintf("*  %+24s | %+24s | %+15s | %-s\n",
                                                        getDateTimeFormat($createDate),
                                                        getDateTimeFormat($modifyDate),
                                                        ipv4_decimal_to_quaddot($next_hop) , $asn_paths);

                                }
                                $response .= $line;
                                $count++;
                        }
                        $select_from_bgp_routes_by_prefix_nocidr_sth->finish();

			if($response eq '') {
				$response = "No prefixes found in routeview database for prefix=$ip/$cidr";
			}
                        return $response;
                }
                else
                {
                        $log->log(level=>"debug", message=>"Invalid ip address specified: $ip\n") if $DEBUG eq 'true';
                        return $INVALID_INPUT;
                }
        }

	# search for all prefixes (as stored in our database snapshot) -- by a specific prefix
	# and return all the routers, Source-AS, Next-Hops, and AS-Paths
	if($req =~ /^\s?routeview prefix=\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}\/\d{1,2}\s?$/i)
	{
		my($ip, $cidr)=($req =~ /^\s?routeview prefix=(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})\/(\d{1,2})\s?$/i);
		
		if(ipv4_is_valid_quaddot($ip .'/'. $cidr))
		{
		    	$log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Performing routeview prefix db query for $ip/$cidr src=$client_ip count=".
		        				  $requests{$client_ip}{'count'} ." limit=". $requests{$client_ip}{'limit'} ."\n") if $opt{verbose} >= 1;
			$select_from_bgp_routes_by_prefix_sth->execute(ipv4_quaddot_to_decimal($ip), $cidr);
												
			my $count=0;
			while(my($next_hop, $asn, $asn_paths, $createDate, $modifyDate, $best_route)
						= $select_from_bgp_routes_by_prefix_sth->fetchrow_array())
			{
				my $line;
				
				if($count == 0) {
					$response = "\n".
						    "Origin-AS: $asn\n".
						    "   Prefix: $ip/$cidr\n\n".
					
						    "    Create-Date             | Modify-Date              | Next-Hop        | AS-Path\n";
				    
				}
				
				if($best_route)
				{
					$line = sprintf("*> %+24s | %+24s | %+15s | %-s\n", 
							getDateTimeFormat($createDate), 
							getDateTimeFormat($modifyDate), 
							ipv4_decimal_to_quaddot($next_hop) , $asn_paths);
				}
				else
				{
					$line = sprintf("*  %+24s | %+24s | %+15s | %-s\n", 
							getDateTimeFormat($createDate), 
							getDateTimeFormat($modifyDate), 
							ipv4_decimal_to_quaddot($next_hop) , $asn_paths);
				
				}
				$response .= $line;
				$count++;
			}
			$select_from_bgp_routes_by_prefix_sth->finish();
		 	if($response eq '') {
		        	$response = "No prefixes found in routeview database for prefix=$ip/$cidr";
			}
			return $response;
		}
		else
		{
		        $log->log(level=>"debug", message=>"Invalid ip address specified: $ip\n") if $DEBUG eq 'true';
			return $INVALID_INPUT; 
		}
	}

	# search for all prefixes (as stored in our database snapshot)
        # and return all the routers, Source-AS, Next-Hops, and AS-Paths
        if($req =~ /^\s?routeview source-as=\d{1,7}\s?$/i)
        {
                my($source_as)=($req =~ /^^\s?routeview source-as=(\d{1,7})\s?$/i);

       		$log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Performing routeview source-as db query for $source_as src=$client_ip count=".
		                                   $requests{$client_ip}{'count'} ." limit=". $requests{$client_ip}{'limit'} ."\n") if $opt{verbose} >= 1;
		$select_from_bgp_routes_by_source_as_sth->execute($source_as);

		my $count=0;
		while(my($prefix, $network_cidr, $next_hop, $asn, $asn_paths, $createDate, $modifyDate, $best_route)
					= $select_from_bgp_routes_by_source_as_sth->fetchrow_array())
		{
			my $line;
			my $network;

			if(defined($network_cidr)) {
				$network = ipv4_decimal_to_quaddot($prefix) .'/'. $network_cidr;
			}
			else
			{
				$network = ipv4_decimal_to_quaddot($prefix);
			}

			if($count == 0) {
				$response = "\n".
					    "Origin-AS: $asn\n\n".

					    "    Prefix            | Create-Date              | Modify-Date              | Next-Hop        | AS-Path\n";
			}

			if($best_route)
			{
				$line = sprintf("*> %+18s | %+24s | %+24s | %+15s | %-s\n",
						$network,	
						getDateTimeFormat($createDate),
						getDateTimeFormat($modifyDate),
						ipv4_decimal_to_quaddot($next_hop) , $asn_paths);
			}
			else
			{
				$line = sprintf("*  %+18s | %+24s | %+24s | %+15s | %-s\n",
						$network,
						getDateTimeFormat($createDate),
						getDateTimeFormat($modifyDate),
						ipv4_decimal_to_quaddot($next_hop) , $asn_paths);

			}
			$response .= $line;
			$count++;
		}
                $select_from_bgp_routes_by_source_as_sth->finish();
     		if($response eq '') {
                 	$response = "No prefixes found in routeview database for source-as=$source_as";
		}
                return $response;
        }
	
	if($req =~ /^\s?peers/i)
	{
		$response = "Peers:\n";
		foreach my $peer (sort keys %peers) {
			$response .= "    $peer\n";
		}
		return $response;
	}
	
	# regular queries (no extra fields or parameters)
	
	# IP notation	
	if($req =~ /^[A-z=]*\s?\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}\s?$/) {
		my($ip)=($req =~ /^[A-z=]*\s?(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})\s?$/);
		if(ipv4_is_valid_quaddot($ip)) {
			$host_ip = $ip;
		}
		else
		{
		        $log->log(level=>"debug", message=>"Invalid ip address specified: $ip\n") if $DEBUG eq 'true';
			return $INVALID_INPUT; 
		}
		
	} # prefix notation
	elsif($req =~ /^[A-z=]*\s?\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}\/\d{1,2}\s?$/) {
		my($ip,$cidr)=($req =~ /^[A-z=]*\s?(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})\/(\d{1,2})\s?$/);
			
		if(ipv4_is_valid_quaddot($ip)) {			
			$host_ip = $ip;
		}
		else
		{
			$log->log(level=>"debug", message=>"Invalid ip address specified: $ip\n") if $DEBUG eq 'true';
			return $INVALID_INPUT;
		}
	} # ip/port notation	
	elsif($req =~ /^[A-z=]*\s?\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}:\d{1,6}\s?$/) {
		my($ip,$port)=($req =~ /^[A-z=]*\s?(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}):(\d{1,6})\s?$/);
			
		if(ipv4_is_valid_quaddot($ip)) {			
			$host_ip = $ip;
		}
		else
		{
			$log->log(level=>"debug", message=>"Invalid ip address specified: $ip\n") if $DEBUG eq 'true';
			return $INVALID_INPUT;
		}
	}
	else
	{
	        $log->log(level=>"debug", message=>"Invalid request specified: $req\n") if $DEBUG eq 'true';
		return $INVALID_INPUT; 
	}

	# check to make sure the client IP hasn't exceeded their request limit
	if(defined($requests{$client_ip})) {
		my $count = $requests{$client_ip}{'count'};
		# if the source IP has exceeded their query limit in the past 24 hours, bail out and
		# don't let them continue.  If the limit is 0, then they can submit unlimited queries
		if($count >= $requests{$client_ip}{'limit'} and $requests{$client_ip}{'lastQuery'} >= $requestDate - 1*24*3600 and $requests{$client_ip}{'limit'} > 0)
		{
	       		$log->log(level=>"debug", message=>"Request not processed -- limit exceeded by $client_ip\n") if $DEBUG eq 'true';
			return "Error: Unable to perform pwhois lookup; Query limit exceeded.";	
		}
		elsif($count >= $requests{$client_ip}{'limit'} and $requests{$client_ip}{'lastQuery'} < $requestDate - 1*24*3600)
		{
			# update the count so the user can start over
			$requests{$client_ip}{'count'} == 0;	
			$requests{$client_ip}{'lastQuery'} = $requestDate;	        
		}
		else
		{
			$requests{$client_ip}{'count'}++;
			$requests{$client_ip}{'lastQuery'} = $requestDate;	
		}
	}
       	else 
	{
		$requests{$client_ip}{'count'}=1;
		$requests{$client_ip}{'lastQuery'} = $requestDate; 
		$requests{$client_ip}{'firstQuery'} = $requests{$client_ip}{'lastQuery'};
		$requests{$client_ip}{'limit'} = $opt{'limit-max-queries'};
	}

	$log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Performing query for $host_ip src=$client_ip app=\"$application\" count=". 
				$requests{$client_ip}{'count'} ." limit=". $requests{$client_ip}{'limit'} ."\n") if $opt{verbose} >= 1;

	# take the IP address provided and find the network address that matches
	# The most specific.  Then return the ASN for that netblock based upon
	# The BGP routing table (FIB) that is cached in memory.

	my ($result, $network, $asn, $asn_paths, $cache_date) = findInNetwork($host_ip);
	if($result) {
	
		if($display_type eq 'cymru' or 
		  ($connections{$client}{'bulk'} and $connections{$client}{'displayType'} eq 'cymru'))
		{
			if($connections{$client}{'bulk'} == 0 or 
				($connections{$client}{'bulk'} and 
				$connections{$client}{'bulk_count'} == 0)) 
			{
				$response = "Origin-AS | IP              | Prefix             | AS-Path\n"; 
			}
			my $line = sprintf("%+9s | %+15s | %+18s | %-s", 
					$asn, $host_ip, $network , $asn_paths);
			$response .= $line;

			if($connections{$client}{'bulk'}) {
				$connections{$client}{'bulk_count'}++;
			}
		}
		elsif($display_type eq 'rpsl' and !$connections{$client}{'bulk'})
		{
			# origin: AS<asn> AS12266
			# route: <cidr_prefix> 4.0.0.0/8
			# date: <date of update in rpsl>
			# source: PWHOIS Server x.x.x.x:port at <date of update>

			$response = "origin: AS$asn\n" .
				    "route: $network\n" .
				    "date: ". getRpslDateFormat($cache_date) ."\n" .
				    "source: PWHOIS Server ". $socket->LocalAddr($client) .":". $opt{port} ." at ".
				    	getRpslDateFormat($cache_date);					
		}
		elsif($display_type eq 'pwhois' or # pwhois format
		     ($connections{$client}{'bulk'} and $connections{$client}{'displayType'} eq 'pwhois'))
		{		
			$response = "IP: $host_ip\n" .
				    "Origin-AS: $asn\n" .
				    "Prefix: $network\n" .
				    "AS-Path: $asn_paths\n" .
				    "Cache-Date: $cache_date";
		
			if($connections{$client}{'bulk'} and $connections{$client}{'bulk_end'} == 0) {
				$response .= "\n";
			}
		}
		else
		{
			return $INVALID_INPUT; 
		}
	}
	else # not found
	{	
		if($connections{$client}{'bulk'})
		{
			if($display_type eq 'cymru' or 
		  	  ($connections{$client}{'bulk'} and $connections{$client}{'displayType'} eq 'cymru'))
			{
				if($connections{$client}{'bulk_count'} == 0) {
					$response = "Origin-AS | IP              | Prefix             | AS-Path\n"; 
				}
				my $line = sprintf("%+9s | %+15s | %+18s | %-s", 
						0, "NULL", "NULL", "NULL");
				$response .= $line;

				$connections{$client}{'bulk_count'}++;
	
			}
			elsif($display_type eq 'rpsl')
			{
				return "No matching data found";
			}
			elsif($display_type eq 'pwhois' or 
		  	  ($connections{$client}{'bulk'} and $connections{$client}{'displayType'} eq 'pwhois'))
			{
				$response = "IP: $host_ip\n" .
					    "Origin-AS: 0\n" .
					    "Prefix: NULL\n" .
					    "AS-Path: NULL\n" .
					    "Cache-Date: NULL";
		
				if($connections{$client}{'bulk_end'} == 0) {
					$response .= "\n";
				}
				
			}
		}
		else
		{				
			$response = "That IP address doesn't appear in the global routing table as of ". getDateTimeFormat($last_table_update);
		}
	}
	return $response;
}

sub load_data()
{
	my $cacheDate = time();
	
	# clear any cached data (if any)	
		
	%routes = ();
	undef $pt; $pt = new Net::Patricia;

	my $routes_found=0;

	# get the data from the database (currently about 150K rows)
	$select_from_bgp_routes_sth->execute();
	while(my($id,$router_id, $network,$cidr,$nextHop,$asn,$asn_paths,$createDate,$modifyDate,$status,$best_route) =
		$select_from_bgp_routes_sth->fetchrow_array()) {

		# polulate the internal cache table with data from the database
		# add the values to the hash
		my $network_quaddot = ipv4_decimal_to_quaddot($network);
		my $route;
		if(defined($cidr)) {
			$route = $network_quaddot .'/'. $cidr;
		}
		else
		{
			# if the route doesn't have a CIDR prefix, get the default prefix for classful routing
			$route = ipv4_parse($network_quaddot .'/'. 
					ipv4_msk2cidr(ipv4_dflt_netmask($network_quaddot)));
		}
		
		if(defined($routes{$route})) {
			$log->log(level=>"error", message=>"Route $route is already in RIB with id=". $routes{$route}{'id'} ." ... $id is replacing it.  Why?\n") if $opt{'verbose'} >= 1;
		}
		
		$routes{$route}{'class'} = 'bgp';
		$routes{$route}{'route'} = $route;
		$routes{$route}{'status'} = $status;
		$routes{$route}{'id'} = $id;
		$routes{$route}{'router_id'} = $router_id;
		$routes{$route}{'asn'} = $asn;
		$routes{$route}{'path'} = $asn_paths;
		$routes{$route}{'createDate'} = $createDate;
		$routes{$route}{'modifyDate'} = $modifyDate;
		$routes{$route}{'cacheDate'} = $cacheDate;
		
		# update the table update date from one of the records
		$last_table_update = $modifyDate if $routes_found == 0;
		
		$pt->add_string($route, $route);
		
		$routes_found++;
		$log->log(level=>"debug", message=>"Loading (best) network route: $route with asn=$asn id=$id.\n") if $opt{'verbose'} >= 10;
	}

	$select_from_bgp_routes_sth->finish();
        $log->log(level=>"info", message=> getCurrentSyslogDateTime() ." BGP routing table cache loaded with ". scalar(keys %routes) . " routes.\n") if $opt{'verbose'} >= 1;

	%requests = ();
	# load our pwhois ACL data
	$select_from_pwhois_acl_sth->execute();
	while(my($ipdec, $max_count) = $select_from_pwhois_acl_sth->fetchrow_array()) {
		  my $ip = ipv4_decimal_to_quaddot($ipdec);
		  $requests{$ip}{'limit'} = $max_count;
		  $requests{$ip}{'lastQuery'} = 0;
		  $requests{$ip}{'count'} = 0;
		  
	          $log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Setting ACL for $ip limit=$max_count\n") if $opt{'verbose'} >= 1;

	}
	$select_from_pwhois_acl_sth->finish();

	%peers = ();
	$select_from_bgp_routes_next_hops_sth->execute();
	while(my($next_hop) = $select_from_bgp_routes_next_hops_sth->fetchrow_array()) 
	{
		my $ip = ipv4_decimal_to_quaddot($next_hop);
		$peers{$ip} = 1;
	}
	$select_from_bgp_routes_next_hops_sth->finish();
		
        $log->log(level=>"info", message=> getCurrentSyslogDateTime() ." Loaded peer information ... ". scalar(keys %peers) ." peers in feed(s).\n") if $opt{'verbose'} >= 1;
}

sub startServer()
{
	$socket = Net::Socket::NonBlock::Nest->new(SelectT  => 0.1,
        						SilenceT => 0,
							debug    => $^W,
							BuffSize => 10240,
							) or die "Error creating sockets nest: $@\n";
	
	$socket->Listen(LocalAddr => $opt{'bind'},
			LocalPort => $opt{'port'},
			Proto     => 'tcp',
			Accept    => \&clientConnect,
			SilenceT  => 0,
			ClientsST => 10,
			Listen    => 10,
			MaxClients => 10000
			)
			or die "Could not listen on port ". $opt{port} .": $@\n";
	
	my $localaddr = "*" if $opt{'bind'} eq '';
	$localaddr = $opt{'bind'} if $opt{'bind'} ne '';
	$log->log(level=>"info", message=>getCurrentSyslogDateTime() ." Whois server listening on pwhois://$localaddr:". $opt{'port'} ."\n") if $opt{verbose} >= 1;


	# drop previledges
	POSIX::setuid($opt{'uid'});
	POSIX::setgid($opt{'gid'});

	while($socket->IO())
	{	      
		$log->log(level=>"debug", message=>"Selecting on socket\n") if $opt{'verbose'} >= 10;
		foreach my $ClnSock (sort keys %connections)
		{
			my $req = undef;
			my $client_ip = $socket->PeerAddr($ClnSock);
			$log->log(level=>"debug", message=>"Receiving data from $client_ip\n") if $opt{'verbose'} >= 9;
			while(($req = $socket->Gets($ClnSock)) and length($req))
			{
				$log->log(level=>"debug", message=>"Data received from $client_ip: $req\n") if $opt{'verbose'} >= 9;
				my($response) = do_query($req, $ClnSock);						
				$socket->Puts($ClnSock, $response . "\n");
				$socket->IO();
				if($connections{$ClnSock}{'bulk'})
				{
					if($connections{$ClnSock}{'bulk_end'})
					{
						$socket->Close($ClnSock);
						delete($connections{$ClnSock});
					}
					else
					{
						# wait for additional commands
						$log->log(level=>"debug", message=>"Waiting for additional commands from: $client_ip\n")
							if $opt{'verbose'} >= 9;
					}
				}
				else
				{
					$log->log(level=>"debug", message=>"Connection closed: $client_ip\n") if $opt{'verbose'} >= 9;
					$socket->Close($ClnSock);
					delete($connections{$ClnSock});
				}
			}
			if(!defined($req)) {
				$socket->Close($ClnSock);
				delete($connections{$ClnSock});
			}
		}
	}

	$socket->Close();
			
}

sub clientConnect 
{
        my $client = shift;
	if(defined($client)) {
		my $client_ip = $socket->PeerAddr($client);
		$log->log(level=>"debug", message=>"Got connection from: $client_ip\n") if $opt{'verbose'} >= 9;
		#$connections{$client}{'socket'} = \$client;
		$connections{$client}{'bulk'} = 0;
		$connections{$client}{'bulk_end'} = 0;
		$connections{$client}{'application'} = 'unknown';
		return 1;
	}
	else
	{
		return 0;
	}
}

sub getDateTimeFormat($) {
	return Time::Format::time_format('Mon dd yyyy hh:mm{in}:ss tz', shift);
}

sub getRpslDateFormat($) {
	return Time::Format::time_format('yyyymmdd', shift);
}

sub getSyslogDateTime($) {
	return Time::Format::time_format('Mon ?d yyyy hh:mm{in}:ss', shift);
}

sub getCurrentSyslogDateTime() {
	return getSyslogDateTime(time());
}



sub DESTROY {
   my $self = shift;
   # clean up lock and temp files
   #$self->SUPER::DESTROY
}

## check the routing table to see if the
# ip address is within one of the networks.  If so
# set the community name (class) and network route
# address that matches.
sub findInNetwork
{
        my $dest = shift;
		
        if($dest eq '')  {
                warn "No ip address specified.\n";
                return (0, '', '');
        }
	
	die "Ip address can not be null\n"  if !defined($dest);

	#	$log->log(level=>'debug', message=>"Attempting to find '$dest' in RIB.\n")
	#	if $opt{verbose} >= 9 and $DEBUG eq 'true';
  
        # parse the dest. address to determine netmask -- default to /32 if non-provided
        my ($ip,$cidr) = ipv4_parse($dest);

	my $node = $pt->match_string($dest);
	if(defined($node)) {
		my $network = $node;                
                my $asn;
		my $path;
		my $cache_date;
		my ($network_ip, $network_cidr) =  ipv4_parse($network);
		$network_cidr =  ipv4_msk2cidr( ipv4_dflt_netmask($network_ip)) if !defined($network_cidr);

		$log->log(level=>'debug', message=>"comparing $ip is in network $network ($network_ip/$network_cidr) ... \n") if $opt{verbose} >= 4 and $DEBUG eq 'true';
		if(ipv4_in_network($network_ip, $network_cidr, $ip, $cidr))
		{

                	if($routes{$network}{'class'} eq 'bgp') {

				$path = $routes{$network}{'path'};
                       		$asn = $routes{$network}{'asn'};
				$cache_date = $routes{$network}{'cacheDate'};
                	}                        
                	else
                	{
                        	$log->log(level=>'error',
                	          	  message=>" woah ... unknown routing table entry found for $network\n")
                            		if $opt{verbose} and $DEBUG eq 'true';
                	}

                	$log->log(level=>'debug',
                          	  message=>" found $ip in network $network with most specific asn $asn\n")
                		if $opt{verbose} >= 7 and $DEBUG eq 'true';
            		return (1, $network, $asn, $path, $cache_date);
		}
        }
        $log->log(level=>'debug', message=>"$ip was not found in the routing table.\n") if $opt{verbose} >= 7 and $DEBUG eq 'true';
        return (0,'','','','');
}

sub ipv4_is_valid_quaddot($)
{
	my $ip = shift;

	if($ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
	{
		my (@octets) = split /\./, $ip;
		foreach (@octets) {
			return 0 if $_  < 0 or $_ > 255;
		}
	}
	elsif($ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/)
	{
		my ($ip_addr, $cidr) = split /\//, $ip;
	 	my (@octets) = split /\./, $ip_addr;
	        foreach (@octets) {
			return 0 if $_  < 0 or $_ > 255;
		}

		if($cidr < 0 or $cidr > 32) {
			return 0;	
		}
	}
	else
	{
		return 0;
	}
	# valid quaddot
	return 1;
}


sub contains 
{
	my $value = shift;
	my @array = @_;

        foreach my $av (@array) {
#		$log->log(level=>"debug", message=>"Comparing value $value eq $av\n") if $DEBUG eq 'true';
		return 1 if $value == $av;
	}
	return 0;
}


#  Some of these ipv4 functions are from the IPv4Addr module.  I had to modify
# some of the to work appropriately for our use here.
#
#    Author: Francis J. Lacoste <francis@Contre.COM>
#
#    Copyright (C) 1999 Francis J. Lacoste, iNsu Innovations Inc.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms as perl itself.

my $ip_rgx = "\\d+\\.\\d+\\.\\d+\\.\\d+";

# Given an IPv4 address in host, ip/netmask or cidr format
# returns a ip / cidr pair.
sub ipv4_parse($;$) {
  my ($ip,$msk);
  # Called with 2 args, assume first is IP address
  if ( defined $_[1] ) {
    $ip = $_[0];
    $msk= $_[1];
  } else {
    ($ip)  = $_[0] =~ /($ip_rgx)/o;
    ($msk) = $_[0] =~ m!/(.+)!o;
  }

  # Remove white spaces
  $msk =~ s/\s//g if defined $msk;

  # Check Netmask to see if it is a CIDR or Network
  if (defined $msk ) {
    if ($msk =~ /^\d{1,2}$/) {
      # Check cidr
      warn ": invalid cidr: ". $msk ."\n"
        if $msk < 0 or $msk > 32;
    } elsif ($msk =~ /^$ip_rgx$/o ) {
      $msk = ipv4_msk2cidr($msk);
    } else {
      warn ": invalid netmask specification: ". $msk ."\n";
    }
  } else {
    # Host
    return $ip;
  }
  wantarray ? ($ip,$msk) : "$ip/$msk";
}

sub ipv4_dflt_netmask($) {
  my ($ip) = ipv4_parse($_[0]);

  my ($b1) = split /\./, $ip;

  return "255.0.0.0"	if $b1 <= 127;
  return "255.255.0.0"	if $b1 <= 191;
  return "255.255.255.0";
}

# Transform a netmask in a CIDR mask length
sub ipv4_msk2cidr($) {
  my $msk = $_[0];
  my @bytes = split /\./, $msk;
  my $cidr = 0;
  for (@bytes) {
    my $bits = unpack( "B*", pack( "C", $_ ) );
    $cidr +=  $bits =~ tr /1/1/;
  }
  return $cidr;
}

# Transform a CIDR mask length in a netmask
sub ipv4_cidr2msk($) {
  my $cidr = shift;
  my $bits = "1" x $cidr . "0" x (32 - $cidr);
  return join ".", (unpack 'CCCC', pack("B*", $bits ));
}

# Return the network address of
# an IPv4 address
sub ipv4_network($;$) {
  my ($ip,$cidr) = ipv4_parse( $_[0], $_[1] );

  # If only an host is given, use the default netmask
  unless ($cidr) {
    $cidr = ipv4_msk2cidr( ipv4_dflt_netmask($ip) );
  }
  my $u32 = unpack "N", pack "CCCC", split /\./, $ip;
  my $bits = "1" x $cidr . "0" x (32 - $cidr );

  my $msk = unpack "N", pack "B*", $bits;

  my $net = join ".", unpack "CCCC", pack "N", $u32 & $msk;

  wantarray ? ( $net, $cidr) : "$net/$cidr";
}

sub ipv4_in_network($$;$$) {
  my ($ip1,$cidr1,$ip2,$cidr2);
  if ( @_ >= 3) {
  	($ip1,$cidr1) = ipv4_parse( $_[0], $_[1] );
	($ip2,$cidr2) = ipv4_parse( $_[2], $_[3] );
  } else {
	($ip1,$cidr1) = ipv4_parse( $_[0]);
	($ip2,$cidr2) = ipv4_parse( $_[1]);
  }

  # Check for magic addresses.
  return 0 if $ip1 eq "255.255.255.255" or $ip1 eq "0.0.0.0";
  return 0 if $ip2 eq "255.255.255.255" or $ip2 eq "0.0.0.0";
			  
  # Case where first argument is really a host
  return $ip1 eq $ip2 unless ($cidr1);
  # Case where second argument is an host
  if ( not defined $cidr2) {
	return ipv4_network( $ip1, $cidr1) eq ipv4_network( $ip2, $cidr1 );
  } elsif ( $cidr2 > $cidr1 ) {
  	# Netmask 2 is more specific than netmask 1
 	 return ipv4_network( $ip1, $cidr2) eq ipv4_network( $ip2, $cidr2);
  } else {
	# Netmask 1 is more specific than netmask 2
	return ipv4_network( $ip1, $cidr1) eq ipv4_network( $ip2, $cidr2);
  }
}

# convert quaddot to decimal integer
sub ipv4_quaddot_to_decimal($) {
	my $ip = shift;
	warn "invalid ip address provided" if !defined($ip);
	return -1 if !defined($ip);
	warn "invalid ip address provided: $ip" if(! $ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
	return "" if(! $ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
       
	return unpack "N", pack "CCCC", split /\./, $ip;
}

# convert decimal integer to quaddot
sub ipv4_decimal_to_quaddot($) {
	my $decimal = shift;
	warn "invalid decimal value provided" if !defined($decimal);
	return "" if !defined($decimal);
	
	warn "invalid decimal value provided: $decimal" if($decimal < 0 or $decimal > 4294967295);
	return "" if($decimal < 0 or $decimal > 4294967295);
        return join ".", unpack "CCCC", pack "N", $decimal;
}
 
my $obj = new Pwhoisd(); 
$obj->load_data() if !defined($opt{'no-load'});
$obj->startServer();

1;
