/*
**  $Id: network_db.c,v 1.1 1999/03/31 22:10:27 gregm Exp $
**
**  GXSNMP -- An snmp mangament application
**  Copyright (C) 1998 Gregory McLean
**
**  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, Cambridge, MA 02139, USA.
**
**  Subroutines for accessing the network database
**
**  THIS MODULE USES THE OLD DATA STRUCTURES AND IS MOSTLY OBSOLETE
*/

#include <glib.h>
#include <g_sql.h>

/*
**  These are needed for inet_addr().  Once the network printing and 
**  scanning routines are generalized, these #includes can be removed 
*/

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "iputil.h"
#include "new-struct.h"

#include "db_interface.h"
#include "network_db.h"

/******************************************************************************
**
**  Data structures for storing the network representation in memory
**
******************************************************************************/

static GList 	    * G_networks;	/* GList of all networks */
static GHashTable   * H_network_names;	/* Hash table of network names */

/*******************************************************************************
**
**  Private function to initialize the in-memory representation of the
**  database.
**
**  The network in-memory database consists of a GList of all of the 
**  networks, and a hash table of the network names.
**
*******************************************************************************/

static void
db_mem_init ()
{
  G_networks = NULL;	
  H_network_names = g_hash_table_new (g_str_hash, g_str_equal);
}

/*******************************************************************************
**
**  Private function to add an entry to the in-memory representation of
**  the database.
**
*******************************************************************************/

static void
db_mem_add (DB_networks * entry)
{
  DB_networks * dbn;

  G_networks = g_list_append (G_networks, entry);

  entry->hash_name = g_strdup (entry->nl_name); 

  dbn = g_hash_table_lookup (H_network_names, entry->hash_name);
  if (dbn)
    {
      g_print ("network_db: Could not add entry to network database "
	       "with duplicate network name %s\n", entry->hash_name);
      g_assert_not_reached ();			/* Die a horrible death */
    }

  g_hash_table_insert (H_network_names, entry->hash_name, entry);
}

/******************************************************************************
**
**  Private function to delete an entry from the in-memory representation
**  of the database.
**
******************************************************************************/

static void
db_mem_delete (DB_networks * entry)
{
  DB_networks * dbn;

  G_networks = g_list_remove (G_networks, entry);	   

  dbn = g_hash_table_lookup (H_network_names, entry->hash_name);
  if (!dbn)
    {
      g_print ("network_db: Entry with name %s not found in network "
               "database!\n", entry->hash_name);
      g_assert_not_reached ();                  /* Die a horrible death */
    }

  g_hash_table_remove (H_network_names, entry->hash_name);
  g_free (entry->hash_name);
  entry->hash_name = NULL;

}

/******************************************************************************
**
**  Private function to modify an entry in the in-memory representation
**  of the database.
** 
**  If the user changed the name of the network, rehash the entry.
**
******************************************************************************/

static void
db_mem_modify (DB_networks * entry)
{
  if (strcmp (entry->nl_name, entry->hash_name))
    {
      db_mem_delete (entry);
      db_mem_add (entry);
    }
}

/*******************************************************************************
**
**  network_db_load_table ()
**
**  This subroutine loads the network table into storage.
**
**  Return Values:
**
**  TRUE  --  The network table was successfully loaded
**  FALSE --  A problem occurred, and the network table was not loaded.
**
*******************************************************************************/

static gchar * load_query = "SELECT * FROM networks";

gboolean
network_db_load_table  (G_sql * network_db, 
			gchar * network_database)
{
  G_sql_connection  * dbc; 		    /* Handle of database connection */
  G_sql_query       * dbq;		    /* Handle of database query */
  DB_networks        * n;		    /* Used to build each object */
  G_sql_accelerator ac[6] = { NULL, };      /* Query accelerators */

  db_mem_init ();			    /* Initialize the hash tables */


  if (!(dbc = g_sql_connect (network_db)))
    return FALSE;

  if (!g_sql_select (dbc, network_database))
    {
      g_sql_disconnect (dbc);   
      return FALSE;
    }

  if (!(dbq = g_sql_query (dbc, load_query, strlen(load_query))))
    {
      g_sql_disconnect (dbc);
      return FALSE;
    }

  while ((g_sql_next_row (dbq)))            /* Repeat until out of rows */
    {
      gchar * work;
      gint    workint;

      n = g_new0 (DB_networks, 1);	    /* Allocate storage for object */

      g_sql_field_string (dbq, &ac[0], "name",            &n->nl_name);

      g_sql_field_string (dbq, &ac[1], "address",         &work);
      n->nl_net.s_addr = inet_addr (work);
      g_free (work);

      g_sql_field_string (dbq, &ac[2], "mask",            &work);
      n->nl_mask.s_addr = inet_addr (work);
      g_free (work);

      g_sql_field_int    (dbq, &ac[3], "_rowid",          &n->rowid);

      g_sql_field_int    (dbq, &ac[4], "map_x",           &workint);
      n->nl_location.x = (double) workint;

      g_sql_field_int    (dbq, &ac[5], "map_y",           &workint);
      n->nl_location.y = (double) workint;

      if (network_db_unique (n))	/* Make sure the entry is unique */
	{
	  db_mem_add (n);		/* Yes - add to in-storage database */
	}
      else
	{
	  g_print ("network_db_load_table: duplicate entry for network %s "
		   "rowid %d ignored.", n->nl_name, n->rowid);
	  g_free (n);
	}
    }

  g_sql_free_query (dbq);		    /* Finished with query */ 
  g_sql_disconnect (dbc);                   /* Finished with database */

  return TRUE;				    /* Return resulting table */
}

