/*
 * Copyright 1997-2000 Phil Schwan <phil@off.net>
 *
 * 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.
 *
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#else
#  error You did not run configure, did you?
#endif

#ifdef HAVE_SYS_TYPES_H /* required for FreeBSD to work */
#  include <sys/types.h>
#endif

#ifdef HAVE_STRING_H
#  include <string.h>
#endif

#ifdef HAVE_MMAP
#  include <sys/mman.h>
#endif

#ifdef HAVE_FCNTL_H
#  include <fcntl.h>
#endif

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

#ifdef HAVE_CTYPE_H
#  include <ctype.h>
#endif

#include <sys/stat.h>
#include <time.h>

#include <stdio.h>
#include <stdlib.h>
#include "xferstats.h"

extern char MONTHS[12][4];


/*
 * Yanked from phhttpd, copyright (C) 1999, 2000 Red Hat Inc, by Zach Brown
 *
 * This follows the rules laid out in rfc 1808 for resolving
 * relative URLS:
 *
 *          a) All occurrences of "./", where "." is a complete path
 *             segment, are removed.
 *
 *          b) If the path ends with "." as a complete path segment,
 *             that "." is removed.
 *
 *          c) All occurrences of "<segment>/../", where <segment> is a
 *             complete path segment not equal to "..", are removed.
 *             Removal of these path segments is performed iteratively,
 *             removing the leftmost matching pattern on each iteration,
 *             until no matching pattern remains.
 *
 *          d) If the path ends with "<segment>/..", where <segment> is a
 *             complete path segment not equal to "..", that
 *             "<segment>/.." is removed.
 *
 * This is a one pass state machine.  We copy each character as we go.
 * when we hit a sequence of characters we know should be removed,
 * we rewind the destination pointer and carry on.
 */
static void copy_clean_path(char *output, char *string, int left)
{
        int oind = 0;
        char *s = string;
        int start = 1;          /* if > 0, the current char is the start of a path component */
        int inmagic = 0;        /* 1 for ".", 2 for ".." */

        for(; left > 0 ; start--, s++) {
                if( *s == '.' ) {
                        /*
                         * alter our inmagic state depending on the
                         * makeup of the current path component 
                         */
                        if(start > 0) 
				/* we're the first char of a magic component */
                                inmagic++;
                        else 
				/* ok, we're the second '.' in ".." */
                                if(inmagic) inmagic++;

                        if(inmagic == 3) inmagic=0;
                } else 
                if( *s == '\0' || *s == '/' ) {
                        /*
                         * ok, we've finished a path component.  if it was magical, we have
                         * to undo it in the destination string
                         */
			if (start > 0) {
				/* we just saw a '/', drop it on the floor */
				oind--;
			}
                        start = 2; /* next char will start a path component */
                        if(inmagic) {
                                oind -= inmagic; /* skip back over "." or ".." */
                                if(inmagic == 2 && oind > 1) {
                                        /*
                                         * alrighty, we're a ".." and there was a previous path
                                         * component.  Lets nuke it.  Notice that the "> 1"s here
                                         * mean "/../" will turn into "/", this is vague in the
                                         * spec so I'm going with it.
                                         */
                                        oind-=2; /* skip to before the seperating "/" */
                                        for(;;) {
                                                 /* walk back through the previous component */
                                                if(oind && (output[oind-1] == '/')) break;
                                                if(!oind) break;
                                                oind--;
                                        }
                                }
                                inmagic = 0; /* ok, we're not magical anymore  */
                                if(*s == '/') continue; /* the trailing '/' is always ignored */
                        }
                } else inmagic=0; /* we're a normal char, no more magic */

                if( (output[oind++] = *(s)) == '\0') break;
                left--;
        }
        output[oind] = '\0'; /* paranoia */
}


int
dir_in_list(GSList * list, char * path)
{
  GSList * clist;

  if (!list)
    return 0;

  for (clist = list; clist; clist = clist->next)
    if (!strncmp(((only_dir_t *)clist->data)->dir, path,
		 ((only_dir_t *)clist->data)->len))
      return 1;

  return 0;
} /* dir_in_list */


