/* dircproxy
 * Copyright (C) 2000,2001,2002,2003 Scott James Remnant <scott@netsplit.com>.
 * Copyright (C) 2004,2005,2006 Francois Harvey <fharvey at securiweb dot net>
 *
 * irc_log.c
 *  - Handling of log files
 *  - Handling of log programs
 *  - Recalling from log files
 * --
 * $Id: irc_log.c,v 1.49 2004/03/27 15:15:35 bear Exp $
 *
 * 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>
#endif /* HAVE_CONFIG_H */
#include "dircproxy.h"

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

#include <pwd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "sprintf.h"
#include "irc_net.h"

#include <fcntl.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

#include "net.h"
#include "irc_prot.h"
#include "irc_client.h"
#include "irc_string.h"

#include "irc_log.h"


/* Log time format for strftime(3) */
#define LOG_TIME_FORMAT "[%H:%M] "

/* User log time format */
#define LOG_USER_TIME_FORMAT "[%d %b %H:%M] "

/* Log time/date format for strftime(3) */
#define LOG_TIMEDATE_FORMAT "%a, %d %b %Y %H:%M:%S %z"

/* Define MIN() */
#ifndef MIN
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif /* !MIN */

/* Convenient code defines */
#define IS_SERVER_LOG(_p, _log)  ((_log) == &((_p)->server_log))
#define IS_PRIVATE_LOG(_p, _log) ((_log) == &((_p)->private_log))

// MacOSX dont add PACKAGE_NAME to config.h
#ifndef PACKAGE_NAME
# define PACKAGE_NAME "dircproxy"
#endif 
       

/* Key/Value pairs to hold a string event flag and associated #define value */
typedef struct _flag_info {
	char *name;
	int   value;
} FlagInfo;


/* Forward prototypes for internal functions */
static char *	_safe_name(char *);
static LogFile *_logfile_get(IRCProxy *, const char *);
static void	_logfile_close(LogFile *);
static FILE *	_open_user_log(IRCProxy *, const char *);
static char *	_log_read(FILE *);
static void	_log_printf(FILE *, const char *, ...);
static int	_logfile_write(LogFile *, const char *, ...);
static int	_log_pipe(IRCProxy *, int, const char *, const char *,
			  const char *);
static int	_logfile_writetext(IRCProxy *, LogFile *, int, const char *,
				   const char *, const char *);

static int _irclog_recall(struct ircproxy *, struct logfile *, unsigned long,
                          unsigned long, const char *, const char *);


/* The translation table between our #defines and string event types */
static FlagInfo flag_table[] = {
	{ "message",	IRC_LOG_MSG },
	{ "notice",	IRC_LOG_NOTICE },
	{ "action",	IRC_LOG_ACTION },
	{ "ctcp",	IRC_LOG_CTCP },
	{ "join",	IRC_LOG_JOIN },
	{ "part",	IRC_LOG_PART },
	{ "kick",	IRC_LOG_KICK },
	{ "quit",	IRC_LOG_QUIT },
	{ "nick",	IRC_LOG_NICK },
	{ "mode",	IRC_LOG_MODE },
	{ "topic",	IRC_LOG_TOPIC },
	{ "client",	IRC_LOG_CLIENT },
	{ "server",	IRC_LOG_SERVER },
	{ "error",	IRC_LOG_ERROR },
	{ NULL,		IRC_LOG_NONE }
};


/* irclog_maketempdir
 * Create the temporary directory in which we place internal log files.
 * The name of this is based on the username running dircproxy, the process
 * ID of this dircproxy and a static counter for each time we're called.
 * The temp directory can be changed with either the TMPDIR or TEMP environment
 * variables.
 */