/*******************************************************************************
**
**  network_db_add_entry ()  --  Add an entry to the network database.
**
**  Return Values:
**
**  TRUE  --  The entry was successfully added to the database table.
**  FALSE --  An error occurred, and the entry was not added to the
**            database table.
**
*******************************************************************************/

gboolean        network_db_add_entry    (G_sql          * network_db,
                                         gchar          * network_database,
                                         DB_networks     * entry)
{
  G_sql_connection * dbc;                   /* Database connection handle */
  G_sql_query      * dbq;                   /* Handle of open query */

  char	        * query;		    /* Pointer to query text */
  char 		  datebuf [21];		    /* Buffer for storing timestamp */

  char		* address;
  char		* mask;

  db_mem_add (entry);			/* Add to in-memory database first */

  if (!(dbc = g_sql_connect (network_db)))
    return FALSE;

  if (!g_sql_select (dbc, network_database))
    {
      g_sql_disconnect (dbc);
      return FALSE;
    }

  db_timestamp(&datebuf[0], sizeof(datebuf));

  address = g_strdup (inet_ntoa (entry->nl_net));
  mask    = g_strdup (inet_ntoa (entry->nl_mask));

  query = g_strdup_printf (
            "INSERT INTO networks (name, address, mask, map_x, map_y, "
            "created, modified) "
            "VALUES ('%s', '%s', '%s', %i, %i, '%s', '%s')",
            entry->nl_name, 
	    address,
            mask,
            (int) entry->nl_location.x,
            (int) entry->nl_location.y,
            datebuf, datebuf);
  g_free (address);

  if (!(dbq = g_sql_query (dbc, query, strlen(query))))    /* Query database */
    {
      g_free (query);
      g_sql_disconnect (dbc);
      return FALSE;
    }

  g_free (query);
  g_sql_free_query (dbq);                   /* Finished with query */
  g_sql_disconnect (dbc);                   /* Finished with database */

  return FALSE;
}

/*******************************************************************************
**
**  network_db_update_entry ()  --  Update an entry in the SNMP configuration
**                                  database.
**
**  This subroutine updates an SNMP configuration record in the SNMP
**  configuration database.  This subroutine operates on the database
**  only.  
**
**  Return Values:
**
**  TRUE  -- The entry was successfully updated
**  FALSE -- An error occurred, and the entry was not updated.
**
*******************************************************************************/

gboolean        network_db_update_entry (G_sql          * network_db,
                                         gchar          * network_database,
                                         DB_networks     * entry)
{
  G_sql_connection  * dbc;                  /* Handle of open database */
  G_sql_query       * dbq;                  /* Handle of open query */

  char              * query;                /* Pointer to query text */
  char                datebuf [21];         /* Buffer for storing timestamp */

  char          * address;
  char          * mask;

  db_mem_modify (entry);		/* Update the in-memory database */

  if (!(dbc = g_sql_connect (network_db)))
    return FALSE;

  if (!g_sql_select (dbc, network_database))
    {
      g_sql_disconnect (dbc);
      return FALSE;
    }

  db_timestamp(&datebuf[0], sizeof(datebuf));

  address = g_strdup (inet_ntoa (entry->nl_net));
  mask    = g_strdup (inet_ntoa (entry->nl_mask));

  query = g_strdup_printf (
            "UPDATE networks SET "
		"name='%s', "
		"address='%s', "
		"mask='%s', "
		"map_x=%i, "
		"map_y=%i, "
		"modified='%s' "
		"WHERE _rowid=%d",
		entry->nl_name, 
		address,
		mask,
		(int) entry->nl_location.x,
		(int) entry->nl_location.y,
		datebuf,
	        entry->rowid);

  g_free (address);
  g_free (mask);

  if (!(dbq = g_sql_query (dbc, query, strlen(query))))    /* Query database */
    {
      g_free (query);
      g_sql_disconnect (dbc);
      return FALSE;
    }

  g_free (query);
  g_sql_free_query (dbq);                   /* Finished with query */
  g_sql_disconnect (dbc);                   /* Finished with database */

  return TRUE;
}

