/*
* logfile.cpp
*
* (c) 1999-2002 Murat Deligonul
*
* 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 "autoconf.h"
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>
#include <ctype.h>
#include <dirent.h>
#include "debug.h"
#include "linkedlist.h"
#include "ezbounce.h"
#include "general.h"
#include "logfile.h"
#include "server.h"
static char * cheesy_encrypt(char *);
static char * cheesy_decrypt(char *);
list<char> logfile::active_logs;
/*
* open()'s a new logfile, ensuring that is unique and not locked by anyone
* else
*/
/* static */ int logfile::mkname(const char * basedir, const char * owner, int id,
const char * pw, const char * type, char * buff)
{
static const char * const extra[] = { "", "1.", "2.", "3.", "4.", "5.", "6." };
int c = 0, fd = -1;
while (1)
{
sprintf(buff, "%s/%d %s %d %s %s.%slog", basedir, getpid(), owner, id, pw, type, extra[c]);
if (is_locked(buff)
|| (fd = open(buff, O_CREAT | O_APPEND | O_WRONLY, 0600)) < 0)
{
if (fd > -1)
{
close(fd);
fd = -1;
}
if (++c > 6)
return -1;
continue;
} else if (fd > -1)
break;
}
return fd;
}
/*
* Updates result on leave. Can be:
* 1 = Ok
* 0 = Errors, but can go on
* -1 = Fatal
*/
logfile::logfile(const char * basedir, const char * owner, int id, int options, int maxsize, const char * password, int * result)
{
char filebuff[PATH_MAX + 1];
char * pw = (password ? my_strdup(password) : my_strdup("none"));
pw = cheesy_encrypt(pw);
log1 = log2 = NULL;
fd1 = fd2 = -1;
this->options = options;
this->maxsize = maxsize;
/* Create the log files now */
if (options & LOG_SEPERATE)
{
if (options & LOG_CHANNEL)
{
/* Assemble the file name */
fd1 = mkname(basedir, owner, id, pw, "(channel)", filebuff);
if (fd1 < 0)
*result = 0;
else
log1 = my_strdup(filebuff);
}
if (options & LOG_PRIVATE)
{
/* Assemble the file name */
fd2 = mkname(basedir, owner, id, pw, "(private)", filebuff);
if (fd2 < 0)
*result = 0;
else
log2 = my_strdup(filebuff);
}
if (fd1 < 0 && fd2 >= 0)
fd1 = fd2;
else if (fd2 < 0 && fd1 >= 0)
fd2 = fd1;
} else {
if ((options & LOG_CHANNEL) || (options & LOG_PRIVATE))
fd1 = mkname(basedir, owner, id, pw, "(all)", filebuff);
if (fd1 < 0)
{
*result = -1;
delete[] pw;
return;
}
log1 = my_strdup(filebuff);
}
delete[] pw;
if (fd2 < 0 && fd1 < 0)
{
*result = -1;
return;
}
struct stat st;
fstat(fd1, &st);
size1 = st.st_size;
fstat(fd2, &st);
size2 = st.st_size;
if ((maxsize) && (size1 > (unsigned) maxsize || size2 > (unsigned) maxsize))
{
*result = -1;
DEBUG("Log file already too big; aborting.\n");
close(fd1);
if (fd2 >= 0 && fd2 != fd1)
close(fd2);
fd1 = fd2 = -1;
return;
}
/* And I think we're ready after this */
if (log1)
lock(log1);
if (fd1 != fd2 && log2)
lock(log2);
fdprintf(fd1, "******************************************************\n"
"ezbounce log file started at %s\n"
"******************************************************\n",
timestamp());
if (fd2 != fd1)
fdprintf(fd2, "******************************************************\n"
"ezbounce log file started at %s\n"
"******************************************************\n",
timestamp());
*result = 1;
}
void logfile::stop(void)
{
if (fd1 > -1)
{
fdprintf(fd1, "******************************************************\n"
"ezbounce log file stopped at %s\n"
"******************************************************\n",
timestamp());
close(fd1);
}
if (fd2 != fd1)
{
fdprintf(fd2, "******************************************************\n"
"ezbounce log file stopped at %s\n"
"******************************************************\n",
timestamp());
close(fd2);
}
fd1 = fd2 = -1;
}
logfile::~logfile(void)
{
stop();
if (log1)
release(log1);
if (log2)
release(log2);
delete[] log1;
delete[] log2;
}
/*
* The interface to the log files -- we do the parsing
* elsewhere.. Arguments:
*
* flags - combination of options to specify the event
* source - who it's coming from (in a full ircaddr structure)
* target - who it's to
* text - chat text, or part & quit part messages
* this is not const; we need to be able to modify it
* so we can get rid of the color codes and stuff
* extra - for anything that don't fit; for example in KICK, who got kicked
* or in CTCPs, the CTCP itself, like CLIENTINFO or VERSION or whatever
*/
int logfile::write(int flags, const __ircaddr * source, const char * target, char * text, const char * extra)
{
int target_fd = fd1;
unsigned long * size = &size1;
int written = 0;
if (flags & EVENT_PRIVATE)
{
if (!(options & LOG_PRIVATE))
return 0;
if (!log2)
{
size = &size1;
target_fd = fd1;
} else {
size = &size2;
target_fd = fd2;
}
}
/* Enforce the size limits now */
if ((maxsize) && (*size > (unsigned) maxsize))
{
fdprintf(target_fd, "*** [NOTE] Logfile size limit of %d bytes exceeded; stopping log ***\n", maxsize);
fdprintf(target_fd, "******************************************************\n"
"ezbounce log file stopped at %s\n"
"******************************************************\n", timestamp());
close(target_fd);
if (target_fd == fd1)
fd1 = -1;
else if (target_fd == fd2)
fd2 = -1;
return 0;
}
else if (flags & EVENT_PUBLIC)
{
/* Check if we really need to log */
if (!(options & LOG_CHANNEL))
return 0;
}
if (text)
strip_burc_codes(text);
if (options & LOG_TIMESTAMP)
written += fdprintf(target_fd, "%s ", timestamp(1));
if (flags & EVENT_NOTICE)
written += fdprintf(target_fd, "-%s!%s@%s:%s- %s\n", source->nick,
source->ident, source->host, target, text);
else if (flags & EVENT_JOIN)
written += fdprintf(target_fd, "*** %s (%s@%s) has JOINed %s\n", source->nick,
source->ident, source->host, target);
else if (flags & EVENT_PART)
written += fdprintf(target_fd, "*** %s (%s@%s) has PARTed %s (%s)\n", source->nick,
source->ident, source->host, target, text);
else if (flags & EVENT_TOPIC)
written += fdprintf(target_fd, "*** %s changes %s TOPIC to: %s\n", source->nick, target, text);
else if (flags & EVENT_KICK)
written += fdprintf(target_fd, "*** %s (%s@%s) has KICKed %s from %s (%s)\n", source->nick, source->ident,
source->host, extra, target, text);
else if (flags & EVENT_MODE)
written += fdprintf(target_fd, "*** %s changes %s MODE to: %s\n", source->nick, target, text);
else if (flags & EVENT_QUIT)
written += fdprintf(target_fd, "*** %s (%s@%s) has QUIT (%s)\n", source->nick, source->ident,
source->host, text);
else if (flags & EVENT_NICK)
written += fdprintf(target_fd, "*** %s changes NICK to %s\n", source->nick, target);
else if (flags & EVENT_CTCP)
{
/* Remove ending 001 char */
if (text)
{
char * dummy = &text[strlen(text)] - 1;
if (*dummy == '\001')
*dummy = 0;
}
if (flags & EVENT_PUBLIC)
written += fdprintf(target_fd, "[%s] ", target);
if (strcmp(extra, "ACTION") == 0)
{
if ((options & LOG_FULL_ADDRS) || (flags & EVENT_PRIVATE))
written += fdprintf(target_fd, "* %s!%s@%s %s\n", source->nick, source->ident, source->host, extra);
else
written += fdprintf(target_fd, "* %s %s\n", source->nick, extra);
}
else
written += fdprintf(target_fd, "*** CTCP %s [%s] from %s (%s@%s) to %s\n", extra, text, source->nick,
source->ident, source->host, target);
} else {
if (flags & EVENT_PUBLIC)
written += fdprintf(target_fd, "[%s] ", target);
if ((options & LOG_FULL_ADDRS) || (flags & EVENT_PRIVATE))
written += fdprintf(target_fd, "<%s!%s@%s> %s\n", source->nick, source->ident, source->host, text);
else
written += fdprintf(target_fd, "<%s> %s\n", source->nick, text);
}
if (written >= 0)
*size += (unsigned long) written;
return written;
}
/*
* Convert character log control codes to logfile class
* flags. Valid codes:
* f - full user address in public channel messages
* p - private
* c - channel
* a - both channel and private
* n - none (causes immediate return
* s - log seperately
* t - timestamp all events
*/
/* static */ int logfile::charflags_to_int(const char * stuff)
{
int flags = 0;
for (unsigned x = 0; x < strlen(stuff); x++)
{
switch (tolower(stuff[x]))
{
case 'c':
flags |= LOG_CHANNEL;
break;
case 'p':
flags |= LOG_PRIVATE;
break;
case 'a':
flags |= LOG_ALL;
break;
case 'n':
return LOG_NONE;
case 's':
flags |= LOG_SEPERATE;
break;
case 't':
flags |= LOG_TIMESTAMP;
break;
case 'f':
flags |= LOG_FULL_ADDRS;
break;
}
}
return flags;
}
/*
* Not going to bother with checks.
* Buffer had better be able to hold 15 chars.
* Return size, not including the null char */
/* static */ int logfile::intflags_to_char(int flags, char * buff)
{
int p = 0;
if (flags & LOG_SEPERATE)
buff[p++] = 's';
if ((flags & LOG_ALL) == LOG_ALL)
buff[p++] = 'a';
if (flags & LOG_PRIVATE)
buff[p++] = 'p';
if (flags & LOG_CHANNEL)
buff[p++] = 'c';
if (flags & LOG_FULL_ADDRS)
buff[p++] = 'f';
if (flags & LOG_TIMESTAMP)
buff[p++] = 't';
if (flags == LOG_NONE)
{
p = 0;
buff[p++] = 'n';
}
buff[p] = 0;
return p;
}
/* Not going to bother checking for exceeding size limit. */
int logfile::dump(const char * raw)
{
int ret = -1;
if (fd1 > -1)
{
ret = fdprintf(fd1, "%s", raw);
size1 += ret;
}
if (fd2 != fd1)
size2 += fdprintf(fd2, "%s", raw);
return ret;
}
/*
* Fill an array with our file names.
* We will always fill both elements.
* f[0] will be all OR channel logs
* f[1] will be NULL OR private logs
*/
int logfile::get_filenames(char *f[2])
{
f[0] = my_strdup(log1);
f[1] = my_strdup(log2);
return 1;
}
/*
* Remove Bold, Underline, Reverse, and Color codes from
* the text.
*/
int logfile::strip_burc_codes(char * text)
{
static const char BOLD = 2,
COLOR = 3,
ULINE = 21,
REVERSE = 18;
/* Speed hack.. */
if (!strchr(text,BOLD) && !strchr(text, COLOR)
&& !strchr(text,ULINE) && !strchr(text, REVERSE))
return 1;
char * n = new char[strlen(text) + 1];
char * orig = text, *origN = n;
while (*text)
{
if (*text == BOLD || *text == COLOR
|| *text == ULINE || *text == REVERSE)
{
text++;
continue;
}
if (*text == COLOR)
{
/* From what i can tell.. there seems to be no clear
* standard as to how many digits the number after the
* ^C will be, and how many digits the number after the
* comma will be.. and I think the valid range of any
* color code number is 0-15.
*/
text++;
if (isdigit(*text))
text++;
if (*text <= '5' && *text >= '0' && *(text - 1) == '1')
text++;
if (*text == ',')
{
text++;
if (isdigit(*text))
text++;
if (*text <= '5' && *text >= '0' && *(text - 1) == '1')
text++;
}
continue;
}
*n++ = *text++;
}
*n = 0;
strcpy(orig, origN);
delete[] origN;
/* there's gotta be a better way.. */
return 1;
}
/* i don't know anything about encryption */
static char * cheesy_encrypt(char * text)
{
unsigned int i;
char c;
for (i = 0; i < strlen(text); i++)
{
c = text[i];
if (!(i % 2))
c = (9 ^ c);
else
c = c ^ (i % c);
text[i] = c;
}
return text;
}
static char * cheesy_decrypt(char * text)
{
unsigned int i;
char c;
for (i = 0; i < strlen(text); i++)
{
c = text[i];
if (!(i % 2))
c = (9 ^ c);
else
c = c ^ (i % c);
text[i] = c;
}
return text;
}
/*
* Finds log files matching requested properties in basedir.
* First checks for ones under this pid then everything else is fair game.
* Makes sure to not match log files that are currently being written
* to.
*
* Store up to two results in logfiles. Return number of results found.
*/
struct logfile_ent {
int uid;
pid_t pid;
char * filename;
char file_nick[NICKNAME_LENGTH];
char file_pw[PASSWORD_LENGTH];
};
/*
* Arguments:
* basedir - where to find logs
* nickname, password, id, self explanatatory
*/
/* static */ int logfile::find_log_files(const char * basedir, const char * username,
const char * password, int id, list<char> * plist, int max)
{
DIR *dir = opendir(basedir);
struct dirent *d = 0;
int num_found = 0;
static pid_t mypid = getpid();
assert(username);
if (!password)
password = "none";
if (!dir)
return -1;
list<struct logfile_ent> list;
struct logfile_ent * lt = 0;
errno = 0;
while ((d = readdir(dir)))
{
int args;
if (is_locked(d->d_name))
{
DEBUG("Log file %s found to be locked, skipping.\n", d->d_name);
continue;
}
lt = (lt ? lt : new struct logfile_ent);
/* First of all, check that the name matches the known pattern we use */
args = sscanf(d->d_name, "%d %" STR_NICKNAME_LENGTH "s %d %" STR_PASSWORD_LENGTH "s", <->pid, lt->file_nick, <->uid, lt->file_pw);
if (args < 4)
continue;
/*
* Check that the username matches
* that the passwords match (specify "none" if no pass!!)
* and that the id's match, if a valid id was provided
*/
if (!strcasecmp(username, lt->file_nick)
&& !strcasecmp(password, cheesy_decrypt(lt->file_pw))
&& (id < 0 || id == lt->uid))
{
num_found++;
lt->filename = new char[strlen(basedir) + strlen(d->d_name) + 5];
sprintf(lt->filename, "%s/%s", basedir, my_strdup(d->d_name));
list.add(lt);
lt = 0;
}
}
closedir(dir);
if (lt)
{
delete lt;
lt = 0;
}
if (num_found)
{
int cur = 0;
/* Go through the whole list, favor ones with current PID */
for (int i = 0; i < num_found && cur < max; i++)
{
lt = list.get(i);
if (lt->pid == mypid)
{
cur++;
plist->add(my_strdup(lt->filename));
}
}
/* Fill in other matches. */
for (int i = 0; i < num_found && cur < max; i++)
{
lt = list.get(i);
if (lt->pid != mypid)
{
cur++;
plist->add(my_strdup(lt->filename));
}
}
lt = 0;
/* We got the filenames now, so clean up the mess */
destroy_list(&list, 1);
return num_found;
}
return 0;
}
/* static */ int logfile::lock(const char * file)
{
DEBUG("logfile::lock() called on %s\n", file);
file = nopath(file);
if (!file || is_locked(file))
{
DEBUG("logfile::lock() on alreday locked file %s !!\n", file);
return 0;
}
active_logs.add(my_strdup(file));
return 1;
}
/* static */ int logfile::release(const char * file)
{
file = nopath(file);
DEBUG("logfile::release() called on %s\n", file);
return strlist_remove(&active_logs, file);
}
/* static */ int logfile::is_locked(const char * file)
{
list_iterator<char> i (&active_logs);
file = nopath(file);
DEBUG("Lock Check on file %s\n", file);
while (i.has_next())
if (strcmp(file, i.next()) == 0)
return 1;
return 0;
}
const char * logfile::fixup_logname(const char * filename)
{
static char * fixup_buff;
if (fixup_buff)
{
delete[] fixup_buff;
fixup_buff = 0;
}
filename = nopath(filename);
int len = strlen(filename) + 20;
fixup_buff = new char[len];
char nick[NICKNAME_LENGTH], id[10], suffix[20];
time_t blah = ircproxy_time();
struct tm * t = localtime(&blah);
gettok(filename, nick, sizeof(nick), ' ', 2);
gettok(filename, id, sizeof(id), ' ', 3);
gettok(filename, suffix, sizeof(suffix), ' ', 5);
snprintf(fixup_buff, len, "%d%02d%02d-%s-[%s]-%s", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, nick, id, suffix);
return fixup_buff;
}
syntax highlighted by Code2HTML, v. 0.9.1