int
irclog_maketempdir(IRCProxy *p)
{
	static unsigned int  counter = 0;
	struct passwd       *pw;
	struct stat	     statinfo;
	const char	    *tmpdir, *uname;

	/* Don't allow ourselves to be called twice on the same proxy */
	if (p->temp_logdir)
		return 0;

	/* Find a temporary directory */
	tmpdir = getenv("TMPDIR");
	if (tmpdir == NULL)
		tmpdir = getenv("TEMP");
	if (tmpdir == NULL)
		tmpdir = "/tmp";
	debug("Temp Directory = '%s'", tmpdir);

	/* Get our username */
	pw = getpwuid(geteuid());
	if (pw != NULL) {
		uname = pw->pw_name;
	} else {
		char uid[16];

		snprintf(uid, sizeof uid, "%d", geteuid());
		uname = uid;
	}
	debug("Username = '%s'", uname);

	/* Combine them all to make the log directory name */
	debug("Process ID = '%d'", getpid());
        p->temp_logdir = x_sprintf("%s/%s-%s-%d-%d", tmpdir, PACKAGE_NAME,
				   uname, getpid(), counter++);
	debug("Log temp directory = '%s'", p->temp_logdir);

	/* Make sure this is a safe directory to use */
	if (lstat(p->temp_logdir, &statinfo)) {
		if (errno != ENOENT) {
			syscall_fail("lstat", p->temp_logdir, 0);
			free(p->temp_logdir);
			p->temp_logdir = 0;
			return -1;

		} else if (mkdir(p->temp_logdir, 0700)) {
			syscall_fail("mkdir", p->temp_logdir, 0);
			free(p->temp_logdir);
			p->temp_logdir = 0;
			return -1;
		}

	} else if (!S_ISDIR(statinfo.st_mode)) {
		debug("Existed, but not directory");
		free(p->temp_logdir);
		p->temp_logdir = 0;
		return -1;
	}

	return 0;
}

/* _safe_name
 * Channel names are allowed to contain . and / according to the IRC
 * protocol.  These are nasty as it means someone could theoretically create
 * a channel called #/../../etc/passwd and the program would try to unlink
 * "/tmp/#/../../etc/passwd" = "/etc/passwd".  * If running as root this
 * could be bad.  So to compensate we replace '/' with ':' as thats not
 * valid in channel names.
 * We do the same for $ and \ (for Microsoft OS) 
 */
static char *
_safe_name(char *name)
{
	char *ptr;

	ptr = name;
	while (*ptr) {
		switch (*ptr) {
		 case '$':
		 case '\\':
		 case '/':
		   *ptr = ':';
		   break;
		}

		ptr++;
	}

	return name;
}

/* _logfile_get
 * Get the appropriate LogFile from an IRCProxy
 */
static LogFile *
_logfile_get(IRCProxy *p, const char *to)
{
	IRCChannel *c;

	if (to) {
		c = ircnet_fetchchannel(p, to);
		if (c) {
			return &(c->log);
		} else {
			return &(p->private_log);
		}
	} else {
		return &(p->server_log);
	}
}

/* irclog_init
 * Initialise a log file, this decides on, and allocates, a filename and is
 * usually called when creating the proxy/channel this log file is a member
 * of.  This does not open the file.
 */
int
irclog_init(IRCProxy *p, const char *to)
{
	char	*filename;
	LogFile	*log;

	if (!(log = _logfile_get(p, to)))
		return -1;

	if (!p->temp_logdir)
		return -1;

	/* Copy the config, this makes it fixed while a log file is open,
	 * which is probably the right thing to do.
	 */
	if (IS_SERVER_LOG(p, log)) {
		debug("Initialising server log file");
		filename = x_strdup("server");
		log->maxlines = p->conn_class->server_log_maxsize;
		log->always = p->conn_class->server_log_always;

	} else if (IS_PRIVATE_LOG(p, log)) {
		debug("Initialising private log file");
		filename = x_strdup("private");
		log->maxlines = p->conn_class->private_log_maxsize;
		log->always = p->conn_class->private_log_always;

	} else {
		debug("Initialising channel log file for %s", to);
		filename = x_strdup(to);
		irc_strlwr(_safe_name(filename));
		log->maxlines = p->conn_class->chan_log_maxsize;
		log->always = p->conn_class->chan_log_always;
	}

	/* Store the filename in the LogFile */
	if (log->filename)
		free(log->filename);
	log->filename = x_sprintf("%s/%s", p->temp_logdir, filename);
	debug("Log filename = '%s'", log->filename);
	log->made = 0;

	free(filename);
	return 0;
}

/* irclog_open
 * Open a previously initialised log file
 */
