/*
 * Copyright (c) 2001 Fenris, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * File:  network.c
 *
 * Purpose:  This source file defines the interface for manipulating the
 * network object (a structure of 3 lists of lists)  Understanding the 
 * structure of this object and its interface was hard.  Read on at your 
 * own risk.  
 *
 * Basically, to buffer the packet information coming in, a structure was
 * needed that could organize the information according to sources,
 * destinations, and unique pairs.  We need a timestamp for aging and a count
 * for rate information.  Each of the 3 main lists contains a list of node
 * (either src, dst, or pair) along with the count of instances for each unique
 * node.  Maybe a bit convoluted, but it works nicely now.  --Hunter
 *
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "globals.h"
#include "network.h"
#include "resolver.h"


int init_net(Network* net)
{
    pthread_mutex_lock(net_mutex);

    if ((net->src_list = malloc(sizeof(Net_List))) == NULL)
        die("malloc");
    if ((net->dst_list = malloc(sizeof(Net_List))) == NULL)
        die("malloc");
    if ((net->pair_list = malloc(sizeof(Net_List))) == NULL)
        die("malloc");

    init_net_list(net->src_list);
    init_net_list(net->dst_list);
    init_net_list(net->pair_list);
    net->timeout = 10;

    pthread_mutex_unlock(net_mutex);
    return 0;
}


void into_net(Network* net, Net_Obj* obj)
{
    Net_List * 		netlist;
    Net_List * 		new_list;
    Net_Obj * 		new_obj;
    struct in_addr      in;
    int 		flag;
    unsigned char       msg[NET_TO_RSLV_LEN];
    unsigned char       *name;

    pthread_mutex_lock(net_mutex);

    if (net == NULL) {
        die("net is null");
    }

    netlist = net->src_list;
    flag = SRC;
    if (net_check_sublists(netlist, obj, flag) != 1) {
        pthread_mutex_unlock(net_mutex);
        if ((new_list = malloc(sizeof(Net_List))) == NULL)
            die("malloc");

        if ((new_obj = malloc(sizeof(Net_Obj))) == NULL)
            die("malloc");

        pthread_mutex_lock(net_mutex);
        memcpy(new_obj, obj, sizeof(Net_Obj));
        init_net_list(new_list);
        append_net_list(netlist, new_list);
        append_net_list(new_list, new_obj);

        if ((name = find_cache_obj(&dnscache, new_obj->src)) == NULL) {
            in.s_addr = htonl(new_obj->src);
            strncpy(new_list->src, inet_ntoa(in), MAX_HOSTNAME_LEN);
            msg[0] = SRC;
            msg[1] = new_obj->src >> 24;
            msg[2] = (new_obj->src << 8) >> 24;
            msg[3] = (new_obj->src << 16) >> 24;
            msg[4] = (new_obj->src << 24) >> 24;
            write(net_to_rslv[1], &msg, NET_TO_RSLV_LEN);
        } else {
            strncpy(new_list->src, name, MAX_HOSTNAME_LEN);
        }

        if ((name = find_cache_obj(&dnscache, new_obj->dst)) == NULL) {
            in.s_addr = htonl(new_obj->dst);
            strncpy(new_list->dst, inet_ntoa(in), MAX_HOSTNAME_LEN);
        } else {
            strncpy(new_list->dst, name, MAX_HOSTNAME_LEN);
        }
    }

    netlist = net->dst_list;
    flag = DST;
    if (net_check_sublists(netlist, obj, flag) != 1) {
        pthread_mutex_unlock(net_mutex);
        if ((new_list = malloc(sizeof(Net_List))) == NULL)
            die("malloc");
        if ((new_obj = malloc(sizeof(Net_Obj))) == NULL)
            die("malloc");
        pthread_mutex_lock(net_mutex);
        memcpy(new_obj, obj, sizeof(Net_Obj));
        init_net_list(new_list);
        append_net_list(netlist, new_list);
        append_net_list(new_list, new_obj);

        if ((name = find_cache_obj(&dnscache, new_obj->src)) == NULL) {
            in.s_addr = htonl(new_obj->src);
            strncpy(new_list->src, inet_ntoa(in), MAX_HOSTNAME_LEN);
        } else {
            strncpy(new_list->src, name, MAX_HOSTNAME_LEN);
        }

        if ((name = find_cache_obj(&dnscache, new_obj->dst)) == NULL) {
            in.s_addr = htonl(new_obj->dst);
            strncpy(new_list->dst, inet_ntoa(in), MAX_HOSTNAME_LEN);
            msg[0] = DST;
            msg[1] = new_obj->dst >> 24;
            msg[2] = (new_obj->dst << 8) >> 24;
            msg[3] = (new_obj->dst << 16) >> 24;
            msg[4] = (new_obj->dst << 24) >> 24;
            write(net_to_rslv[1], &msg, NET_TO_RSLV_LEN);
        } else {
            strncpy(new_list->dst, name, MAX_HOSTNAME_LEN);
        }
    }

    netlist = net->pair_list;
    flag = PAIR;
    if (net_check_sublists(netlist, obj, flag) != 1) {
        pthread_mutex_unlock(net_mutex);
        if ((new_list = malloc(sizeof(Net_List))) == NULL)
            die("malloc");
        pthread_mutex_lock(net_mutex);
        init_net_list(new_list);
        append_net_list(netlist, new_list);
        append_net_list(new_list, obj);
    }

    pthread_mutex_unlock(net_mutex);

    return;
}


int net_check_sublists(Net_List* netlist, Net_Obj* obj, int flag)
{
    Net_Node * 		net_node 		= NULL;
    Net_List * 		sublist 		= NULL;
    Net_Node * 		net_sublist_node 	= NULL;
    Net_Obj * 		sublist_obj 		= NULL;
    Net_Obj * 		new_obj;

    /* verify good non-null ptrs passed to function */
    if (netlist == NULL) return -2;
    if (obj == NULL) return -3;

    /* verify network list (src, dst, pair) not empty_n */
    net_node = netlist->head;
    if (net_node == NULL) return 0;

    /* loop through the nodes in the network list (src, dst, pair) */
    while (net_node != NULL) {
        /* verify that the sublist exists, if not, then remove the node */ 
        sublist = (Net_List*)net_node->obj;
        if (sublist == NULL) {
            delete_net_list_node(netlist, net_node);
        } else {
            /* verify sublist is non-empty_n, if empty_n, then remove the node*/
            net_sublist_node = sublist->head;
            if (net_sublist_node == NULL) {
                delete_net_list_node(netlist, net_node);
            } else {
                /* verify sublist head has object, if not, delete the node */
                sublist_obj = (Net_Obj*)net_sublist_node->obj;
                if (sublist_obj == NULL) {
                    delete_net_list_node(sublist, net_sublist_node);
                } else {
                    switch (flag) {
                        /* if the sublist head has an object, switch on the flag
                           (src = 0, dst = 1, pair = 2) for the proper truth statement */
                        /* In all cases, if the objects match, return 1 (true),
                           otherwise increment netlist node ptr to next and try again */
                    case SRC:
                        if (obj->src == sublist_obj->src) {
                            if ((new_obj = malloc(sizeof(Net_Obj))) == NULL)
                                die("malloc");
                            memcpy(new_obj, obj, sizeof(Net_Obj));
                            append_net_list(sublist, new_obj);
                            return 1;
                        }
                        break;
                    case DST:
                        if (obj->dst == sublist_obj->dst) {
                            if ((new_obj = malloc(sizeof(Net_Obj))) == NULL)
                                die("malloc");
                            memcpy(new_obj, obj, sizeof(Net_Obj));
                            append_net_list(sublist, new_obj);
                            return 1;
                        }
                        break;
                    case PAIR:
                        if ((obj->src == sublist_obj->src) && (obj->dst == sublist_obj->dst)) {
                            append_net_list(sublist, obj);
                            return 1;
                        }
                        break;
                    default :
                        return -4;
                    }
                }
            }
        }
        net_node = net_node->next;
    }
    /* if all cases failed, then return 0 (false) */
    return 0;
}


