/* Copyright (C) 2001 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: napigator.c,v 1.5 2002/07/24 22:47:08 khaytsus Exp $ */

/*** support for Napigator ***/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifndef ROUTING_ONLY

#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#endif /* !WIN32 */
#include <errno.h>
#include "opennap.h"
#include "debug.h"
#include "md5.h"

static char *stat_server_salt = 0;

static void *
lookup_hostname2 (void)
{
    struct hostent *he;
    char  *servername;

    /* get our canonical host name */
    gethostname (Buf, sizeof (Buf));
    he = gethostbyname (Buf);
    if (he) {
       	servername = STRDUP (he->h_name);
    } else {
      	log_message_level ( LOG_LEVEL_SERVER, "lookup_hostname: unable to find fqdn for %s", Buf);
	servername = STRDUP (Buf);
    }
    return servername;
}

void
stat_server_read (void)
{
    int     n;
    char   *p;
    int     do_close = 0;
    char   *salt, *servername;

/*    log_message_level (LOG_LEVEL_DEBUG, "stat_server_read: entering and reading."); */
    n = READ (global.stat_server_fd, Buf, sizeof (Buf));
/*    log_message_level (LOG_LEVEL_DEBUG, "stat_server_read: successfully read. Result: %d", n); */
    if (n <= 0) {
        if (n == -1) {
            log_message_level (LOG_LEVEL_ERROR, "stat_server_read: %s (%d)", STRERROR (N_ERRNO), N_ERRNO);
        }
        log_message_level ( LOG_LEVEL_ERROR, "stat_server_read: got hangup");
        do_close = 1;
    }
    else
    {
	/* find end of line */
	p = strpbrk (Buf, "\r\n");
	if (*p)
	    *p = 0;
	p = Buf;

	n = atoi (Buf);
/*    log_message_level (LOG_LEVEL_DEBUG, "stat_server_read: got message %d", n); */
	if (n == 220)
	{
	    /* got connect

	     * first, save the salt */
	    next_arg (&p);
	    salt = next_arg (&p);
	    if (!salt)
	    {
		log_message_level ( LOG_LEVEL_ERROR, "stat_server_read:unable to get salt string");
		strcpy (Buf, "QUIT\r\n");
		WRITE (global.stat_server_fd, Buf, strlen (Buf));
	    }
	    else
	    {
		if (stat_server_salt)
		    FREE (stat_server_salt);
		stat_server_salt = STRDUP (salt);

		snprintf (Buf, sizeof (Buf), "USER %s\r\n", global.stat_user);

		WRITE (global.stat_server_fd, Buf, strlen (Buf));
	    }
	}
	else if (n == 221)
	{
	    /* server hangup */
	    do_close = 1;
	}
	else if (n == 300)
	{
	    struct md5_ctx md;
	    char    hash[33];

	    md5_init_ctx (&md);
	    md5_process_bytes (stat_server_salt, strlen (stat_server_salt),
			       &md);
	    md5_process_bytes (global.stat_pass, strlen (global.stat_pass),

			       &md);
	    md5_finish_ctx (&md, hash);
	    expand_hex (hash, 16);
	    hash[32] = 0;
	    snprintf (Buf, sizeof (Buf), "PASS %s\r\n", hash);
	    WRITE (global.stat_server_fd, Buf, strlen (Buf));
	}
	else if (n == 201)
	{
	    /* auth complete */
	    log_message_level ( LOG_LEVEL_SERVER, "stat_server_read: logged in");

	    /* send updated ip:port in case we are a dynamic server */
	    snprintf (Buf, sizeof (Buf), "IPPORT %s %s %d\r\n",
		    global.report_name,
		    global.report_ip,
		    global.report_port);
	    WRITE (global.stat_server_fd, Buf, strlen (Buf));

	    /* force immediate update */
	    stat_server_push ();
	}
	/* The >= comparison made the comparison for == 400 some
	   lines below not to work any more. So dynamic server
	   addresses are not updated any more.
	   But this solution is cumbersome - to say the least.
	   Is there any better - and ofc any quicker detection
	   of an ip address change possible? Now it takes up 
	   to stats_click seconds for a hub to recognize the 
	   change.
	
	*/
	else if (n / 100 > 4)
	{
	    /* something failed */
	    log_message_level ( LOG_LEVEL_SERVER, "stat_server_read:%s", Buf);
	    strcpy (Buf, "QUIT\r\n");
	    WRITE (global.stat_server_fd, Buf, strlen (Buf));
	}
	/* this part is needed if a server owner wants to use an alias
	   usually server aliases are not resolvable - leodav
	*/
        else if (n == 400)
        {
            servername = lookup_hostname2 ();
                                                 
            log_message_level ( LOG_LEVEL_SERVER, "stat_server_read:%s", Buf);
	    strcpy (Buf, "QUIT\r\n");
	    WRITE (global.stat_server_fd, Buf, strlen (Buf));
            Server_Ip = lookup_ip (servername);
            global.report_ip = STRDUP (my_ntoa (Server_Ip));
            log_message_level ( LOG_LEVEL_ERROR, "napigator.c:debug: %s", servername);
        }
	else if (n == 200)
	{
	    /* stats updated successfully */
	}
	else
	{
	    log_message_level ( LOG_LEVEL_ERROR, "stat_server_read: unhandled:%s", Buf);
	}
    }

    if (do_close)
    {
	log_message_level ( LOG_LEVEL_SERVER, "stat_server_read: closing connection. fd: %d", global.stat_server_fd);
	CLOSE (global.stat_server_fd);
#if HAVE_POLL
	remove_fd (global.stat_server_fd);
#else
	FD_CLR (global.stat_server_fd, &global.read_fds);
	FD_CLR (global.stat_server_fd, &global.write_fds);
#endif
	global.stat_server_fd = -1;
    }
}