int
irclog_open(IRCProxy *p, const char *to)
{
	LogFile *log;

	log = _logfile_get(p, to);
	if (!log || !log->filename)
		return -1;
	if (log->open)
		return 0;

	/* Unlink first for security */
	if (unlink(log->filename) && (errno != ENOENT)) {
		syscall_fail("unlink", log->filename, 0);
		free(log->filename);
		log->filename = 0;
		return -1;
	}

	/* Open for reading and writing */
	log->file = fopen(log->filename, "w+");
	if (log->file == NULL) {
		syscall_fail("fopen", log->filename, 0);
		free(log->filename);
		log->filename = 0;
		return -1;
	}

	/* Try to remove world and group read/write */
	if (fchmod(fileno(log->file), 0600))
		syscall_fail("fchmod", log->filename, 0);
  
	log->open = log->made = 1;
	log->nlines = 0;
	return 0;
}

/* _logfile_close
 * Close the filehandle associated with a LogFile structure
 */
static void
_logfile_close(LogFile *log)
{
	if (!log->open)
		return;

	debug("Closing log file '%s'", log->filename);
	fclose(log->file);
	log->open = 0;
}

/* irclog_close
 * Close a log file, don't log anything more to it for now.  This doesn't
 * unlink the file or free the information, it just indicates that logging
 * has concluded.  The file and information remains so we can reopen it to
 * recall things.
 */
void
irclog_close(IRCProxy *p, const char *to)
{
	LogFile *log;

	if (!(log = _logfile_get(p, to)))
		return;
	_logfile_close(log);
}

/* irclog_free
 * Close a log file and free up all of it's information.  Once this is called
 * the log file will need to be reinitialised before it can be used, and
 * nothing will be able to be recalled.
 */
void
irclog_free(LogFile *log)
{
	if (!log->filename)
		return;

	/* Close it if necessary */
	if (log->open)
		_logfile_close(log);

	/* Unlink the file, and free up the space used by the filename */
	debug("Freeing up log file '%s'", log->filename);
	unlink(log->filename);
	free(log->filename);
	log->nlines = 0;
	log->made = 0;
}

/* irclog_closetempdir
 * Remove the temporary directory and free up the space in the IRCProxy
 * structure.  This should only be called once all log files have been closed.
 */
void
irclog_closetempdir(IRCProxy *p)
{
	if (!p->temp_logdir)
		return;

	debug("Freeing log temp directory '%s'", p->temp_logdir);
	rmdir(p->temp_logdir);
	free(p->temp_logdir);
	p->temp_logdir = 0;
}

/* _open_use_log
 * Open a file to which we append log messages in a human-readable format.
 * This file should be closed once you've finished with it, it'll be opened
 * again next time (to allow the user to wipe it while we're running).
 */
static FILE *
_open_user_log(IRCProxy *p, const char *to)
{
	struct stat  statinfo;
	char	    *filename, *userfile;
	FILE	    *log;

	if (!p->conn_class->log_dir)
		return NULL;

	/* Work out the filename, because we don't have a LogFile structure
	 * we simply accept whatever we're given.
	 */
	if (to == IRC_LOGFILE_SERVER) {
		filename = x_strdup("Server");
	
	} else {
		filename = x_strdup(to);
		irc_strlwr(_safe_name((char*)to));
	}

	/* The filename is under the user's log_dir */
	userfile = x_sprintf("%s/%s.log", p->conn_class->log_dir, filename);
	debug("User log file = '%s'", userfile);
	free(filename);

	/* Make sure it's safe to use */
	if (lstat(userfile, &statinfo)) {
		if (errno != ENOENT) {
			syscall_fail("lstat", userfile, 0);
			free(userfile);
			return NULL;
		}
	} else if (!S_ISREG(statinfo.st_mode)) {
		debug("File existed, but wasn't a file");
		free(userfile);
		return NULL;
	}

	/* Open the file for appending */
	if (!(log = fopen(userfile, "a")))
		syscall_fail("fopen", userfile, 0);
	free(userfile);

	return log;
}