void refresh_net(Network* net)
{
    struct timeval 		tc;
    Net_List * 			network_list;
    Net_Node * 			network_node;
    Net_Node * 			next;

    if (net != NULL) { 
        gettimeofday(&tc, NULL);
        tc.tv_sec = tc.tv_sec - net->timeout;

        network_list = net->src_list;
        if (network_list != NULL) {
            network_node = network_list->head;
            while (network_node != NULL) {
                next = network_node->next;
                net_sublist_clear(network_list, network_node, tc);
                network_node = next;
            }
        }

        network_list = net->dst_list;
        if (network_list != NULL) {
            network_node = network_list->head;
            while (network_node != NULL) {
                next = network_node->next;
                net_sublist_clear(network_list, network_node, tc);
                network_node = next;
            }
        }

        network_list = net->pair_list;
        if (network_list != NULL) {
            network_node = network_list->head;
            while (network_node != NULL) {
                next = network_node->next;
                net_sublist_clear(network_list, network_node, tc);
                network_node = next;
            }
        }

        check_resolver(net);
    }
    return;
}


void check_resolver(Network *net)
{
    unsigned char        msg[RSLV_TO_NET_LEN];
    int                  rcvd;
    int                  len;
    Net_Node            *current_net_node;
    Net_List            *net_sublist;
    Net_Node            *net_node;
    Net_Obj             *net_obj;
    Name_Obj            *name_obj;
    char                 type;
    unsigned int         addr;
    char                 name[MAX_HOSTNAME_LEN];
    int                  i;

    while ((rcvd = read(rslv_to_net[0], &msg, RSLV_TO_NET_LEN)) > 0) {
        len = rcvd;
        for (i = 0; i < 100 && len < RSLV_TO_NET_LEN; i++) {
            rcvd = read(rslv_to_net[0], &msg + len, RSLV_TO_NET_LEN - len);
            len += rcvd;
        }

        if (len == RSLV_TO_NET_LEN) {
            type = msg[0];
            addr = msg[1];
            addr <<= 8;
            addr += msg[2];
            addr <<= 8;
            addr += msg[3];
            addr <<= 8;
            addr += msg[4];
            memcpy(name, msg + 5, MAX_HOSTNAME_LEN);

            /* First, append the resolved name to the cache list. */
            if ((name_obj = malloc(sizeof(Name_Obj))) == NULL)
                die("malloc");
            name_obj->address = addr;
            if ((name_obj->name = malloc(len + 1)) == NULL)
                die("malloc");
            strncpy(name_obj->name, name, len);
            name_obj->name[len] = '\0';
            append_cache_list(&dnscache, name_obj);

            /* Update unresolved objects in the net list. */
            switch (type) {
            case SRC:
                if (net->src_list != NULL) {
                    current_net_node = net->src_list->head;
                    while (current_net_node != NULL) {
                        net_sublist = current_net_node->obj;
                        if (net_sublist != NULL) {
                            net_node = net_sublist->head;
                            if (net_node != NULL) {
                                net_obj = net_node->obj;
                                if (net_obj->src == addr) {
                                    strncpy(net_sublist->src, name, MAX_HOSTNAME_LEN);
                                    break;
                                }
                            }
                        }
                        current_net_node = current_net_node->next;
                    }
                }
                break;
            case DST:
                if (net->dst_list != NULL) {
                    current_net_node = net->dst_list->head;
                    while (current_net_node != NULL) {
                        net_sublist = current_net_node->obj;
                        if (net_sublist != NULL) {
                            net_node = net_sublist->head;
                            if (net_node != NULL) {
                                net_obj = net_node->obj;
                                if (net_obj->dst == addr) {
                                    strncpy(net_sublist->dst, name, MAX_HOSTNAME_LEN);
                                    break;
                                }
                            }
                        }
                        current_net_node = current_net_node->next;
                    }
                }
                break;
            default:
                break;
            }
        }
    }

    return;
}


