/*  
 * Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
 * 
 * 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.,
 * 59 Temple Place - Suite 330, Boston, MA 02111, USA.
 */

/*  ----- from tinyproxy for jftpgw
 *
 * Logs the various messages which tinyproxy produces to either a log file or
 * the syslog daemon. Not much to it...
 *
 * Copyright (C) 1998  Steven Young
 * Copyright (C) 1999  Robert James Kaes (rjkaes@flarenet.com)
 *
 * 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, 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.
 *
 * log.c - For the manipulation of log files.
 */

#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>

#include "jftpgw.h"
#include "log.h"

#define LENGTH 64
#define LOGSIZE 800

#ifndef HAVE_SNPRINTF
#   include "snprintf.c"
#else
#   ifndef HAVE_VSNPRINTF
#     include "snprintf.c"
#   endif
#endif

struct loginfo_st loginfo;
struct serverinfo srvinfo;
static struct loginfo_st* loginfo_bk;

/*
 * This routine logs messages to either the log file or the syslog function.
 */
void jlog(int level, const char *fmt, ...)
{
	va_list args;
	time_t nowtime;
	FILE *cf;

	static char time_string[LENGTH];
	static char str[LOGSIZE];

	if (level > loginfo.debuglevel) {
		return;
	}

	/* we may dump the log to stderr if we have not yet
	 * opened a log file or the syslog */
	/* but don't log if we are run by inetd */
	if (!loginfo.logf
		&& !loginfo.syslog
		&& srvinfo.servertype == SERVERTYPE_INETD
		&& level > 5) {

		return;
	}

	va_start(args, fmt);
	if (!loginfo.syslog) {
		/* log via files */
		nowtime = time(NULL);
		/* Format is month day hour:minute:second (24 time) */
		strftime(time_string, LENGTH, "%b %d %H:%M:%S", localtime(&nowtime));
		if (!(cf = loginfo.logf)) {
			cf = stderr;
		}

		fprintf(cf, "%s [%ld]: ", time_string, (long int) getpid());
		vfprintf(cf, fmt, args);
		fprintf(cf, "\n");
		fflush(cf);
	} else {
		int logtype = LOG_DEBUG;
		if (level < 8) {
			logtype = LOG_INFO;
		}
		if (level < 6) {
			logtype = LOG_WARNING;
		}
		if (level < 4) {
			logtype = LOG_ERR;
		}
		vsnprintf(str, LOGSIZE - 1, fmt, args);
		syslog(logtype, "%s", str);
	}

	va_end(args);
}




#define NUMBER_BUFFER   30
char* conv_int2char(signed int i) {
	char* s = (char*) malloc( NUMBER_BUFFER );
	enough_mem(s);
	snprintf(s, NUMBER_BUFFER, "%d", i);
	return s;
}

char* conv_uint2char(unsigned int i) {
	return conv_int2char(i);
}

char* conv_lint2char(signed long int i) {
	char* s = (char*) malloc( NUMBER_BUFFER );
	enough_mem(s);
	snprintf(s, NUMBER_BUFFER, "%ld", i);
	return s;
}

char* conv_luint2char(unsigned long int i) {
	return conv_lint2char(i);
}

char* conv_float2char(float f) {
	char* s = (char*) malloc( NUMBER_BUFFER );
	enough_mem(s);
	snprintf(s, NUMBER_BUFFER, "%.2f", f);
	return s;
}

const char* base_name(const char* s) {
	const char* r, *t;

	if (!s) {
		return "(null)";
	}
	t = r = s;
	while ((t = strchr(t, '/'))) {
		t++;
		r = t;
	}
	return r;
}


#define LOG_REPLACE_STRING       1
#define LOG_REPLACE_CHAR         2
#define LOG_REPLACE_UINT         3
#define LOG_REPLACE_LUINT        4
#define LOG_REPLACE_INT          5
#define LOG_REPLACE_LINT         6
#define LOG_REPLACE_FLOAT        7