/* Read a line from the log FIXME */
static char *_log_read(FILE *file) {
  char buff[512], *line;

  line = 0;
  while (1) {
    if (!fgets(buff, 512, file)) {
      free(line);
      return 0;
    } else if (!strlen(buff)) {
      free(line);
      return 0;
    } else {
      char *ptr;

      if (line) {
        char *new;

        new = x_sprintf("%s%s", line, buff);
        free(line);
        line = new;
      } else {
        line = x_strdup(buff);
      }

      ptr = line + strlen(line) - 1;
      if (*ptr == '\n') {
        while ((ptr >= line) && (!ptr || strchr(" \t\r\n", *ptr))) *(ptr--) = 0;
        break;
      }
    }
  }

  return line;
}

/* _log_printf
 * Seek to the end of the file, write a line then flush the file so it
 * appears immediately.
 */
static void
_log_printf(FILE *fd, const char *format, ...)
{
	va_list  ap;
	char	*msg;

	/* Slurp the arguments in like printf */
	va_start(ap, format);
	msg = x_vsprintf(format, ap);
	va_end(ap);

	/* Write the line at the end of the file, then flush */
	fseek(fd, 0, SEEK_END);
	fputs(msg, fd);
	fflush(fd);

	free(msg);
}

/* Write a line to the log FIXME don't just roll by line counts now? */
static int _logfile_write(struct logfile *log, const char *format, ...) {
  va_list ap;
  char *msg;

  va_start(ap, format);
  msg = x_vsprintf(format, ap);
  va_end(ap);

  if (log->open && log->maxlines && (log->nlines >= log->maxlines)) {
    FILE *fout;
    char *l;

    /* We can't simply add .tmp or something on the end, because there is
       always a possibility that might be a channel name.  Besides using
       temporary files always looks icky to me.  This "Sick Puppy" way of
       reading from an unlinked file sits with me much better (says a lot
       about me, that) */
    fseek(log->file, 0, SEEK_SET);
    unlink(log->filename);

    /* This *really* shouldn't happen */
    fout = fopen(log->filename, "w+");
    if (!fout) {
      syscall_fail("fopen", log->filename, 0);
      return -1;
    }

    /* Make sure it's got the right permissions */
    if (fchmod( fileno(fout), 0600))
      syscall_fail("fchmod", log->filename, 0);

    /* Eat from the start */
    while ((log->nlines >= log->maxlines) && (l = _log_read(log->file))) {
      free(l);
      log->nlines--;
    }

    /* Write the rest */
    while ((l = _log_read(log->file))) {
      fprintf(fout, "%s\n", l);
      free(l);
    }

    /* Close the input file, thereby *whoosh*ing it */
    fclose(log->file);
    log->file = fout;
  }

  /* Write to the log file */
  if (log->open) {
    _log_printf(log->file, "%s\n", msg);
    log->nlines++;
  }

  free(msg);
  return 0;
}

/* _log_pipe
 * Call a program with the log type, source and destination information as
 * arguments, providing the message to log on its standard input.
 */
static int
_log_pipe(IRCProxy *p, int event, const char *to, const char *from,
	  const char *text)
{
	int   pfd[2], pid;
	FILE *fd;

	if (!p->conn_class->log_program)
		return 1;

	/* Prepare a pipe */
	if (pipe(pfd)) {
		syscall_fail("pipe", 0, 0);
		return 1;
	}

	/* Do the fork() thing */
	switch (pid = fork()) {
	case -1:
		/* Failed :( */
		syscall_fail("fork", 0, 0);
		return -1;

	case 0:
		/* Child process, close the write end of the pipe */
		close(pfd[1]);

		/* Copy read end to STDIN */
		if (dup2(pfd[0], STDIN_FILENO) != STDIN_FILENO) {
			syscall_fail("dup2", 0, 0);
			close(pfd[0]);
			return 1;
		}
		close(pfd[0]);
		
		/* Run the log program with the appropriate arguments.
		 * Use current environment and search the PATH if necessary.
		 */
		execlp(p->conn_class->log_program, p->conn_class->log_program,
		       irclog_flagtostr(event), to, from, NULL);

		/* Uh-oh!  Where's the kaboom?
		 * There was supposed to be an earth-shattering kaboom! 
		 */
		syscall_fail("execlp", p->conn_class->log_program, 0);
		exit(10);

	default:
		/* Parent process, close the read end of the pipe */
		close(pfd[0]);

		/* Open the write end as a FILE * */
		if (!(fd = fdopen(pfd[1], "w"))) {
			syscall_fail("fdopen", 0, 0);
			close(pfd[1]);
			return -1;
		}

		/* Write the log message to the new FILE * and close */
		fprintf(fd, "%s\n", text);
		fflush(fd);
		fclose(fd);
	}

	return 0;
}