int
parse_wuftpd_logfile(pointers_t * pointers)
{
  static ftp_entry_t * ftp_line, * current_ftp_ptr = NULL;
  static char foo[2048], tmp_host[2048], tmp_path[2048], user_type, in_out,
    * logfile;
  static int len;
  static GSList * clist = NULL;
  static FILE * log_stream = NULL;
  int lines = 0;

#ifdef DEBUGS
  fprintf(stderr, "Beginning parse_wuftpd_logfile...(%ld)\n",
	  (long) time(NULL));
#endif

  if (!clist) /* in theory this is only true once */
    clist = pointers->config->logfiles;

  if (pointers->first_ftp_line) {
	  for (ftp_line = pointers->first_ftp_line; ftp_line; ) {
		  current_ftp_ptr = ftp_line->next_ptr;
		  g_free(ftp_line->host);
		  g_free(ftp_line->path);
		  g_free(ftp_line);
		  ftp_line = current_ftp_ptr;
	  }
	  pointers->first_ftp_line = NULL;
  }

  for (; clist; clist = clist->next) {
	  logfile = clist->data;

    if (!pointers->config->use_stdin && !log_stream)
      if (!(log_stream = fopen(logfile, "r"))) {
	perror("parse_wuftpd_logfile: fopen");
	exit(1);
      }
    
    while (1) {
      if (pointers->config->use_stdin) {
	if (fgets(foo, sizeof(foo), stdin) == NULL)
	  break;
      } else {
	if (fgets(foo, sizeof(foo), log_stream) == NULL)
	  break;
      }
      
      if ((len = strlen(foo)) < 42)
	continue; /* data is too small to be valid */
#ifdef DEBUG3
      fprintf(stderr, "RAW READ: %s\n", foo);
#endif
      ftp_line = G_MEM_CHUNK_ALLOC(pointers->ftpentry_chunk);
      
      ftp_line->date[24] = '\0';
      memcpy(ftp_line->date, foo, 24);
      ftp_line->seconds = -1;
      ftp_line->data = 0;
      ftp_line->next_ptr = NULL;
      if ((foo[len - 2] == 'c' || foo[len - 2] == 'i') &&
	  foo[len - 3] == ' ')
	/* this check isn't foolproof, but it's probably good enough for
	 * now.  if it succeeds, this xferlog has a complete/incomplete
	 * byte at the end of the line */
	sscanf(foo + 25, "%ld %s %lu %s %*c %*s %c %c %*s %*s %*d %*s %c",
	       &ftp_line->seconds, tmp_host, &ftp_line->data, tmp_path,
	       &in_out, &user_type, &ftp_line->complete);
      else {
	sscanf(foo + 25, "%ld %s %lu %s %*c %*s %c %c",
	       &ftp_line->seconds, tmp_host, &ftp_line->data, tmp_path,
	       &in_out, &user_type);
	ftp_line->complete = 'c';
      }
      
      if (ftp_line->date[0] == '\0' || tmp_host[0] == '\0' ||
	  tmp_path[0] == '\0' || ftp_line->data == 0 ||
	  ftp_line->seconds < 0 ||
	  (user_type == 'a' && !pointers->config->anon_traffic) ||
	  (user_type == 'g' && !pointers->config->guest_traffic) ||
	  (user_type == 'r' && !pointers->config->real_traffic) ||
	  (in_out == 'i' && !pointers->config->inbound) ||
	  (in_out == 'o' && !pointers->config->outbound) ||
	  (pointers->config->only_dir &&
	   !dir_in_list(pointers->config->only_dir, tmp_path))) {
#ifdef DEBUG3
	if (ftp_line->date[0] == '\0')
	  fprintf(stderr, "Discarding -- no date.\n");
	else if (tmp_host[0] == '\0')
	  fprintf(stderr, "Discarding -- no host.\n");
	else if (tmp_path[0] == '\0')
	  fprintf(stderr, "Discarding -- no path.\n");
	else if (ftp_line->data == 0)
	  fprintf(stderr, "Discarding -- no data.\n");
	else if (ftp_line->seconds < 0)
	  fprintf(stderr, "Discarding -- no seconds.\n");
	else if (user_type == 'a' && !pointers->config->anon_traffic)
	  fprintf(stderr, "Discarding -- don't want anon traffic\n");
	else if (user_type == 'r' && !pointers->config->real_traffic)
	  fprintf(stderr, "Discarding -- don't want real traffic\n");
	else if (user_type == 'g' && !pointers->config->guest_traffic)
	  fprintf(stderr, "Discarding -- don't want guest traffic\n");
	else if (in_out == 'i' && !pointers->config->inbound)
	  fprintf(stderr, "Discarding -- don't want inbound traffic\n");
	else if (in_out == 'o' && !pointers->config->outbound)
	  fprintf(stderr, "Discarding -- don't want outbound traffic\n");
	else if (pointers->config->only_dir &&
		 !dir_in_list(pointers->config->only_dir, tmp_path))
	  fprintf(stderr, "Discarding -- don't want this directory\n");
	else
	  fprintf(stderr, "Discarding -- no clue why.\n");
#endif
#ifndef SMFS
	G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#endif
	continue; /* the line is invalid */
      }
      
      if (pointers->config->strict_check)
	/* These checks can significantly slow processing down, so they're
	 * highly optional. */
	if (ftp_line->date[3] != ' ' || ftp_line->date[7] != ' ' ||
	    ftp_line->date[10] != ' ' || ftp_line->date[19] != ' ' ||
	    ftp_line->date[13] != ':' || ftp_line->date[16] != ':' ||
	    foo[24] != ' ' || !isdigit(ftp_line->date[9]) ||
	    !isdigit(ftp_line->date[11]) || !isdigit(ftp_line->date[12]) ||
	    !isdigit(ftp_line->date[14]) || !isdigit(ftp_line->date[15]) ||
	    !isdigit(ftp_line->date[17]) || !isdigit(ftp_line->date[18]) ||
	    !isdigit(ftp_line->date[20]) || !isdigit(ftp_line->date[21]) ||
	    !isdigit(ftp_line->date[22]) || !isdigit(ftp_line->date[23]) ||
	    !(ftp_line->date[8] == ' ' || ftp_line->date[8] == '1' ||
	      ftp_line->date[8] == '2' || ftp_line->date[8] == '3')) {
#  ifdef DEBUGS
	  fprintf(stderr, "*** Corrupt line discarded.\n");
#  endif
#  ifndef SMFS
	  G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
	  continue; /* the line is invalid */
	}
      
      if ((pointers->config->begin_date[0] &&
	   compare_dates(ftp_line->date,
			 pointers->config->begin_date) == -1) ||
	  (pointers->config->end_date[0] &&
	   compare_dates(ftp_line->date,
			 pointers->config->end_date) == 1)) {
#ifdef DEBUG3
	fprintf(stderr, "Discarding -- not in date range.\n");
#endif
#  ifndef SMFS
	G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
	continue; /* the line is invalid */
      }
      
      if (!ftp_line->seconds)
	ftp_line->seconds = 1;
      
      ftp_line->host = g_malloc((len = strlen(tmp_host)) > MAXHOSTNAMELEN ?
				(len = MAXHOSTNAMELEN + 1) : ++len);
      memcpy(ftp_line->host, tmp_host, len - 1);
      ftp_line->host[len - 1] = '\0';

      if (pointers->config->strip_prefix_len &&
	  !strncmp(tmp_path, pointers->config->strip_prefix,
		   pointers->config->strip_prefix_len)) {
	ftp_line->path = g_malloc((len = strlen(tmp_path) -
				   pointers->config->strip_prefix_len) >
				  MAXPATHLEN ?
				  (len = MAXPATHLEN + 1) : ++len);
	memcpy(ftp_line->path,
	       tmp_path + pointers->config->strip_prefix_len, len - 1);
      } else {
	ftp_line->path = g_malloc((len = strlen(tmp_path)) > MAXPATHLEN ?
				  (len = MAXPATHLEN + 1) : ++len);
	memcpy(ftp_line->path, tmp_path, len - 1);
      }
      ftp_line->path[len - 1] = '\0';
      copy_clean_path(tmp_path, ftp_line->path, len);
      /* tmp_path can only be shorter, never longer, after copy_clean_path */
      memcpy(ftp_line->path, tmp_path, strlen(tmp_path) + 1);
      
      if (!pointers->first_ftp_line) {
	pointers->first_ftp_line = current_ftp_ptr = ftp_line;
#ifdef DEBUG3
	fprintf(stderr, "*** First ftp_line structure created (%p)\n",
		ftp_line);
#endif
      } else {
	current_ftp_ptr->next_ptr = ftp_line;
	current_ftp_ptr = ftp_line;
#ifdef DEBUG3
	fprintf(stderr, "*** New ftp_line structure added (%p)\n",
		ftp_line);
#endif
      }
#ifdef DEBUG3
      fprintf(stderr, "%s %ld %s %lu %s %c %c\n", ftp_line->date,
	      ftp_line->seconds, ftp_line->host, ftp_line->data,
	      ftp_line->path, in_out, user_type);
#endif
      lines++;
      
      if (pointers->config->chunk_input &&
	  lines >= pointers->config->chunk_input)
	return lines;
    }
    
    if (!pointers->config->use_stdin) {
      fclose(log_stream);
      log_stream = NULL;
    }
  }

#ifdef DEBUGS
  fprintf(stderr, "parse_wuftpd_logfile finished. (%ld)\n", (long) time(NULL));
#endif

  return 0;
} /* parse_wuftpd_logfile */