void empty_net(Network* net)
{
    net->timeout = 0;
    refresh_net(net);
}


int net_sublist_clear(Net_List* netlist, Net_Node* net_node, struct timeval tc)
{
    Net_List * 			sublist;
    Net_Node * 			sublist_head;
    Net_Obj * 			obj;
    int 			count;

    sublist = net_node->obj;
    if (sublist == NULL) {
        obj = delete_net_list_node(netlist, net_node);
        free(obj);
    } else {
        sublist_head = sublist->head;
        if (sublist_head != NULL) obj = sublist_head->obj;
        if (obj == NULL) {
            obj = delete_net_list_node(sublist, sublist_head);
            free(obj);
        } else {
            count = 0;
            while((sublist_head != NULL) && (obj->ts.tv_sec < tc.tv_sec)) {
                ++count;
                obj = (Net_Obj*)delete_net_list_head(sublist);
                sublist_head = sublist->head;
                free(obj);
                if (sublist_head != NULL) {
                    obj = sublist_head->obj;
                    if (obj == NULL) {
                        obj = delete_net_list_node(sublist, sublist_head);
                        free(obj);
                        break;
                    }
                } else break;
            }
        }
        if (sublist_head == NULL) {
            free(sublist);
            delete_net_list_node(netlist, net_node);

            return 1;
        }
        return 0;
    }
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1