/* Write some text to a log file */
static int _logfile_writetext(struct ircproxy *p, struct logfile *log, int event, const char *to, const char *from, const char *text) {
  const char *dest;
  FILE *user_log;
  time_t now;

  if (to == IRC_LOGFILE_ALL) {
    return -1;
  } else if (to == IRC_LOGFILE_SERVER) {
    dest = "SERVER";
  } else {
    dest = to;
  }
 
  time(&now);
  if (p->conn_class->log_timeoffset)
    now -= (p->conn_class->log_timeoffset * 60);
  
  _logfile_write(log, "%lu %s %s %s %s",
                now, irclog_flagtostr(event), dest, from, text);

  /* Write to the user's copy */
  user_log = _open_user_log(p, to);
  if (user_log) {
    char tbuf[40];
    
    if (p->conn_class->log_timestamp) {
      strftime(tbuf, sizeof(tbuf), LOG_USER_TIME_FORMAT, localtime(&now));
    } else {
      tbuf[0] = '0';
    }

    /* Print a nicely formatted entry to the log file */
    if (event & IRC_LOG_MSG) {
      _log_printf(user_log, "%s<%s> %s\n", tbuf, from, text);
    } else if (event & IRC_LOG_NOTICE) {
      _log_printf(user_log, "%s-%s- %s\n", tbuf, from, text);
    } else if (event & IRC_LOG_ACTION) {
      char *nick, *ptr;

      nick = x_strdup(from);
      ptr = strchr(nick, '!');
      if (ptr)
        *ptr = 0;

      _log_printf(user_log, "%s* %s %s\n", tbuf, nick, text);
      free(nick);
    } else if (event & IRC_LOG_CTCP) {
      _log_printf(user_log, "%s[%s] %s\n", tbuf, from, text);
    } else if (event & IRC_LOG_JOIN) {
      _log_printf(user_log, "%s--> %s\n", tbuf, text);
    } else if (event & IRC_LOG_PART) {
      _log_printf(user_log, "%s<-- %s\n", tbuf, text);
    } else if (event & IRC_LOG_KICK) {
      _log_printf(user_log, "%s<-- %s\n", tbuf, text);
    } else if (event & IRC_LOG_QUIT) {
      _log_printf(user_log, "%s<-- %s\n", tbuf, text);
    } else if (event & IRC_LOG_NICK) {
      _log_printf(user_log, "%s--- %s\n", tbuf, text);
    } else if (event & IRC_LOG_MODE) {
      _log_printf(user_log, "%s--- %s\n", tbuf, text);
    } else if (event & IRC_LOG_TOPIC) {
      _log_printf(user_log, "%s--- %s\n", tbuf, text);
    } else if (event & IRC_LOG_CLIENT) {
      _log_printf(user_log, "%s*** %s\n", tbuf, text);
    } else if (event & IRC_LOG_SERVER) {
      _log_printf(user_log, "%s*** %s\n", tbuf, text);
    } else if (event & IRC_LOG_ERROR) {
      _log_printf(user_log, "%s*** %s\n", tbuf, text);
    }
      
    fclose(user_log);
  }

  /* Write to the pipe */
  _log_pipe(p, event, dest, from, text); 

  return 0;
}