/*******************************************************************************
**
**  network_db_delete_entry ()  --  Delete an entry in the network database
**
**  Return Values:
**
**  TRUE  -- The entry was successfully updated
**  FALSE -- An error occurred, and the entry was not updated.
**
*******************************************************************************/

gboolean        network_db_delete_entry (G_sql          * network_db,
                                         gchar          * network_database,
                                         DB_networks     * entry)
{
  G_sql_connection * dbc;                   /* Handle of open database */
  G_sql_query      * dbq;                   /* Handle of open query */

  char             * query;                 /* Pointer to query text */
  char               datebuf [21];          /* Buffer for storing timestamp */

  db_mem_delete (entry);	/* Update the in-memory database */

  if (!(dbc = g_sql_connect (network_db)))
    return FALSE;

  if (!g_sql_select (dbc, network_database))
    {
      g_sql_disconnect (dbc);
      return FALSE;
    }

  db_timestamp (&datebuf[0], sizeof(datebuf));
  query = g_strdup_printf (
	      "DELETE FROM hosts WHERE rowid = %d", 
	      entry->rowid);

  if (!(dbq = g_sql_query (dbc, query, strlen (query)))) /* Query database */
    {
      g_free (query);
      g_sql_disconnect (dbc);
      return FALSE;
    }

  g_free (query);
  g_sql_free_query (dbq);                   /* Finished with query */
  g_sql_disconnect (dbc);                   /* Finished with database */

  return TRUE;
}

/*******************************************************************************
**
**  network_db_unique ()  --  Check an entry for key uniqueness.
**
**  Function to see if an entry duplicates an existing entry in the
**  network database.  
**
**  Returns:  TRUE  if the key combination does not exist in the database
**            FALSE if the key combination already exists in the database
**
*******************************************************************************/

gboolean
network_db_unique (DB_networks * entry)
{

  if (g_hash_table_lookup (H_network_names, entry->nl_name))
    return FALSE;

  return TRUE;
}

/******************************************************************************
**
**  network_db_list_all ()  --  Obtain a list of all known networks
**
**  Function to simply return the network glist
**
******************************************************************************/

GList *
network_db_list_all (void)
{
  return G_networks;
}

/******************************************************************************
**
**  network_db_terminate ()  --  Release all memory associated with the 
**                               network database.  We destroy the list, 
**				 the hash tables, the entries, and all   
**				 data items attached to the entries.
**
**  Since the only time this is called is right before the program terminates,
**  in one sense it is an exercise in futility, but it might be useful to
**  know how much memory gxsnmp leaks, and in order to know that, we need to
**  get rid of all known allocated memory.
**	
******************************************************************************/

void
network_db_terminate (void)
{
  GList      * gl;
  DB_networks * dbn;

  gl = G_networks;
  while (gl)
    {
      dbn = (DB_networks *) gl->data;	/* Address the DB_network structure */

      g_free (dbn->nl_name);		/* Free the network name */
      g_free (dbn);			/* Free the network structure */
      gl = gl->next;
    }

/*
  g_hash_table_destroy (H_network_names);
  g_list_free (G_networks);
*/

}

/*******************************************************************************
**
**  network_db_find_by_name ()  --  Search the database for the network 
**				    with the specified name.
**
**  Returns:  Pointer to DB_network structure if the network is found
**            NULL if the network name was not found in the database
**
*******************************************************************************/

DB_networks *
network_db_find_by_name (gchar * name)
{
  return g_hash_table_lookup (H_network_names, name);
}

/******************************************************************************
**
**  network_db_find_by_interface_address ()  --
**
**  Given an interface address, return a GList of all networks that 
**  this host could be attached to, taking subnetting into account.
**
**  This will take the place of nl_get_host_net ().  I can't really write
**  this until more functions and data are added to the g_transport
**  structure.  Specifically, I need a function to tell me, given a host
**  address and a network address/subnet, whether the host address falls
**  within the network address range.
**  
**  FIXME:  Change this to use the NetAddr structure.
**
******************************************************************************/

GList *
network_db_find_by_interface_address (hosts * host)
{
  GList * gl;
  GList * result;

  gl = G_networks;
  result = NULL;

  while (gl)
    {
/*
**  Run through the network linked list, calling the compare routine
**  with the host address, network address, and network subnet mask.
**  If the host address falls within the network address range, then
**  g_list_append () the network to the result GList.
*/
     if (is_host_on_net (host, gl->data))
       result = g_list_append (result, gl->data);

      gl = gl->next;
    }

  return result;
}

/* EOF */
