/*
  data.c - counts the data exchanged
 
  (c) 1999-2002 Robert Cheramy <tibob@via.ecp.fr>
  (c) 2000      Samuel Hocevar <sam@via.ecp.fr>
  (c) 1999      Andres Krapf <dae@via.ecp.fr>
  (c) 2001      Loïc Tortay & IN2P3 Computing Center <tortay@cc.ipin2p3.fr>

  200010 : sam   : - attempt to create directory if error opening logfile.
                   - optional clearing of data
  200010 : tibob : optional append to logfile
  200210 : tibob : fix for Solaris

*/

/*
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include "config.h"
#include "data.h"
#include "filter.h"
#include "init.h"
#include "utils.h"

extern struct OptionsType Options;
extern struct AllLogsType *pAllLogs;
extern char *device;
extern ipfm_timezone tz;

void data_add(struct AllLogsType *pLog, u_int32_t ip, int in, int out) {
  struct ipfm_data *p_datatmp;

  if (NULL == pLog->Data || pLog->Data->ip > ip) {
    /*    p_datatmp = p_data; */
    p_datatmp = xmalloc(sizeof(struct ipfm_data));
    p_datatmp->prev = NULL;
    p_datatmp->next = pLog->Data;
    p_datatmp->ip = ip;
    p_datatmp->in = in;
    p_datatmp->out = out;
    if (pLog->Data != NULL) {
      pLog->Data->prev = p_datatmp;
    }
    pLog->Data = p_datatmp;
    pLog->DataSize++;
  } else {
    for (p_datatmp = pLog->Data;
         (NULL != p_datatmp->next) && (p_datatmp->ip < ip);
         p_datatmp = p_datatmp->next);

    if (p_datatmp->ip == ip) {
      p_datatmp->in += in;
      p_datatmp->out += out;
    } else if (NULL == p_datatmp->next && p_datatmp->ip < ip) {
      p_datatmp->next = xmalloc(sizeof(struct ipfm_data));
      p_datatmp->next->prev = p_datatmp;
      p_datatmp->next->next = NULL;
      p_datatmp->next->ip = ip;
      p_datatmp->next->in = in;
      p_datatmp->next->out = out;
      pLog->DataSize++;
    } else {
      /*  if (NULL == p_datatmp->next && p_datatmp->ip > ip) {
	  or p_datatmp->ip > ip && NULL != p_datatmp) */
      p_datatmp->prev->next = xmalloc(sizeof(struct ipfm_data));
      p_datatmp->prev->next->prev = p_datatmp->prev;
      p_datatmp->prev->next->next = p_datatmp;
      p_datatmp->prev->next->ip = ip;
      p_datatmp->prev->next->in = in;
      p_datatmp->prev->next->out = out;
      p_datatmp->prev = p_datatmp->prev->next;
      pLog->DataSize++;
    }
  }
}

void data_dump(struct AllLogsType *pLog) {
  pid_t pid;
  char *FileName;

  FileName = timefile(pLog->LogFile, pLog->NextDump);
  /* Avoid stdout conflicts between son and father */
  fflush(stdout);
  pid = fork();
  if (-1 == pid) {
    fprintf(stderr, "couldn't fork\n");
    xfree(FileName);
    return;
  } else if (0 == pid) {
    FILE *logfile;
    char DataToFile[MAX_DATA_SIZE];
    char *stringTime;
    char *timezonestring;

    if (tz == local) {
      timezonestring = "local time";
    } else {
      timezonestring = "UTC";
    }

    DataSort(pLog);

    if (1 == pLog->Append) {
      /* append to file */
      logfile = fopen(FileName, "a");
    } else {
      /* replace file */
      logfile = fopen(FileName, "w");
    }
    /* If we failed to create the file, it may be due to a nonexistent
     * directory (which causes ENOENT). we try to create the directory. */
    if (NULL == logfile && ENOENT == errno) {
      if (-1 != makedirname(FileName)) {
        if (1 == pLog->Append) {
          /* append to file */
          logfile = fopen(FileName, "a");
        } else {
          /* replace file */
          logfile = fopen(FileName, "w");
        }
      }
    }
    xfree(FileName);
    /* Check again if we could open the file */
    if (NULL == logfile) {
      fprintf(stderr, "Error opening log file. Exiting.\n");
      /* As under linux, pcap uses atexit to restore non promiscuous mode,
         we use _exit() to avoid unsetting promiscuous mode when the child
         exits
       */
      _exit(1);
    }

    stringTime = timefile("%Y/%m/%d %H:%M:%S", pLog->NextDump);

    fprintf(logfile, "# IPFMv%s %s (%s) -- dump every %ldd%02ld:%02ld:%02ld -- listening on %s\n",
            VERSION, stringTime, timezonestring,
            pLog->DumpInterval / (60*60*24),
            (pLog->DumpInterval / (60*60)) % 24,
            (pLog->DumpInterval / (60)) % 60,
            pLog->DumpInterval % 60,
            device);

    fprintf(logfile, "# %-33s%15s%15s%15s\n",
	    "Host",
	    "In (bytes)",
	    "Out (bytes)",
	    "Total (bytes)");
    while (NULL != pLog->Data) {
      DataFormat(pLog, pLog->Data, DataToFile, MAX_DATA_SIZE);
      fprintf(logfile, "%s", DataToFile);
      /* Do not forget to free tables */
      if (NULL != pLog->Data->next) {
	pLog->Data = pLog->Data->next;
	xfree(pLog->Data->prev);
	pLog->Data->prev = NULL;
      } else {
	pLog->Data = NULL;
      }
    }
    pLog->DataSize = 0;

    fprintf(logfile, "# end of dump %s\n", stringTime);
    xfree(stringTime);
    if (1 == pLog->Append) {
      fprintf(logfile, "\n");
    }
    fclose(logfile);
    /* As under linux, pcap uses atexit to restore non promiscuous mode,
       we use _exit() to avoid unsetting promiscuous mode when the child
       exits
    */
    _exit(0);
  }
  else {
    xfree(FileName);
  }
}