/* Write a message to log file(s) */
int irclog_log(struct ircproxy *p, int event, const char *to, const char *from,
               const char *format, ...) {
  char *text;
  va_list ap;

  if (!(p->conn_class->log_events & event))
    return 0;

  va_start(ap, format);
  text = x_vsprintf(format, ap);

  if (to != IRC_LOGFILE_ALL) {
    struct logfile *log;
    
    /* Write to one file */
    log = _logfile_get(p, to);
    if (!log)
      return -1;

    _logfile_writetext(p, log, event, to, from, text);
  } else {
    struct ircchannel *c;

    /* Write to all files except the private one */
    _logfile_writetext(p, &(p->server_log), event, IRC_LOGFILE_SERVER,
                      from, text);
    c = p->channels;
    while (c) {
	    _logfile_writetext(p, &(c->log), event, c->name, from, text);
      c = c->next;
    }
  }

  free(text);
  va_end(ap);

  return 0;
}


  
/* Called to automatically recall stuff FIXME */
int irclog_autorecall(struct ircproxy *p, const char *to) {
  unsigned long recall, start, lines;
  struct logfile *log;

  log = _logfile_get(p, to);
  if (!log)
    return -1;

  if (log == &(p->server_log)) {
    recall = p->conn_class->server_log_recall;
  } else if (log == &(p->private_log)) {
    recall = p->conn_class->private_log_recall;
  } else {
    recall = p->conn_class->chan_log_recall;
  }

  /* Don't recall anything */
  if (!recall)
    return 0;
  
  /* Recall everything */
  if (recall == -1) {
    start = 0;
  } else {
    start = (recall > log->nlines ? 0 : log->nlines - recall);
  }
  lines = log->nlines - start;

  return _irclog_recall(p, log, start, lines, to, 0);
}

/* Called to manually recall stuff FIXME */
int irclog_recall(struct ircproxy *p, const char *to,
                  long start, long lines, const char *from) {
  struct logfile *log;

  log = _logfile_get(p, to);
  if (!log)
    return -1;

  /* Recall everything */
  if (lines == -1) {
    start = 0;
    lines = log->nlines;
  }

  /* Recall recent */
  if (start == -1)
    start = (lines > log->nlines ? 0 : log->nlines - lines);

  return _irclog_recall(p, log, start, lines, to, from);
}