int
parse_ncftpd_logfile(pointers_t * pointers)
{
  ftp_entry_t * ftp_line, * current_ftp_ptr = NULL;
  char foo[2048], * tmp_char, tmp_host[2048], tmp_path[2048], tmp_date[4],
    user_name[2048], tmp_str[2048], * logfile;
  int temp, len;
  int lines = 0;
  GSList * clist;
#ifdef HAVE_MMAP
  struct stat stat_buf;
  char * map = NULL, * map_curr = NULL, * a, * b;
  int file_fd = -1, size;
#else
  FILE * log_stream = NULL;
#endif

#ifdef DEBUGS
  fprintf(stderr, "Beginning parse_ncftpd_logfile...\n");
#endif

  for (clist = pointers->config->logfiles; clist; clist = clist->next)
    {
      logfile = clist->data;

#ifdef HAVE_MMAP
      if (!pointers->config->use_stdin)
	{
	  if ((file_fd = open(logfile, O_RDONLY)) < 0)
	    {
	      printf("xferstats: fatal: file \"%s\" not found.\n", logfile);
	      exit(1);
	    }
	  stat(logfile, &stat_buf);
#  ifdef _AIX
	  /* Only AIX, it seems, wants this mmap typecast to int... */
	  if ((int)(map_curr = map =
		    (char *)mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED,
				 file_fd, 0)) < 0)
	    {
	      perror("mmap");
	      exit(1);
	    }
#  else
	  if ((map_curr = map =
	       (char *)mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED,
			    file_fd, 0)) < 0)
	    {
	      perror("mmap");
	      exit(1);
	    }
#  endif /* _AIX */
	}