void data_clear(struct AllLogsType *pLog) {
  while (NULL != pLog->Data)
  {
    if (NULL != pLog->Data->next) {
      pLog->Data = pLog->Data->next;
      xfree(pLog->Data->prev);
      pLog->Data->prev = NULL;
    } else {
      xfree(pLog->Data);
      pLog->Data = NULL;
    }
  }
  pLog->DataSize = 0;
}

void DataSort(struct AllLogsType *pLog) {
  if (pLog->Sort) {
    int j;
    int DataSize = pLog->DataSize;
    struct ipfm_data *pTempData;
    struct ipfm_data **pDataArray;

    pDataArray = xmalloc (DataSize * sizeof(struct ipfm_data *));

    j = 0;
    for (pTempData = pLog->Data;
         NULL != pTempData;
         pTempData = pTempData->next)
      pDataArray[j++] = pTempData;

    if (DataSize) {
      qsort(pDataArray, DataSize, sizeof(struct ipfm_data *), pLog->SortFunc);

      for (j = 1; j < DataSize-1; j++) {
	pDataArray[j]->next = pDataArray[j+1];
	pDataArray[j]->prev = pDataArray[j-1];
      }

      if (1 < DataSize) {
	pDataArray[0]->next = pDataArray[1];
	pDataArray[DataSize-1]->prev = pDataArray[DataSize-2];
      }
      pDataArray[0]->prev = NULL;
      pDataArray[DataSize-1]->next = NULL;

      pLog->Data = pDataArray[0];
      xfree(pDataArray);
    }
  }
}

int DataCompareOut(const void *ptr1, const void *ptr2) {
  if ((**(struct ipfm_data **)ptr1).out > (**(struct ipfm_data **)ptr2).out)
    return(-1);

  else if ((**(struct ipfm_data **)ptr1).out < (**(struct ipfm_data **)ptr2).out)
    return(1);

  else
    return(0);
}

int DataCompareIn(const void *ptr1, const void *ptr2) {
  if ((**(struct ipfm_data **)ptr1).in > (**(struct ipfm_data **)ptr2).in)
    return(-1);

  else if ((**(struct ipfm_data **)ptr1).in < (**(struct ipfm_data **)ptr2).in)
    return(1);

  else
    return(0);
}

int DataCompareTotal(const void *ptr1, const void *ptr2) {
  if ((**(struct ipfm_data **)ptr1).out + (**(struct ipfm_data **)ptr1).in >
      (**(struct ipfm_data **)ptr2).out + (**(struct ipfm_data **)ptr2).in)
    return(-1);

  else if ((**(struct ipfm_data **)ptr1).out + (**(struct ipfm_data **)ptr1).in <
	   (**(struct ipfm_data **)ptr2).out + (**(struct ipfm_data **)ptr2).in)
    return(1);

  else
    return(0);
}

void DataFormat(struct AllLogsType *pLog, struct ipfm_data *pData,
                char *pFormatedData, int BufLen) {
  struct in_addr addr;
  int NotLookedUp = 0;
  struct hostent *he = NULL;

  memset(pFormatedData, 0, BufLen);
  addr.s_addr = pData->ip;

  if (pLog->ReverseLookup) {
    he = gethostbyaddr((char *) &addr, sizeof (struct in_addr), AF_INET);
    if ((NULL != he) && (strlen(he->h_name) <= 34) &&
        (strlen(he->h_name) > 0)) {
      snprintf(pFormatedData, BufLen, "%-35s%15" LONGINTFORMAT "%15" LONGINTFORMAT "%15" LONGINTFORMAT "\n",
               he->h_name,
               pData->in, 
               pData->out, 
               pData->in + pData->out);
    } else {
      NotLookedUp = 1;
    }
  }
  if (!pLog->ReverseLookup || NotLookedUp) {
    char *res;
    res = inet_ntoa(addr);
      snprintf(pFormatedData, BufLen, "%-35s%15" LONGINTFORMAT "%15" LONGINTFORMAT "%15" LONGINTFORMAT "\n",
	     res,
	     pData->in, 
	     pData->out, 
	     pData->in + pData->out);
  }
}