char* log_replace_char(const char pattern, struct log_cmd_st* lcs) {
	union {
		char* replace_str;
		char replace_char;
		unsigned int replace_uint;
		unsigned long int replace_luint;
		signed int replace_int;
		signed long int replace_lint;
		float replace_float;
	} replace_val;
	int replace_type;

	switch (pattern) {
		case 'c': /* complete */
			replace_val.replace_char = lcs->complete ? 'c' : 'i';
			replace_type = LOG_REPLACE_CHAR;
			break;
		case 'D': /* common log time/date: [12/Feb/2003:13:34:50 +0100] */
			{
				time_t nowtime = time(NULL);
				replace_val.replace_str = malloc(100);
				enough_mem(replace_val.replace_str);
				/* XXX %z is a GNU extension */
				strftime(replace_val.replace_str, 100,
					"[%d/%b/%Y:%H:%M:%S %z]",
					localtime(&nowtime));
				replace_type = LOG_REPLACE_STRING;
			}
			break;
		case 'T': /* Time taken to transmit/receive file, in seconds */
			replace_val.replace_uint = lcs->transfer_duration;
			replace_type = LOG_REPLACE_UINT;
			break;
		case 't': /* date/time like Wed Feb 14 01:41:28 2001 */
			{
				time_t nowtime = time(NULL);
				replace_val.replace_str = malloc(100);
				enough_mem(replace_val.replace_str);
				strftime(replace_val.replace_str, 100,
					"%a %b %d %H:%M:%S %Y",
					localtime(&nowtime));
				replace_type = LOG_REPLACE_STRING;
			}
			break;
		case 'b': /* Bytes sent for request */
			replace_val.replace_luint = lcs->transferred;
			replace_type = LOG_REPLACE_LUINT;
			break;
		case 'R': /* throughput rate in kbyte/s */
			if (lcs->transfer_duration) {
				replace_val.replace_float =
				      (lcs->transferred / 1024) /
						lcs->transfer_duration;
				replace_type = LOG_REPLACE_FLOAT;
			} else {
				replace_val.replace_char = '-';
				replace_type = LOG_REPLACE_CHAR;
			}
			break;
		case 'f': /* Filename stored or retrieved, absolute path */
			replace_val.replace_str = strfilldup(lcs->filename, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'F':
			/* Filename stored or retrieved, as the client sees
			 * it base_name is just a pointer within lcs->filename
			 * */
			replace_val.replace_str = strfilldup(base_name(lcs->filename), "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'm': /* Command (method) name received from client,
			     e.g., RETR */
			replace_val.replace_str = strfilldup(lcs->method, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'r': /* full commandline */
			if (lcs && lcs->cmd && *(lcs->cmd) &&
					checkbegin(lcs->cmd, "PASS")) {
				replace_val.replace_str = strdup("PASS *");
			} else {
				replace_val.replace_str = strfilldup(lcs->cmd, "-");
			}
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'P': /* pid */
			replace_val.replace_uint = getpid();
			replace_type = LOG_REPLACE_UINT;
			break;
		case 's': /*  Numeric FTP response code (status) */
			replace_val.replace_int = lcs->respcode;
			replace_type = LOG_REPLACE_INT;
			break;
		case 'y': /* tYpe */
			replace_val.replace_char = lcs->type;
			replace_type = LOG_REPLACE_CHAR;
			break;
		case 'w':  /* direction */
			replace_val.replace_char = lcs->direction;
			replace_type = LOG_REPLACE_CHAR;
			break;
		case 'o':  /* anonymous? */
			/* logged in ? */
			if (!lcs->userlogin) {
				replace_val.replace_char = '-';
				replace_type = LOG_REPLACE_CHAR;
				break;
			}
			replace_val.replace_char =
				   strcmp(lcs->userlogin, "anonymous") == 0
				|| strcmp(lcs->userlogin, "ftp") == 0 ? 'a':'r';
			replace_type = LOG_REPLACE_CHAR;
			break;
		case 'e': /* sErvice */
			replace_val.replace_str = strfilldup(lcs->service, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'n': /* aNon-user */
			if (!lcs->userlogin
				|| strcmp(lcs->userlogin, "anonymous") == 0
				|| strcmp(lcs->userlogin, "ftp") == 0) {

				replace_val.replace_str
						= strfilldup(lcs->anon_user, "-");
				enough_mem(replace_val.replace_str);
			} else {
				/* Server user name (login) */
				replace_val.replace_str
						= strfilldup(lcs->userlogin, "-");
				enough_mem(replace_val.replace_str);
			}
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'H': /* Server host name */
			replace_val.replace_str = strfilldup(lcs->svrname, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'A': /* Server host IP */
			replace_val.replace_str = strfilldup(lcs->svrip, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'd': /*  Server host name as specified in the login  */
			replace_val.replace_str = strfilldup(lcs->svrlogin, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'h': /* Client host name */
			replace_val.replace_str = strfilldup(lcs->clntname, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'a': /* Client host IP */
			replace_val.replace_str = strfilldup(lcs->clntip, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'I': /* Server interface address */
			replace_val.replace_str = strfilldup(lcs->ifipsvr, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'i': /* Client interface address */
			replace_val.replace_str = strfilldup(lcs->ifipclnt, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'l': /* Server user name (login) */
			replace_val.replace_str = strfilldup(lcs->userlogin, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'L': /* Effective server user name */
			replace_val.replace_str = strfilldup(lcs->usereffective, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'C': /* Forwarded server user name */
			replace_val.replace_str = strfilldup(lcs->userforwarded, "-");
			enough_mem(replace_val.replace_str);
			replace_type = LOG_REPLACE_STRING;
			break;
		case 'u': /* unix time, seconds since 1970 */
			replace_val.replace_luint =
					(unsigned long int) time(NULL);
			replace_type = LOG_REPLACE_LUINT;
			break;
		case 'U': /* unix time, seconds since 1970 with milliseconds behind */
			{
				struct timeval tv;

				replace_val.replace_str = malloc(100);
				if (gettimeofday(&tv, NULL) < 0) {
					tv.tv_sec = time(NULL);
					tv.tv_usec = 0;
				}
				snprintf(replace_val.replace_str, 100,
						"%lu.%03lu",
						tv.tv_sec,
						(tv.tv_usec / 1000));
				replace_type = LOG_REPLACE_STRING;
			}
			break;
		case '%': /* percent sign */
			replace_val.replace_char = '%';
			replace_type = LOG_REPLACE_CHAR;
			break;
		default:
			return (char*) 0;
	}

	switch(replace_type) {
		char* s;

		case LOG_REPLACE_STRING:
			if (replace_val.replace_str) {
				s = replace_val.replace_str;
			} else {
				s = strdup("");
				enough_mem(s);
			}
			return s;
		case LOG_REPLACE_CHAR:
			s = (char*) malloc(2);
			enough_mem(s);
			s[0] = replace_val.replace_char;
			s[1] = '\0';
			return s;
		case LOG_REPLACE_INT:
			return conv_int2char(replace_val.replace_int);
		case LOG_REPLACE_UINT:
			return conv_uint2char(replace_val.replace_uint);
		case LOG_REPLACE_LINT:
			return conv_lint2char(replace_val.replace_lint);
		case LOG_REPLACE_LUINT:
			return conv_luint2char(replace_val.replace_luint);
		case LOG_REPLACE_FLOAT:
			return conv_float2char(replace_val.replace_float);
	}
	return (char*) 0;
}

char* log_replace_line(const char* line, struct log_cmd_st* lcs) {

	char* replaced_line = (char*) 0;
	char* fragment;
	char* insert;
	int offset = 0;

	/* split up line */

	/* line:  blabla %u blubb %h bla */

	if (line[0] == '%') {
		replaced_line = log_replace_char(line[1], lcs);
		offset = 2;
	}
	while ((fragment = quotstrtok(line, "%", &offset))) {
		if ( ! replaced_line ) {
			replaced_line = fragment;
		} else {
			replaced_line = realloc(replaced_line,
				strlen(replaced_line) + strlen(fragment) + 1);
			enough_mem(replaced_line);
			strcat(replaced_line, fragment);
		}
		/* the key is at line[offset+1]; */
		offset++;
		insert = log_replace_char(line[offset], lcs);
		if ( ! insert ) {
			insert = strdup("<%x not found>");
			enough_mem(insert);
			/* replace `x' above */
			insert[2] = line[offset];
		}
		offset++;
		replaced_line = realloc(replaced_line,
				strlen(replaced_line) + strlen(insert) + 1);
		enough_mem(replaced_line);
		strcat(replaced_line, insert);
		free(insert);
	}
	return replaced_line;
}



/* Idea by Bernd Eckenfels <ecki@lina.inka.de>
 * check of the command 'needle' is in the list of specified 
 * commands 'haystack'. Haystack may contain '*' which is a match-all
 * criteria */
int incommandpattern(const char *haystack, const char *needle) {
	char* negation = char_prepend(" ", needle);
	/* we now have "  NEEDLE " */
	negation[1] = '-';
	/* we now have " -NEEDLE " */

	if (strstr(haystack, negation)) {
		free(negation);
		return 0;
	}
	free(negation);

	if (strstr(haystack, " * ")) {
		return 1;
	}
	if (strstr(haystack, needle)) {
		return 1;
	}
	return 0;
}


void log_cmd_ent(struct cmdlogent_t* lent, struct log_cmd_st* lcs) {

	char* commandpattern;
	size_t commandpatternsize;
	char* ws;

	commandpatternsize = strlen(lcs->cmd) + 3;
	commandpattern = (char*) malloc(commandpatternsize);
	enough_mem(commandpattern);

	if ((ws = strpbrk(lcs->cmd, " \t")) == NULL) {
		/* Command consisting of a single word */
		snprintf(commandpattern, commandpatternsize, " %s ", lcs->cmd);
	} else {
		commandpattern[0] = ' ';
		strncpy(&commandpattern[1], lcs->cmd, ws - lcs->cmd);
		commandpattern[ws-lcs->cmd+1] = ' ';
		commandpattern[ws-lcs->cmd+2] = '\0';
	}
	toupstr(commandpattern);

	if (incommandpattern(lent->specs, commandpattern)) {
		/* found, log it */
		if (strcmp(lent->style, "commonlog") == 0) {
			const char* line_pattern =
				"%A %n %l %D \"%m\" %s %b";
			char* replaced = log_replace_line(line_pattern, lcs);
			fprintf(lent->logf, "%s\n", replaced);
			free(replaced);
		} else if (strcmp(lent->style, "xferlog") == 0) {
			const char* line_pattern =
				"%t %T %d %b \"%f\" %y _ %w %o %n %e 0 * %c";
			char* replaced = log_replace_line(line_pattern, lcs);
			fprintf(lent->logf, "%s\n", replaced);
			free(replaced);
		} else {
			char* line_pattern = lent->style;
			char* replaced;

			replaced = log_replace_line(line_pattern, lcs);
			fprintf(lent->logf, "%s\n", replaced);
			free(replaced);
		}
		fflush(lent->logf);
	}
	free(commandpattern);
}



void log_cmd(struct log_cmd_st* lcs) {

	/* go through the specifications of the logfiles and write
	 * them if they match */

	struct cmdlogent_t* files = loginfo.cmdlogfiles;
	struct cmdlogent_t* dirs  = loginfo.cmdlogdirs;

	while (files && files->logf_name) {
		log_cmd_ent(files, lcs);
		files = files->next;
	}

	while (dirs && dirs->logf_name) {
		log_cmd_ent(dirs, lcs);
		dirs = dirs->next;
	}
	lcs->filename = (char*) 0;
}

static
int log_name_exists(const struct cmdlogent_t* cl, const char* needle) {
	while (cl) {
		if (strcmp(cl->logf_name, needle) == 0) {
			return 1;
		}
		cl = cl->next;
	}
	return 0;
}

static
int log_init_debuglevel() {
	int debuglevel;
	const char* dbl;

	if (!(dbl = config_get_option("debuglevel"))) {
		jlog(4, "No debug level specified. Using level 7");
		debuglevel = 7;
	} else {
		errno = 0;
		/* with conv_char2* we wouldn't be able to log a warning */
		debuglevel = strtol(dbl, (char**) 0, 10);
		if (errno || debuglevel < 0 || debuglevel > 9) {
			jlog(4, "Invalid debuglevel specified: \"%s\". Using "
				"level 7", dbl);
			debuglevel = 7;
		}
	}
	return debuglevel;
}

static
int log_init_syslog(struct loginfo_st* cfg) {
	const char* log_facility_opt = config_get_option("syslogfacility");
	int log_facility;

	cfg->syslog_facility = strdup(log_facility_opt);
	enough_mem(cfg->syslog_facility);

	if (0) {
#ifdef HAVE_LOG_FACILITY_LOG_AUTH
	} else if (strcasecmp(log_facility_opt, "auth") == 0) {
		log_facility = LOG_AUTH;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_AUTHPRIV
	} else if (strcasecmp(log_facility_opt, "authpriv") == 0) {
		log_facility = LOG_AUTHPRIV;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_CRON
	} else if (strcasecmp(log_facility_opt, "cron") == 0) {
		log_facility = LOG_CRON;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_DAEMON
	} else if (strcasecmp(log_facility_opt, "daemon") == 0) {
		log_facility = LOG_DAEMON;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_FTP
	} else if (strcasecmp(log_facility_opt, "ftp") == 0) {
		log_facility = LOG_FTP;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_KERN
	} else if (strcasecmp(log_facility_opt, "kern") == 0) {
		log_facility = LOG_KERN;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL0
	} else if (strcasecmp(log_facility_opt, "local0") == 0) {
		log_facility = LOG_LOCAL0;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL1
	} else if (strcasecmp(log_facility_opt, "local1") == 0) {
		log_facility = LOG_LOCAL1;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL2
	} else if (strcasecmp(log_facility_opt, "local2") == 0) {
		log_facility = LOG_LOCAL2;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL3
	} else if (strcasecmp(log_facility_opt, "local3") == 0) {
		log_facility = LOG_LOCAL3;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL4
	} else if (strcasecmp(log_facility_opt, "local4") == 0) {
		log_facility = LOG_LOCAL4;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL5
	} else if (strcasecmp(log_facility_opt, "local5") == 0) {
		log_facility = LOG_LOCAL5;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL6
	} else if (strcasecmp(log_facility_opt, "local6") == 0) {
		log_facility = LOG_LOCAL6;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LOCAL7
	} else if (strcasecmp(log_facility_opt, "local7") == 0) {
		log_facility = LOG_LOCAL7;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_LPR
	} else if (strcasecmp(log_facility_opt, "lpr") == 0) {
		log_facility = LOG_LPR;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_MAIL
	} else if (strcasecmp(log_facility_opt, "mail") == 0) {
		log_facility = LOG_MAIL;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_NEWS
	} else if (strcasecmp(log_facility_opt, "news") == 0) {
		log_facility = LOG_NEWS;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_SYSLOG
	} else if (strcasecmp(log_facility_opt, "syslog") == 0) {
		log_facility = LOG_SYSLOG;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_USER
	} else if (strcasecmp(log_facility_opt, "user") == 0) {
		log_facility = LOG_USER;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_UUCP
	} else if (strcasecmp(log_facility_opt, "uucp") == 0) {
		log_facility = LOG_UUCP;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_CONSOLE
	} else if (strcasecmp(log_facility_opt, "console") == 0) {
		log_facility = LOG_CONSOLE;
#endif
#ifdef HAVE_LOG_FACILITY_LOG_SECURITY
	} else if (strcasecmp(log_facility_opt, "security") == 0) {
		log_facility = LOG_SECURITY;
#endif
	} else {
		/* facility not found */
		fprintf(stderr,
			"Facility %s not recognized, using default facility ",
			log_facility_opt);

		/* get default facility */
		if (srvinfo.multithread) {
			log_facility = LOG_DAEMON;
			fprintf(stderr, "LOG_DAEMON\n");
		} else {
			log_facility = LOG_USER;
			fprintf(stderr, "LOG_USER\n");
		}
	}
	openlog(srvinfo.binaryname, LOG_PID, log_facility);
	syslog(LOG_INFO, "jftpgw v"JFTPGW_VERSION" opened syslog");
	cfg->syslog = 1;
	return 0;
}

static
int log_init_logfile(struct loginfo_st* cfg) {
	const char* option;

	if (cfg->logf) {
		/* already logging */
		return 0;
	}
	cfg->syslog = 0;
	/* read the logfile name from the configuration file */
	option = config_get_option("logfile");
	if (!option) {
		/* no logfile set, use the default */
		option = DEFAULTLOGFILE;
	}

	/* now option is set in every case */
	cfg->logf_name = chrooted_path(option);

	if (!(cfg->logf = open_logfile(cfg->logf_name))) {
		return -1;
	}
	jlog(7, "jftpgw v"JFTPGW_VERSION" opened the logfile");
	return 0;
}


static
int log_init_cmdlog(struct loginfo_st* cfg, int open) {
	char* option, *specs, *style;
	struct cmdlogent_t* files = cfg->cmdlogfiles;
	struct cmdlogent_t* new;
	char* logfile;
	int i;

	struct slist_t* opt_names =config_get_option_array("cmdlogfile");
	struct slist_t* opt_styles=config_get_option_array("cmdlogfile-style");
	struct slist_t* opt_specs =config_get_option_array("cmdlogfile-specs");

	int count_names = slist_count(opt_names);
	int count_styles = slist_count(opt_styles);
	int count_specs = slist_count(opt_specs);

	if (!(count_names == count_styles && count_styles == count_specs)) {
		jlog(4, "Counted %d times \"cmdlogfile\" as option",
				count_names);
		jlog(4, "Counted %d times \"cmdlogfile-style\" as option",
				count_styles);
		jlog(4, "Counted %d times \"cmdlogfile-specs\" as option",
				count_specs);
		jlog(4, "This is not balanced, please fix your configuration");
		return -1;
	}

	/* reverse them */
	opt_names  = slist_reverse(opt_names);
	opt_styles = slist_reverse(opt_styles);
	opt_specs  = slist_reverse(opt_specs);

	for (i = 0; i < count_names; i++) {
		option = slist_pop(opt_names);
		specs  = slist_pop(opt_specs);
		style  = slist_pop(opt_styles);
		logfile = chrooted_path(option);
		if (log_name_exists(cfg->cmdlogfiles, logfile)
		    || log_name_exists(cfg->cmdlogfiles, option)) {

			free(logfile);
			free(option);
			free(specs);
			free(style);
			continue;
		}
		free(option);

		new = malloc(sizeof (struct cmdlogent_t));
		enough_mem(new);
		if (files) {
			/* there are existing entries */
			files->next = new;
		} else {
			/* this is the first entry */
			cfg->cmdlogfiles = new;
		}
		/* files always keeps the current entry and iterates on */
		files = new;
		files->logf_name = logfile;
		files->specs = char_enclose(" ", specs, " "); free(specs);
		files->style = style;
		files->next = (struct cmdlogent_t*) 0;
		if (open && (files->logf = open_logfile(files->logf_name))
								== NULL) {
			/* the malloc()ed memory will be freed by
			 * reset_loginfo() */
			if (i < count_names - 1) {
				/* don't free if we're already at the end */
				slist_destroy(opt_names);
				slist_destroy(opt_styles);
				slist_destroy(opt_specs);
			}
			return -1;
		}
	}
	/* The values are already free()ed, just the structures are still
	 * malloc()ed */
	free(opt_names);
	free(opt_styles);
	free(opt_specs);
	return 0;
}


#define FILENAME_EXTEND		64
static
FILE* log_init_dirlog_open(const char* fname_pattern) {
	time_t nowtime;
	char* filename, *fname;
	size_t size_max;
	FILE* file;

	size_max = strlen(fname_pattern) + FILENAME_EXTEND;
	fname = (char*) malloc(size_max);
	filename = (char*) malloc(size_max);
	enough_mem(fname);
	enough_mem(filename);

	nowtime = time(NULL);
	strftime(fname, size_max, fname_pattern, localtime(&nowtime));
	snprintf(filename, size_max, fname, getpid());
	free(fname); fname = (char*) 0;

	file = open_logfile(filename);
	free(filename);
	return file;
}

static
int log_init_dirlog(struct loginfo_st* cfg, int open) {
	char* option, *specs, *style;
	char* prefix, *suffix;
	char* logdir, *logfile, *logf_name, *logf_name_chroot;
	struct cmdlogent_t* dirs = cfg->cmdlogdirs;
	struct cmdlogent_t* new;
	int i;

	struct slist_t* opt_names
		= config_get_option_array("connectionlogdir");
	struct slist_t* opt_styles
		= config_get_option_array("connectionlogdir-style");
	struct slist_t* opt_specs
		= config_get_option_array("connectionlogdir-specs");
	struct slist_t* opt_prefix
		= config_get_option_array("connectionlogdir-fileprefix");
	struct slist_t* opt_suffix
		= config_get_option_array("connectionlogdir-filesuffix");

	int count_names = slist_count(opt_names);
	int count_styles = slist_count(opt_styles);
	int count_specs = slist_count(opt_specs);
	int count_prefix = slist_count(opt_prefix);
	int count_suffix = slist_count(opt_suffix);

	if (! (count_names == count_styles
			&& count_styles == count_specs
			&& count_specs == count_prefix
			&& count_prefix == count_suffix)) {

		jlog(4, "Counted %d times \"connectionlogdir\" as option",
				count_names);
		jlog(4, "Counted %d times \"connectionlogdir-style\" as option",
				count_styles);
		jlog(4, "Counted %d times \"connectionlogdir-specs\" as option",
				count_specs);
		jlog(4, "Counted %d times \"connectionlogdir-fileprefix\""
				" as option", count_prefix);
		jlog(4, "Counted %d times \"connectionlogdir-filesuffix\""
				" as option", count_suffix);
		jlog(4, "This is not balanced, please fix your configuration");
		return -1;
	}

	/* reverse them */
	opt_names  = slist_reverse(opt_names);
	opt_styles = slist_reverse(opt_styles);
	opt_specs  = slist_reverse(opt_specs);
	opt_prefix = slist_reverse(opt_prefix);
	opt_suffix = slist_reverse(opt_suffix);

	for (i = 0; i < count_names; i++) {
		option = slist_pop(opt_names);
		specs  = slist_pop(opt_specs);
		style  = slist_pop(opt_styles);
		prefix = slist_pop(opt_prefix);
		suffix = slist_pop(opt_suffix);

		logdir = chrooted_path(option);
		logfile = char_enclose(
				prefix, "%Y-%m-%d--%H:%M:%S-%%d", suffix);
		free(prefix); free(suffix); free(option);

		logf_name = char_enclose(logdir, "/", logfile);
		logf_name_chroot = chrooted_path(logf_name);
		free(logdir);    logdir    = (char*) 0;
		free(logfile);   logfile   = (char*) 0;

		if (log_name_exists(cfg->cmdlogdirs, logf_name)
		      || log_name_exists(cfg->cmdlogdirs, logf_name_chroot)) {

			free(logf_name);        logf_name        = (char*) 0;
			free(logf_name_chroot); logf_name_chroot = (char*) 0;
			free(style);
			free(specs);
			continue;
		}
		free(logf_name); logf_name = (char*) 0;

		new = malloc(sizeof (struct cmdlogent_t));
		enough_mem(new);
		if (dirs) {
			/* there are existing entries */
			dirs->next = new;
		} else {
			/* this is the first entry */
			cfg->cmdlogdirs = new;
		}
		/* dirs always keeps the current entry and iterates on */
		dirs = new;
		dirs->next = (struct cmdlogent_t*) 0;
		dirs->specs = char_enclose(" ", specs, " "); free(specs);
		dirs->logf_name = logf_name_chroot;
		dirs->style = style;

		if (open && (dirs->logf =
			log_init_dirlog_open(dirs->logf_name)) == NULL) {
			if (i < count_names - 1) {
				/* don't free if we're already at the end */
				slist_destroy(opt_names);
				slist_destroy(opt_styles);
				slist_destroy(opt_specs);
				slist_destroy(opt_prefix);
				slist_destroy(opt_suffix);
			}
			return -1;
		}
	}
	return 0;
}

int log_init() {
	loginfo.debuglevel = log_init_debuglevel();

	/* open the general logfile or syslog */
	if (config_compare_option("logstyle", "syslog")) {
		if (log_init_syslog(&loginfo) < 0) {
			return -1;
		}
	} else {
		if (log_init_logfile(&loginfo) < 0) {
			return -1;
		}
	}

	/* Open the command log logfile(s) */
	if (log_init_cmdlog(&loginfo, 1) < 0) {
		return -1;
	}

	/* Do the same with directories */
	if (log_init_dirlog(&loginfo, 1) < 0) {
		return -1;
	}

	return 0;
}


int log_detect_logfile_change() {
	/* syslog is now disabled, it was enabled previously, logging to
	 * files is now enabled */
	if (!config_compare_option("logstyle", "syslog")
						&& loginfo.syslog == 1) {
		return 1;
	}
	/* the name of the logfile has changed */
	if (!config_compare_option("logfile", loginfo.logf_name)) {
		return 1;
	}
	return 0;
}

int log_detect_syslog_change() {
	/* syslog is now enabled, it was not enabled previously */
	if (config_compare_option("logstyle", "syslog")
						&& loginfo.syslog == 0) {
		return 1;
	}
	/* syslog is enabled, but the facility has changed */
	if (!config_compare_option("syslogfacility", loginfo.syslog_facility)
						&& loginfo.syslog == 1){
		return 1;
	}
	return 0;
}

void log_cmdlogent_just_free(struct cmdlogent_t* cmd) {
	if (!cmd) {
		return;
	}
	log_cmdlogent_just_free(cmd->next);
	free(cmd->logf_name);
	free(cmd->style);
	free(cmd->specs);
	free(cmd);
}

struct cmdlogent_t* log_entry_in_cmdlogent(struct cmdlogent_t* cmd,
					   const char* logf_name,
					   const char* specs,
					   const char* style) {

	while(cmd) {
		if (strcmp(logf_name, cmd->logf_name) == 0
		 && strcmp(specs, cmd->specs) == 0
		 && strcmp(style, cmd->style) == 0) {

			return cmd;
		}
		cmd = cmd->next;
	}
	return 0;
}

int log_cmd_compare(struct cmdlogent_t** cmdn,
		    struct cmdlogent_t** cmdo,
		    FILE*(*f)(const char* s)) {

	struct cmdlogent_t* cmditer = *cmdn;
	struct cmdlogent_t* cmdpos;
	/* see, what is in cmdn, but not in cmdo. They are new, create them. */

	while (cmditer) {
		if (!(cmdpos = log_entry_in_cmdlogent(*cmdo,
			cmditer->logf_name, cmditer->specs, cmditer->style))) {
			/* in cmdn, but not in cmdo */
			cmditer->logf = (*f)(cmditer->logf_name);
		} else {
			/* since we have to replace cmdo by cmdn, we also
			 * have to copy the file handle even if the entry
			 * exists in the same way */
			cmditer->logf = cmdpos->logf;
			cmditer->logf_size = cmdpos->logf_size;
		}
		cmditer = cmditer->next;
	}

	/* see what is in cmdo, but not in cmdn. They are old, close them */
	cmditer = *cmdo;

	while(cmditer) {
		if (!(cmdpos = log_entry_in_cmdlogent(*cmdn,
			cmditer->logf_name, cmditer->specs, cmditer->style))) {
			fclose(cmditer->logf);
		} else {
			cmdpos->logf = cmditer->logf;
			cmdpos->logf_size = cmditer->logf_size;
		}
		cmditer = cmditer->next;
	}

	/* delete the old cmdlogent structure */
	log_cmdlogent_just_free(*cmdo);
	/* copy the new over the old */
	*cmdo = *cmdn;

	return 0;
}


int log_detect_cmdlog_change() {
	/* logf_name, specs and style may change */
	loginfo_bk = (struct loginfo_st*) malloc(sizeof(struct loginfo_st));
	enough_mem(loginfo_bk);

	memset(loginfo_bk, (int) 0, sizeof(struct loginfo_st));

	if (log_init_cmdlog(loginfo_bk, 0) < 0) {
		return -1;
	}
	if (log_init_dirlog(loginfo_bk, 0) < 0) {
		return -1;
	}

	/* check for new ones - first step
	 * a         a - found, ok
	 * b         c - found, ok
	 * c         d - found, ok
	 * d         e - not found, new
	 *           f - not found, new
	 *
	 *  check for old ones - second step
	 * a         a - found, ok
	 * c         b - not found, delete
	 * d         c - found, ok
	 * e         d - found, ok
	 * f
	 */

	log_cmd_compare(&loginfo_bk->cmdlogfiles, &loginfo.cmdlogfiles,
								open_logfile);
	log_cmd_compare(&loginfo_bk->cmdlogdirs, &loginfo.cmdlogdirs,
							log_init_dirlog_open);
	free(loginfo_bk);

	return 0;
}

int log_detect_log_change() {
	int newlevel = config_get_ioption("debuglevel", loginfo.debuglevel);
	if (newlevel != loginfo.debuglevel) {
		/* the debug level has changed, very simple to adjust  :-) */
		loginfo.debuglevel = log_init_debuglevel();
	}
	if (log_detect_logfile_change()) {
		/* close the old logfile */
		if (loginfo.logf) {
			fclose(loginfo.logf);
			loginfo.logf = (FILE*) 0;
		}
		if (loginfo.logf_name) {
			free(loginfo.logf_name);
			loginfo.logf_name = (char*) 0;
		}
		/* open a new logfile */
		log_init_logfile(&loginfo);
	}
	if (log_detect_syslog_change()) {
		/* close the syslog */
		closelog();
		/* and re-open it */
		log_init_syslog(&loginfo);
	}
	log_detect_cmdlog_change();
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1