/* Called to do the recall from a log file FIXME */
static int _irclog_recall(struct ircproxy *p, struct logfile *log,
                          unsigned long start, unsigned long lines,
                          const char *to, const char *from) {
  FILE *file;
  int close;

  /* If the file isn't open, we have to open it and remember to close it
     later */
  if (log->open) {
    file = log->file;
    close = 0;
  } else if (log->filename && log->made) {
    file = fopen(log->filename, "r");
    if (!file) {
      ircclient_send_notice(p, "Couldn't open log file %s", log->filename);
      syscall_fail("fopen", log->filename, 0);
      return -1;
    }
    close = 1;
  } else {
    return -1;
  }

  debug("recalling log [%s]\r\n", log->filename);

  /* If to is 0, then we're recalling from the server_log, and need to send
   * it to the nickname */
  if (!to)
    to = p->nickname ? p->nickname : "";

  /* Jump to the beginning */
  fseek(file, 0, SEEK_SET);

  if (start < log->nlines) {
    char *msg;

    /* Skip start lines */
    while (start && (msg = _log_read(file))) {
      free(msg);
      start--;
    }

    /* Make lines sensible */
    lines = MIN(lines, log->nlines - start);

    /* Recall lines */
    while (lines && (msg = _log_read(file))) {
      time_t when = 0;
      char *ll;
      int event;
      char *src, *frm, *eventtext;
      char *work, *rest;
      time_t now, diff;
      char tbuf[40];

      tbuf[0] = 0;
     
      debug("log: [%s]\r\n", msg);

       /*
	* 1079304950 client #dircproxy dircproxy You connected
	* 1079304951 join #dircproxy niven.freenode.net You joined the channel
	* 1079305132 client #dircproxy dircproxy You disconnected
	* 1079305143 message #dircproxy bear!~bear@pa.comcast.net lah
	* 1079305150 action #dircproxy bear!~bear@pa.comcast.net moons the channel
	*/

      ll   = msg;
      work = msg;
      if (!*work) {                             /* Timestamp is first value now */
        free(ll);
        continue;
      }
  
      rest = strchr(msg, ' ');                  /* parse to the space */
      if (!rest) {
        free(ll);
        continue;
      }

      *(rest++) = 0;                            /* delete the space */
      msg = rest;
  
      when = strtoul(work, (char **)NULL, 10);  /* Obtain the timestamp */

      eventtext = msg;                          /* store the event text */
      if (!*eventtext) {
        free(ll);
        continue;
      }
      
      rest = strchr(msg, ' ');                  /* Message continues after a space */
      if (!rest) {
        free(ll);
        continue;
      }
      
      *(rest++) = 0;                            /* Delete the space */
      msg = rest;

      event = irclog_strtoflag(eventtext);      /* determine the event code */
      src   = msg;                              /* log source */
      
      rest = strchr(msg, ' ');                  /* Message continues after a space */
      if (!rest) {
        free(ll);
        continue;
      }
  
      *(rest++) = 0;                            /* Delete the space */
      msg = rest;
  
      frm = msg;                                /* where the entry is from */
      
      rest = strchr(msg, ' ');                  /* Message continues after a space */
      if (!rest) {
        free(ll);
        continue;
      }
      
      *(rest++) = 0;                             /* Delete the space */
      msg = rest;
  
      debug("timestamp: %d event: %d src: [%s] frm: [%s] log: [%s]\r\n", when, event, src, frm, msg);

      /* If the log_timestamp option is on, format the timestamp */
      if (when && p->conn_class->log_timestamp) {
        if (p->conn_class->log_relativetime) {
          time(&now);
          diff = now - when;

          if (diff < 82800L) {                /* Within 23 hours [hh:mm] */
            strftime(tbuf, sizeof(tbuf), "[%H:%M] ", localtime(&when));
          } else if (diff < 518400L) {        /* Within 6 days [day hh:mm] */
            strftime(tbuf, sizeof(tbuf), "[%a %H:%M] ", localtime(&when));
          } else if (diff < 25920000L) {      /* Within 300 days [d mon] */
            strftime(tbuf, sizeof(tbuf), "[%d %b] ", localtime(&when));
          } else {                            /* Otherwise [d mon yyyy] */
            strftime(tbuf, sizeof(tbuf), "[%d %b %Y] ", localtime(&when));
          }
        } else {
          strftime(tbuf, sizeof(tbuf), LOG_TIME_FORMAT, localtime(&when));
        }
      }

      /* Message or Notice lines, these require a bit of parsing */
      if ((event == IRC_LOG_NOTICE) || (event == IRC_LOG_MSG) || (event == IRC_LOG_ACTION)) {
        /* Do filtering */
        if (from) {
          char *comp, *ptr;

          /* We just check the nickname, so strip off anything after the ! */
          comp = x_strdup(src);
          if ((ptr = strchr(comp, '!')))
            *ptr = 0;

            /* Check the nicknames are the same */
          if (irc_strcasecmp(comp, from)) {
            free(comp);
            free(ll);
            continue;
          }
          free(comp);
        }
      }
      
        /* Send the line */
      if (event == IRC_LOG_MSG) {
        net_send(p->client_sock, ":%s PRIVMSG %s :%s%s\r\n", frm, to, tbuf, msg);
      } else if (event == IRC_LOG_ACTION) {
        net_send(p->client_sock, ":%s PRIVMSG %s :\001ACTION %s%s\001\r\n", frm, to, tbuf, msg);
      } else if (event == IRC_LOG_CTCP) {
        net_send(p->client_sock, ":%s PRIVMSG %s :\001%s %s%s%s\001\r\n", src, to, eventtext, tbuf, (strlen(msg) ? " " : ""), msg);
      } else if (event == IRC_LOG_NOTICE) {
        ircclient_send_notice(p, "%s", msg);
      } else {
        net_send(p->client_sock, ":%s PRIVMSG %s :%s%s\r\n", src, to, tbuf, msg);
      }

      free(ll);
      lines--;
    }
  }

    /* Either close, or skip back to the end */
  if (close) {
    fclose(file);
  } else {
    fseek(file, 0, SEEK_END);
  }
  return 0;
}

/* irclog_strtoflag
 * Convert a textual flag name into the equivalent #define value
 */
int
irclog_strtoflag(const char *str)
{
	FlagInfo *fi;

	for (fi = flag_table; fi->name != NULL; fi++) {
		if (!strcasecmp(str, fi->name))
			return fi->value;
	}

	return IRC_LOG_NONE;
}

/* irclog_flagtostr
 * Convert a flag #define value into the equivalent textual name.  Returns the
 * empty string if the flag does not exist.
 */
const char *
irclog_flagtostr(int flag)
{
	FlagInfo *fi;

	for (fi = flag_table; fi->name != NULL; fi++) {
		if (fi->value == flag)
			return fi->name;
	}

	return "";
}


syntax highlighted by Code2HTML, v. 0.9.1