#else /* HAVE_MMAP */
      if (!pointers->config->use_stdin)
	if (!(log_stream = fopen(logfile, "r")))
	  {
	    perror("parse_wuftpd_logfile: fopen");
	    exit(1);
	  }
#endif /* HAVE_MMAP */

      while (1)
	{
#ifdef HAVE_MMAP
	  if (pointers->config->use_stdin)
	    {
	      if (fgets(foo, sizeof(foo), stdin) == NULL)
		break;
	    }
	  else
	    {
	      if (map_curr - map >= stat_buf.st_size)
		{
		  munmap(map, stat_buf.st_size);
		  close(file_fd);
		  break;
		}

	      a = memchr(map_curr, '\n', (size = stat_buf.st_size - (map_curr -
								     map)));
	      if (a)
		b = memchr(map_curr, '\0', a - map_curr);
	      else
		b = memchr(map_curr, '\0', size);
	      if (a && b)
		{
		  if (a < b)
		    len = a - map_curr;
		  else
		    len = b - map_curr;
		}
	      else if (a)
		len = a - map_curr;
	      else if (b)
		len = b - map_curr;
	      else
		len = size;

	      memcpy(foo, map_curr,
		     len > sizeof(foo) - 1 ? (len = sizeof(foo) - 1) : len);
	      foo[len] = '\0';	
	      map_curr += len + 1;
	    }
#else /* HAVE_MMAP */
	  if (pointers->config->use_stdin)
	    {
	      if (fgets(foo, sizeof(foo), stdin) == NULL)
		break;
	    }
	  else
	    {
	      if (fgets(foo, sizeof(foo), log_stream) == NULL)
		break;
	    }
#endif /* HAVE_MMAP */

	  if (strlen(foo) < 54) continue; /* data is too small to be valid */
#ifdef DEBUG2
	  fprintf(stderr, "RAW READ: %s", foo);
#endif
	  /* this is a fairly ugly quick hack, but it seems to work.  I
	     don't plan on replacing this parsing code util a) ncftpd
	     logfiles change or b) someone sends me better code :) */

	  if ((foo[30] == 'R' || foo[30] == 'S') && foo[31] == ',')
	    {
	      ftp_line = G_MEM_CHUNK_ALLOC(pointers->ftpentry_chunk);
	
	      ftp_line->seconds = -1;
	      ftp_line->next_ptr = NULL;
	  
	      /* This is the new (maybe better, maybe not) way of doing it,
		 with individual byte setting */
	      memcpy(ftp_line->date, "    ", 4);
	      memcpy(tmp_date, foo + 5, 2);
	      tmp_date[2] = '\0';
	      temp = atoi(tmp_date);
	      memcpy(ftp_line->date + 4, MONTHS[temp - 1], 3);
	      ftp_line->date[7] = ' ';
	      memcpy(ftp_line->date + 8, foo + 8, 2);
	      ftp_line->date[10] = ' ';
	      memcpy(ftp_line->date + 11, foo + 11, 8);
	      ftp_line->date[19] = ' ';
	      ftp_line->date[20] = *foo;
	      memcpy(ftp_line->date + 21, foo + 1, 3);
	      continue;

	  if (pointers->config->strict_check)
	    /* These checks can significantly slow processing down, so they're
	     * highly optional. */
	    if (ftp_line->date[13] != ':' || ftp_line->date[16] != ':' ||
		foo[24] != ' ' || !isdigit(ftp_line->date[9]) ||
		!isdigit(ftp_line->date[11]) || !isdigit(ftp_line->date[12]) ||
		!isdigit(ftp_line->date[14]) || !isdigit(ftp_line->date[15]) ||
		!isdigit(ftp_line->date[17]) || !isdigit(ftp_line->date[18]) ||
		!isdigit(ftp_line->date[20]) || !isdigit(ftp_line->date[21]) ||
		!isdigit(ftp_line->date[22]) || !isdigit(ftp_line->date[23]) ||
		!(ftp_line->date[8] == ' ' || ftp_line->date[8] == '1' ||
		  ftp_line->date[8] == '2' || ftp_line->date[8] == '3'))
	      {
#  ifdef DEBUGS
		fprintf(stderr, "*** Corrupt line discarded.\n");
#  endif
#  ifndef SMFS
		G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
		continue; /* the line is invalid */
	      }

	  tmp_char = foo + 32;
	  while ((tmp_char = strchr(tmp_char, ',')) != NULL)
	    *tmp_char = ' ';
	      
	  /* there's an extra %s in the next line to catch the decimal
	   * portion of the 'seconds' */
	  sscanf(foo + 32, "%s %lu %ld %s %s %s %s %s", tmp_path,
		 &ftp_line->data, &ftp_line->seconds, tmp_str, tmp_str,
		 user_name, tmp_str, tmp_host);

	  if ((foo[30] == 'R' && !pointers->config->outbound) ||
	      (foo[30] != 'R' && !pointers->config->inbound) ||
	      !strcmp(ftp_line->date, "") || ftp_line->seconds < 0 ||
	      (pointers->config->only_dir &&
	       !dir_in_list(pointers->config->only_dir, tmp_path)))
	    {
#ifndef SMFS
	      G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#endif
	      continue; /* we don't want this log line */
	    }
	  if (!strcmp(user_name, "anonymous"))
	    {
	      if (!pointers->config->anon_traffic)
		{
#ifndef SMFS
		  G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#endif
		  continue; /* we don't want anon traffic */
		}
	    }
	  else
	    {
	      if (!pointers->config->real_traffic)
		{
#ifndef SMFS
		  G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#endif
		  continue; /* we don't want anon traffic */
		}
	      /* for a real user, the log line (after comma parsing) looks
	       * like:
	       * /foo 12345 5.123 30.069 pschwan  127.0.0.1  OK
	       * which means that the hostname is in tmp_str instead of
	       * tmp_host so we must use it instead */
	      memcpy(tmp_host, tmp_str, MAXHOSTNAMELEN);
	    }
	      
	  if ((pointers->config->begin_date[0] &&
	       compare_dates(ftp_line->date,
			     pointers->config->begin_date) == -1) ||
	      (pointers->config->end_date[0] &&
	       compare_dates(ftp_line->date,
			     pointers->config->end_date) == 1))
	    {
#ifdef DEBUG3
	      fprintf(stderr, "Discarding -- not in date range.\n");
#endif
#  ifndef SMFS
	      G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
	      continue; /* the line is invalid */
	    }

	  if (!ftp_line->seconds)
	    ftp_line->seconds = 1;

	  ftp_line->host =
	    g_malloc((len = strlen(tmp_host)) > MAXHOSTNAMELEN ?
		     (len = MAXHOSTNAMELEN + 1) : ++len);
	  memcpy(ftp_line->host, tmp_host, len - 1);
	  ftp_line->host[len - 1] = '\0';
	  
	  if (!strncmp(tmp_path, pointers->config->strip_prefix,
		       pointers->config->strip_prefix_len))
	    {
	      ftp_line->path =
		g_malloc((len = strlen(tmp_path) -
			  pointers->config->strip_prefix_len)
			 > MAXPATHLEN ? (len = MAXPATHLEN + 1) : ++len);
	      memcpy(ftp_line->path,
		     tmp_path + pointers->config->strip_prefix_len, len - 1);
	    }
	  else
	    {
	      ftp_line->path = g_malloc((len = strlen(tmp_path)) >
					MAXPATHLEN ? (len = MAXPATHLEN + 1)
					: ++len);
	      memcpy(ftp_line->path, tmp_path, len - 1);
	    }
	  ftp_line->path[len - 1] = '\0';
	  
	  if (!pointers->first_ftp_line)
	    {
	      pointers->first_ftp_line = current_ftp_ptr = ftp_line;
#ifdef DEBUG
	      fprintf(stderr, "*** First ftp_line structure created (%p)\n"
		      ftp_line);
#endif
	    }
	  else
	    {
	      while (current_ftp_ptr->next_ptr != NULL)		
		current_ftp_ptr = current_ftp_ptr->next_ptr;
	      current_ftp_ptr->next_ptr = ftp_line;
#ifdef DEBUG
	      fprintf(stderr, "*** New ftp_line structure added (%p)\n",
		      ftp_line);
#endif
	    }
#ifdef DEBUG2
	  fprintf(stderr, "%s %ld %s %lu %s\n", ftp_line->date,
		  ftp_line->seconds, ftp_line->host, ftp_line->data,
		  ftp_line->path);
#endif
	    }
      lines++;
      
      if (pointers->config->chunk_input &&
	  lines >= pointers->config->chunk_input)
	return lines;
	}

