//-----------------------------------------------------------------------------
// Copyright © 2005 - Philip Howard - All rights reserved
//
// 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.
//-----------------------------------------------------------------------------
// package	libh
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh@ipal.org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// file		ipv4lookup.c
//
// program	ipv4lookup
//
// purpose	Read in a file containing a list of CIDR subnet addresses and
//		associated data strings (one per line), then look up addresses
//		from the command line in this data collection, outputting the
//		address and the data found with the best matching subnet.
//
// usage	Shell command
//
// syntax	ipv4lookup  { address ... } < datafile
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>

#include <libh/io.h>
#include <libh/map.h>
#include <libh/net.h>
#include <libh/string.h>

//-----------------------------------------------------------------------------
// function	table_out
//
// purpose	Output the contents of one IPv4 lookup table entry.  This
//		function is intended to be called via ipv4_table_iterate().
//-----------------------------------------------------------------------------
unsigned long
table_out (
    ipv4_t	arg_addr
    ,
    int		arg_prefix
    ,
    void *	arg_subnet_ptr
    ,
    void *	arg_iterate_ptr
    )
{
    printf( arg_subnet_ptr ? "%u.%u.%u.%u/%u %s\n" : "%u.%u.%u.%u/%u\n",
	    ( arg_addr >> 24 ) & 0xff,
	    ( arg_addr >> 16 ) & 0xff,
	    ( arg_addr >>  8 ) & 0xff,
	    ( arg_addr       ) & 0xff,
	    arg_prefix,
	    (const char *) arg_subnet_ptr );
    return 1UL << ( 32 - arg_prefix );
}

//-----------------------------------------------------------------------------
// function	main
//
// purpose	Start the ipv4lookup program here.
//-----------------------------------------------------------------------------
int
main (
    int		argc
    ,
    char * *	argv
)
{
    MAP			string_share_map	;

    ipv4_table_p	lookup_table		;

    char *		addr_ptr		;
    char *		end_ptr			;
    char *		info_ptr		;
    const char *	shared_ptr		;

    ipv4_t		addr1			;
    ipv4_t		addr2			;
    ipv4_t		cidr_addr		;

    unsigned int	count			;
    unsigned int	prefix			;
    int			sprefix			;
    int			result			;

    char		line_space	[4096]	;


    //-----------------------------
    // Create an IPv4 lookup table.
    //-----------------------------
    lookup_table = ipv4_table_create();
    if ( ! lookup_table ) goto error_ipv4_table_create;

    //-------------------------------
    // Create a string share mapping.
    //-------------------------------
    string_share_map = map_str_new();
    if ( ! string_share_map ) goto error_map_str_new;

    //---------------------------------------
    // Read in database and build IPv4 table.
    //---------------------------------------
    for (;;) {

	//-- Read in one line at a time.
	if ( get_line_file( line_space, sizeof line_space, stdin ) < 0 ) break;

	//-- Split address and remaining info string.
	addr_ptr = line_space;
	while ( * addr_ptr && isspace( * addr_ptr ) ) ++ addr_ptr;
	if ( * addr_ptr < '0' || '9' < * addr_ptr ) continue;
	info_ptr = addr_ptr;
	while ( * info_ptr && ! isspace( * info_ptr ) ) ++ info_ptr;
	if ( * info_ptr ) {
	    * info_ptr = 0;
	    ++ info_ptr;
	    while ( * info_ptr && isspace( * info_ptr ) ) ++ info_ptr;
	}

	//-- Convert address and check it.
	addr2 = 0xfffffffe;
	result = ipv4_str_to_addr( addr_ptr, & end_ptr, & addr1, & addr2, NULL, & prefix, & count, NULL );

	if ( ( result & IPV4_STR_TO_ADDR_ADDR ) == 0 ) {
	    fprintf( stderr, "invalid IP address: \"%s\"\n", addr_ptr );
	    continue;
	}
	if ( ( result & IPV4_STR_TO_ADDR_NETMASK ) ) {
	    fprintf( stderr, "netmask not supported: \"%s\"\n", addr_ptr );
	    continue;
	}

	if ( result & IPV4_STR_TO_ADDR_PREFIX ) {
	    addr1 &= 0xffffffffUL << ( 32 - prefix );
	    addr2 = addr1 + ( 1UL << ( 32 - prefix ) ) - 1UL;
	} else if ( result & IPV4_STR_TO_ADDR_COUNT ) {
	    addr2 = addr1 + count;
	} else {
	    addr2 = addr1;
	}

	//-- Share the info string with others like it.
	if ( * info_ptr ) {
	    result = map_str_insert( string_share_map, info_ptr );
	    if ( result != 0 ) goto error_map_str_insert;
	    shared_ptr = map_str_key_ptr( string_share_map );
	} else {
	    shared_ptr = "";
	}

	//-- Store this address and info under each CIDR part.
	for (;;) {
	    sprefix = ipv4_range_to_cidr_int( & addr1, & addr2, & cidr_addr, NULL, NULL );
	    if ( sprefix < 0 ) break;
	    result = ipv4_table_insert( lookup_table, cidr_addr, sprefix, (void *) shared_ptr );
	    if ( result < 0 ) {
		goto error_ipv4_table_insert;
	    }
	}
    }

    //-------------------------------------------
    // If no arguments, then just dump the table.
    //-------------------------------------------
    if ( argc <= 1 ) {
	ipv4_table_iterate( lookup_table, table_out, NULL );
	return 0;
    }

    //---------------------------------------
    // Look up each address in the arguments.
    //---------------------------------------
    while ( -- argc && * ++ argv ) {
	result = ipv4_str_to_addr( * argv, & end_ptr, & addr1, & addr2, NULL, NULL, NULL, NULL );
	shared_ptr = ipv4_table_find( lookup_table, addr1 );
	printf( "%u.%u.%u.%u %s\n",
		( addr1 >> 24 ) & 0xff,
		( addr1 >> 16 ) & 0xff,
		( addr1 >>  8 ) & 0xff,
		( addr1       ) & 0xff,
		shared_ptr ? shared_ptr : "not found" );
    }

    //-------------------------------------------
    // Destroy the table, just to be sure we can.
    //-------------------------------------------
    ipv4_table_destroy( lookup_table );

    //----------
    // All done.
    //----------
    return 0;

 error_output:
    fprintf( stderr, "error in %s()\n", shared_ptr );
    return 1;

 error_ipv4_table_create:
    shared_ptr = "ipv4_table_create";
    goto error_output;

 error_map_str_new:
    shared_ptr = "map_str_new";
    goto error_output;

 error_map_str_insert:
    shared_ptr = "map_str_insert";
    goto error_output;

 error_ipv4_table_insert:
    shared_ptr = "ipv4_table_insert";
    goto error_output;

}