#ifdef WIN32
#if 0   /* This was experimental code only! */

/* Windows specific GotNap connect procedure without nonblocking mode */

static int make_gotnap_tcp_connection (const char *host, int port, unsigned int *ip) {
 struct sockaddr_in     sin;
 int                    f;

    memset (&sin, 0, sizeof (sin));
    sin.sin_port = htons (port);
    sin.sin_family = AF_INET;
    if ((sin.sin_addr.s_addr = lookup_ip (host)) == 0) {
        return -1; }
    if (ip) {
        *ip = sin.sin_addr.s_addr; }
    f = socket (AF_INET, SOCK_STREAM, 0);
    if (f < 0) {
        logerr ("new_tcp_socket", "socket");
	    return -1; }
/* if an interface was specify, bind to it before connecting */
    if (Interface) {
        bind_interface (f, Interface, 0); }
/* turn on TCP/IP keepalive messages */
    set_keepalive (f, 1);
    log_message_level (LOG_LEVEL_DEBUG, "make_gotnap_tcp_connection: connecting to %s:%hu",
	                inet_ntoa (sin.sin_addr), ntohs (sin.sin_port));
    if (connect (f, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
        if (N_ERRNO != EINPROGRESS
/* winsock returns EWOULDBLOCK even in nonblocking mode! ugh!!! */
        && N_ERRNO != EWOULDBLOCK) {
            nlogerr ("make_tcp_connection", "connect");
            CLOSE (f);
            return -1; }
        log_message_level (LOG_LEVEL_SERVER, "make_gotnap_tcp_connection: connection to %s in progress", host); }
    else {
        log_message_level (LOG_LEVEL_SERVER, "make_gotnap_tcp_connection: connection established to %s", host); }
    return f;
}

#endif
#endif /* WIN32 */


static int check_gotnap_connect_status (int f) {
 socklen_t  len;
 int        err;

    len = sizeof (err);
    if (getsockopt (f, SOL_SOCKET, SO_ERROR, SOCKOPTCAST & err, &len) != 0) {
    	nlogerr ("check_gotnap_connect_status", "getsockopt");
    	return h_errno;
    }
    log_message_level (LOG_LEVEL_DEBUG, "check_gotnap_connect_status: error %d (%s)",
        err, STRERROR (err));
    return err;
}



void stat_server_push (void)
{
    unsigned int    ip;
    int             err;

    log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: stat_server_fd: %d", global.stat_server_fd);
    if (global.stat_server_fd == -1) {
/*        log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: no existing connection."); */
    /* attempt to make new connection to stats server */
        if (!global.stat_user || !*global.stat_user ||
            !global.stat_pass || !*global.stat_pass ||
            !global.stat_server || !*global.stat_server) {
            log_message_level (LOG_LEVEL_SERVER, "Skipping report stats to Napigator. User | Pass | Server not set.");
	        return;		/* nothing defined */
        }
        log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: making new TCP connection.");
        global.stat_server_fd = make_tcp_connection (global.stat_server,
                                                global.stat_server_port, &ip);
        log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: stat_server_fd after connection attempt: %d", global.stat_server_fd);
        if (global.stat_server_fd != -1) {
    /* do a nonblocking connect */
            add_fd (global.stat_server_fd);
            set_write (global.stat_server_fd);
            set_read (global.stat_server_fd);
        } else {
            log_message_level (LOG_LEVEL_ERROR, "stat_server_push: unable to connect to stat server %s:%i! %s (%d)",
                        global.stat_server, global.stat_server_port, STRERROR (N_ERRNO), N_ERRNO);
        }
        return;
    }

/*    log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: assuming open connection."); */

    snprintf (Buf, sizeof (Buf), "STATS %s %u %u 0 %.0f 0\r\n",
	      global.report_name, Users->dbsize, Num_Files, Num_Gigs * 1024);

    err = check_gotnap_connect_status (global.stat_server_fd);
    if (err || WRITE (global.stat_server_fd, Buf, strlen (Buf)) == -1) {
        log_message_level ( LOG_LEVEL_ERROR,  "stat_server_push: write error! fd: %d, Bytes: %d, write: %s (%d)",
                            global.stat_server_fd, strlen (Buf), STRERROR (N_ERRNO ? N_ERRNO : err), N_ERRNO ? N_ERRNO : err);
        if (global.stat_server_fd > 0) {
            log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: closing fd after error.");
            CLOSE (global.stat_server_fd);
        }
#if HAVE_POLL
        remove_fd (global.stat_server_fd);
#else
        log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: clearing fds");
        FD_CLR (global.stat_server_fd, &global.read_fds);
        FD_CLR (global.stat_server_fd, &global.write_fds);
#endif
        global.stat_server_fd = -1;
    }
    log_message_level (LOG_LEVEL_DEBUG, "stat_server_push: leaving.");
}

#endif /* !ROUTING_ONLY */