#ifndef HAVE_MMAP
      if (!pointers->config->use_stdin)
	fclose(log_stream);
#endif /* HAVE_MMAP */
    }

#ifdef DEBUGS
  fprintf(stderr, "parse_ncftpd_logfile finished.\n");
#endif

  return 0;
} /* parse_ncftpd_logfile */


int
parse_apache_logfile(pointers_t * pointers)
{
  ftp_entry_t * ftp_line, * current_ftp_ptr = NULL;
  char foo[2048], tmp_host[2048], tmp_path[2048], tmp_user[2048], * logfile;
  int len;
  int lines = 0;
  GSList * clist;
#ifdef HAVE_MMAP
  struct stat stat_buf;
  int file_fd = -1, size;
  char * map = NULL, * map_curr = NULL, * a, * b;
#else /* HAVE_MMAP */
  FILE * log_stream = NULL;
#endif /* HAVE_MMAP */

#ifdef DEBUGS
  fprintf(stderr, "Beginning parse_apache_logfile...(%ld)\n",
	  (long) time(NULL));
#endif

  for (clist = pointers->config->logfiles; clist; clist = clist->next)
    {
      logfile = clist->data;

#ifdef HAVE_MMAP
      if (!pointers->config->use_stdin)
	{
	  if ((file_fd = open(logfile, O_RDONLY)) < 0)
	    {
	      printf("xferstats: fatal: file \"%s\" not found.\n", logfile);
	      exit(1);
	    }
	  stat(logfile, &stat_buf);
#  ifdef _AIX
	  /* Only AIX, it seems, wants this mmap typecast to int... */
	  if ((int)(map_curr = map =
		    (char *)mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED,
				 file_fd, 0)) < 0)
	    {
	      perror("mmap");
	      exit(1);
	    }
#  else
	  /* Linux doesn't care, and Solaris segfaults if it's an int.  God
	   * how I love Linux... */
	  if ((map_curr = map =
	       (char *)mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED,
			    file_fd, 0)) < 0)
	    {
	      perror("mmap");
	      exit(1);
	    }
#  endif /* _AIX */
	}
