/* * Copyright (C) 1999-2004 Joachim Wieland * * 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 #include #include #include #include #include #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 * 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; }