#else /* HAVE_MMAP */
      if (!pointers->config->use_stdin)
	if (!(log_stream = fopen(logfile, "r")))
	  {
	    perror("parse_wuftpd_logfile: fopen");
	    exit(1);
	  }
#endif /* HAVE_MMAP */

      while (1)
	{
#ifdef HAVE_MMAP
	  if (pointers->config->use_stdin)
	    {
	      if (fgets(foo, sizeof(foo), stdin) == NULL)
		break;
	    }
	  else
	    {
	      if (map_curr - map >= stat_buf.st_size)
		{
		  munmap(map, stat_buf.st_size);
		  close(file_fd);
		  break;
		}
	      
	      a = memchr(map_curr, '\n', (size = stat_buf.st_size - (map_curr -
								     map)));
	      if (a)
		b = memchr(map_curr, '\0', a - map_curr);
	      else
		b = memchr(map_curr, '\0', size);
	      if (a && b)
		{
		  if (a < b)
		    len = a - map_curr;
		  else
		    len = b - map_curr;
		}
	      else if (a)
		len = a - map_curr;
	      else if (b)
		len = b - map_curr;
	      else
		len = size;
	      
	      memcpy(foo, map_curr,
		     len > sizeof(foo) - 1 ? (len = sizeof(foo) - 1) : len);
	      foo[len] = '\0';	
	      map_curr += len + 1;
	    }
#else /* HAVE_MMAP */
	  if (pointers->config->use_stdin)
	    {
	      if (fgets(foo, sizeof(foo), stdin) == NULL)
		break;
	    }
	  else
	    {
	      if (fgets(foo, sizeof(foo), log_stream) == NULL)
		break;
	    }
#endif /* HAVE_MMAP */

	  if (strlen(foo) < 34) continue; /* data is too small to be valid */
#ifdef DEBUG3
	  fprintf(stderr, "RAW READ: %s", foo);
#endif
	  ftp_line = G_MEM_CHUNK_ALLOC(pointers->ftpentry_chunk);

	  ftp_line->date[24] = '\0';
	  strncpy(ftp_line->date, foo, 24);
	  
	  ftp_line->seconds = -1;
	  ftp_line->next_ptr = NULL;
	  
	  sscanf(foo + 25, "%ld %s %lu %s %s", &ftp_line->seconds,
		 tmp_host, &ftp_line->data, tmp_path, tmp_user);
	  
	  if (ftp_line->date[0] == '\0' || tmp_host[0] == '\0' ||
	      tmp_path[0] == '\0' || ftp_line->data == 0 ||
	      ftp_line->seconds < 0 ||
	      (!strcmp("-", tmp_user) && !pointers->config->anon_traffic) ||
	      (strcmp("-", tmp_user) && !pointers->config->real_traffic) ||
	      !pointers->config->outbound ||
	      (pointers->config->only_dir &&
	       !dir_in_list(pointers->config->only_dir, tmp_path)))
	    {
#ifndef SMFS
	      G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#endif
	      continue; /* the line is invalid */
	    }
	  
	  if (pointers->config->strict_check)
	    /* These checks can significantly slow processing down, so they're
	     * highly optional. */
	    if (ftp_line->date[3] != ' ' || ftp_line->date[7] != ' ' ||
		ftp_line->date[10] != ' ' || ftp_line->date[19] != ' ' ||
		ftp_line->date[13] != ':' || ftp_line->date[16] != ':' ||
		foo[24] != ' ' || !isdigit(ftp_line->date[9]) ||
		!isdigit(ftp_line->date[11]) || !isdigit(ftp_line->date[12]) ||
		!isdigit(ftp_line->date[14]) || !isdigit(ftp_line->date[15]) ||
		!isdigit(ftp_line->date[17]) || !isdigit(ftp_line->date[18]) ||
		!isdigit(ftp_line->date[20]) || !isdigit(ftp_line->date[21]) ||
		!isdigit(ftp_line->date[22]) || !isdigit(ftp_line->date[23]) ||
		!(ftp_line->date[8] == ' ' || ftp_line->date[8] == '1' ||
		  ftp_line->date[8] == '2' || ftp_line->date[8] == '3'))
	      {
#  ifdef DEBUGS
		fprintf(stderr, "*** Corrupt line discarded.\n");
#  endif
#  ifndef SMFS
		G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
		continue; /* the line is invalid */
	      }

	  if ((pointers->config->begin_date[0] &&
	       compare_dates(ftp_line->date,
			     pointers->config->begin_date) == -1) ||
	      (pointers->config->end_date[0] &&
	       compare_dates(ftp_line->date,
			     pointers->config->end_date) == 1))
	    {
#ifdef DEBUG3
	      fprintf(stderr, "Discarding -- not in date range.\n");
#endif
#  ifndef SMFS
	      G_MEM_CHUNK_FREE(pointers->ftpentry_chunk, ftp_line);
#  endif
	      continue; /* the line is invalid */
	    }

	  if (!ftp_line->seconds)
	    ftp_line->seconds = 1;
	  
	  ftp_line->host = g_malloc((len = strlen(tmp_host)) > MAXHOSTNAMELEN ?
				    (len = MAXHOSTNAMELEN + 1) : ++len);
	  strncpy(ftp_line->host, tmp_host, len - 1);
	  ftp_line->host[len - 1] = '\0';
	  
	  if (!strncmp(tmp_path, pointers->config->strip_prefix,
		       pointers->config->strip_prefix_len))
	    {
	      ftp_line->path = g_malloc((len = strlen(tmp_path) -
					 pointers->config->strip_prefix_len) >
					MAXPATHLEN ?
					(len = MAXPATHLEN + 1) : ++len);
	      memcpy(ftp_line->path,
		     tmp_path + pointers->config->strip_prefix_len, len - 1);
	    }
	  else
	    {
	      ftp_line->path = g_malloc((len = strlen(tmp_path)) > MAXPATHLEN ?
					(len = MAXPATHLEN + 1) : ++len);
	      memcpy(ftp_line->path, tmp_path, len - 1);
	    }
	  ftp_line->path[len - 1] = '\0';
      
	  if (pointers->first_ftp_line == NULL)
	    {
	      pointers->first_ftp_line = current_ftp_ptr = ftp_line;
#ifdef DEBUG3
	      fprintf(stderr, "*** First ftp_line structure created (%p)\n",
		      ftp_line);
#endif
	    }
	  else
	    {
	      current_ftp_ptr->next_ptr = ftp_line;
	      current_ftp_ptr = current_ftp_ptr->next_ptr;
#ifdef DEBUG3
	      fprintf(stderr, "*** New ftp_line structure added (%p)\n",
		      ftp_line);
#endif
	    }
#ifdef DEBUG3
	  fprintf(stderr, "%s %ld %s %lu %s\n", ftp_line->date,
		  ftp_line->seconds, ftp_line->host, ftp_line->data,
		  ftp_line->path);
#endif
      lines++;
      
      if (pointers->config->chunk_input &&
	  lines >= pointers->config->chunk_input)
	return lines;
	}

#ifndef HAVE_MMAP
      if (!pointers->config->use_stdin)
	fclose(log_stream);
#endif /* HAVE_MMAP */
    }

#ifdef DEBUGS
  fprintf(stderr, "parse_apache_logfile finished. (%ld)\n", (long) time(NULL));
#endif

  return 0;
} /* parse_apache_logfile */


syntax highlighted by Code2HTML, v. 0.9.1