/*
* ProFTPD - FTP server daemon
* Copyright (c) 1997, 1998 Public Flood Software
* Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
* Copyright (c) 2001-2005 The ProFTPD Project team
*
* 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-1307, USA.
*
* As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
* and other respective copyright holders give permission to link this program
* with OpenSSL, and distribute the resulting executable, without including
* the source code for OpenSSL in the source distribution.
*/
/* Flexible logging module for proftpd
* $Id: mod_log.c,v 1.72 2005/10/18 23:27:31 castaglia Exp $
*/
#include "conf.h"
#include "privs.h"
extern pr_response_t *resp_list, *resp_err_list;
module log_module;
#define EXTENDED_LOG_BUFFER_SIZE 1025
#define EXTENDED_LOG_MODE 0644
typedef struct logformat_struc logformat_t;
typedef struct logfile_struc logfile_t;
struct logformat_struc {
logformat_t *next,*prev;
char *lf_nickname;
unsigned char *lf_format;
};
struct logfile_struc {
logfile_t *next,*prev;
char *lf_filename;
int lf_fd;
int lf_syslog_level;
logformat_t *lf_format;
int lf_classes;
/* Pointer to the "owning" configuration */
config_rec *lf_conf;
};
/* Value for lf_fd signalling that data should be logged via syslog, rather
* than written to a file.
*/
#define EXTENDED_LOG_SYSLOG -4
#define META_START 0xff
#define META_ARG_END 0xfe
#define META_ARG 1
#define META_BYTES_SENT 2
#define META_FILENAME 3
#define META_ENV_VAR 4
#define META_REMOTE_HOST 5
#define META_REMOTE_IP 6
#define META_IDENT_USER 7
#define META_PID 8
#define META_TIME 9
#define META_SECONDS 10
#define META_COMMAND 11
#define META_LOCAL_NAME 12
#define META_LOCAL_PORT 13
#define META_LOCAL_IP 14
#define META_LOCAL_FQDN 15
#define META_USER 16
#define META_ORIGINAL_USER 17
#define META_RESPONSE_CODE 18
#define META_CLASS 19
#define META_ANON_PASS 20
#define META_METHOD 21
#define META_XFER_PATH 22
#define META_DIR_NAME 23
#define META_DIR_PATH 24
#define META_CMD_PARAMS 25
static pool *log_pool;
static logformat_t *formats = NULL;
static xaset_t *format_set = NULL;
static logfile_t *logs = NULL;
static xaset_t *log_set = NULL;
/* format string args:
%A - Anonymous username (password given)
%a - Remote client IP address
%b - Bytes sent for request
%c - Class
%D - full directory path
%d - directory (for client)
%{FOOBAR}e - Contents of environment variable FOOBAR
%F - Transfer path (filename for client)
%f - Filename
%h - Remote client DNS name
%J - Request (command) arguments (file.txt, etc)
%L - Local server IP address
%l - Remote logname (from identd)
%m - Request (command) method (RETR, etc)
%P - Process ID of child serving request
%p - Port of server serving request
%r - Full request (command)
%s - Response code (status)
%T - Time taken to serve request, in seconds
%t - Time
%{format}t - Formatted time (strftime(3) format)
%U - Original username sent by client
%u - Local user
%V - DNS name of server serving request
%v - ServerName of server serving request
*/
static void add_meta(unsigned char **s, unsigned char meta, int args,
...) {
int arglen;
char *arg;
**s = META_START;
(*s) = (*s) + 1;
**s = meta;
(*s) = (*s) + 1;
if (args) {
va_list ap;
va_start(ap, args);
while (args--) {
arglen = va_arg(ap, int);
arg = va_arg(ap, char *);
memcpy(*s, arg, arglen);
(*s) = (*s) + arglen;
**s = META_ARG_END;
(*s) = (*s) + 1;
}
va_end(ap);
}
}
static
char *preparse_arg(char **s)
{
char *ret = (*s) + 1;
(*s) = (*s) + 1;
while (**s && **s != '}')
(*s) = (*s) + 1;
**s = 0;
(*s) = (*s) + 1;
return ret;
}
static
void logformat(char *nickname, char *fmts)
{
char *tmp, *arg;
unsigned char format[4096] = {'\0'}, *outs;
logformat_t *lf;
/* This function can cause potential problems. Custom logformats
* might overrun the format buffer. Fixing this problem involves a
* rewrite of most of this module. This will happen post 1.2.0.
*/
outs = format;
for (tmp = fmts; *tmp; ) {
if (*tmp == '%') {
arg = NULL;
tmp++;
for (;;) {
switch (*tmp) {
case '{':
arg = preparse_arg(&tmp);
continue;
case 'a':
add_meta(&outs, META_REMOTE_IP, 0);
break;
case 'A':
add_meta(&outs, META_ANON_PASS, 0);
break;
case 'b':
add_meta(&outs, META_BYTES_SENT, 0);
break;
case 'c':
add_meta(&outs, META_CLASS, 0);
break;
case 'D':
add_meta(&outs, META_DIR_PATH, 0);
break;
case 'd':
add_meta(&outs, META_DIR_NAME, 0);
break;
case 'e':
if (arg) {
add_meta(&outs, META_ENV_VAR, 0);
add_meta(&outs, META_ARG, 1, (int) strlen(arg), arg);
}
break;
case 'f':
add_meta(&outs, META_FILENAME, 0);
break;
case 'F':
add_meta(&outs, META_XFER_PATH, 0);
break;
case 'h':
add_meta(&outs, META_REMOTE_HOST, 0);
break;
case 'J':
add_meta(&outs, META_CMD_PARAMS, 0);
break;
case 'l':
add_meta(&outs, META_IDENT_USER, 0);
break;
case 'L':
add_meta(&outs, META_LOCAL_IP, 0);
break;
case 'm':
add_meta(&outs, META_METHOD, 0);
break;
case 'p':
add_meta(&outs, META_LOCAL_PORT, 0);
break;
case 'P':
add_meta(&outs, META_PID, 0);
break;
case 'r':
add_meta(&outs, META_COMMAND, 0);
break;
case 's':
add_meta(&outs, META_RESPONSE_CODE, 0);
break;
case 't':
add_meta(&outs, META_TIME, 0);
if (arg)
add_meta(&outs, META_ARG, 1, (int) strlen(arg), arg);
break;
case 'T':
add_meta(&outs, META_SECONDS, 0);
break;
case 'u':
add_meta(&outs, META_USER, 0);
break;
case 'U':
add_meta(&outs, META_ORIGINAL_USER, 0);
break;
case 'v':
add_meta(&outs, META_LOCAL_NAME, 0);
break;
case 'V':
add_meta(&outs, META_LOCAL_FQDN, 0);
break;
case '%':
*outs++ = '%';
break;
}
tmp++;
break;
}
} else {
*outs++ = *tmp++;
}
}
*outs++ = 0;
lf = (logformat_t *) pcalloc(log_pool, sizeof(logformat_t));
lf->lf_nickname = pstrdup(log_pool, nickname);
lf->lf_format = palloc(log_pool, outs - format);
memcpy(lf->lf_format, format, outs - format);
if (!format_set)
format_set = xaset_create(log_pool, NULL);
xaset_insert_end(format_set, (xasetmember_t *) lf);
formats = (logformat_t *) format_set->xas_list;
}
/* Syntax: LogFormat nickname "format string" */
MODRET set_logformat(cmd_rec *cmd) {
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT);
logformat(cmd->argv[1], cmd->argv[2]);
return HANDLED(cmd);
}
static int _parse_classes(char *s) {
int classes = 0;
char *nextp = NULL;
do {
if ((nextp = strchr(s, ',')))
*nextp++ = '\0';
if (!nextp) {
if ((nextp = strchr(s, '|')))
*nextp++ = '\0';
}
if (strcasecmp(s, "NONE") == 0) {
classes = CL_NONE;
break;
}
if (strcasecmp(s, "ALL") == 0) {
classes = CL_ALL;
break;
} else if (strcasecmp(s, "AUTH") == 0) {
classes |= CL_AUTH;
} else if (strcasecmp(s, "INFO") == 0) {
classes |= CL_INFO;
} else if (strcasecmp(s, "DIRS") == 0) {
classes |= CL_DIRS;
} else if (strcasecmp(s, "READ") == 0) {
classes |= CL_READ;
} else if (strcasecmp(s, "WRITE") == 0) {
classes |= CL_WRITE;
} else if (strcasecmp(s, "MISC") == 0) {
classes |= CL_MISC;
} else if (strcasecmp(s, "SEC") == 0 ||
strcasecmp(s, "SECURE") == 0) {
classes |= CL_SEC;
} else
pr_log_pri(PR_LOG_NOTICE, "ExtendedLog class '%s' is not defined.", s);
} while ((s = nextp));
return classes;
}
/* Syntax: ExtendedLog file [<cmd-classes> [<nickname>]] */
MODRET set_extendedlog(cmd_rec *cmd) {
config_rec *c = NULL;
int argc;
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
argc = cmd->argc;
if (argc < 2)
CONF_ERROR(cmd, "Syntax: ExtendedLog file [<cmd-classes> [<nickname>]]");
c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
if (strncasecmp(cmd->argv[1], "syslog:", 7) == 0) {
char *tmp = strchr(cmd->argv[1], ':');
if (pr_log_str2sysloglevel(++tmp) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown syslog level: '",
tmp, "'", NULL));
} else
c->argv[0] = pstrdup(log_pool, cmd->argv[1]);
} else if (cmd->argv[1][0] != '/') {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "relative paths not allowed: '",
cmd->argv[1], "'", NULL));
} else
c->argv[0] = pstrdup(log_pool, cmd->argv[1]);
if (argc > 2)
c->argv[1] = pstrdup(log_pool, cmd->argv[2]);
if (argc > 3)
c->argv[2] = pstrdup(log_pool, cmd->argv[3]);
c->argc = argc-1;
return HANDLED(cmd);
}
/* Syntax: AllowLogSymlinks <on|off> */
MODRET set_allowlogsymlinks(cmd_rec *cmd) {
int bool = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if ((bool = get_boolean(cmd, 1)) == -1)
CONF_ERROR(cmd, "expected Boolean parameter");
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
*((unsigned char *) c->argv[0]) = bool;
return HANDLED(cmd);
}
/* Syntax: ServerLog <filename> */
MODRET set_serverlog(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return HANDLED(cmd);
}
/* Syntax: SystemLog <filename> */
MODRET set_systemlog(cmd_rec *cmd) {
char *syslogfn = NULL;
int res;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT);
log_closesyslog();
syslogfn = cmd->argv[1];
if (strcasecmp(syslogfn, "NONE") == 0) {
log_discard();
return HANDLED(cmd);
}
if (*syslogfn != '/')
syslogfn = dir_canonical_path(cmd->tmp_pool,syslogfn);
pr_signals_block();
res = log_opensyslog(syslogfn);
if (res < 0) {
int xerrno = errno;
pr_signals_unblock();
if (res == PR_LOG_WRITABLE_DIR) {
CONF_ERROR(cmd,
"you are attempting to log to a world writeable directory");
} else if (res == PR_LOG_SYMLINK) {
CONF_ERROR(cmd, "you are attempting to log to a symbolic link");
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
"unable to redirect logging to '", syslogfn, "': ",
strerror(xerrno), NULL));
}
}
pr_signals_unblock();
return HANDLED(cmd);
}
#ifdef HAVE_GMTOFF
static
struct tm *_get_gmtoff(int *tz)
{
time_t tt = time(NULL);
struct tm *t;
t = localtime(&tt);
*tz = (int)(t->tm_gmtoff / 60)
return t;
}
#else
static
struct tm *_get_gmtoff(int *tz)
{
time_t tt = time(NULL);
struct tm gmt;
struct tm *t;
int days,hours,minutes;
gmt = *gmtime(&tt);
t = localtime(&tt);
days = t->tm_yday - gmt.tm_yday;
hours = ((days < -1 ? 24 : 1 < days ? -24 : days * 24)
+ t->tm_hour - gmt.tm_hour);
minutes = hours * 60 + t->tm_min - gmt.tm_min;
*tz = minutes;
return t;
}
#endif /* HAVE_GMTOFF */
static char *get_next_meta(pool *p, cmd_rec *cmd, unsigned char **f) {
unsigned char *m;
char arg[512] = {'\0'}, *argp = NULL, *pass;
/* This function can cause potential problems. Custom logformats
* might overrun the arg buffer. Fixing this problem involves a
* rewrite of most of this module. This will happen post 1.2.0.
*/
m = (*f) + 1;
switch (*m) {
case META_ARG:
m++; argp = arg;
while (*m != META_ARG_END)
*argp++ = (char)*m++;
*argp = 0; argp = arg;
m++;
break;
case META_ANON_PASS:
argp = arg;
pass = get_param_ptr(cmd->server->conf, C_PASS, FALSE);
if (!pass)
pass = "UNKNOWN";
sstrncpy(argp, pass, sizeof(arg));
m++;
break;
case META_BYTES_SENT:
argp = arg;
if (session.xfer.p)
snprintf(argp, sizeof(arg), "%" PR_LU,
(pr_off_t) session.xfer.total_bytes);
else
sstrncpy(argp, "-", sizeof(arg));
m++;
break;
case META_CLASS:
argp = arg;
sstrncpy(argp, session.class ? session.class->cls_name : "-", sizeof(arg));
m++;
break;
case META_DIR_NAME:
argp = arg;
if (strcmp(cmd->argv[0], C_CDUP) == 0 ||
strcmp(cmd->argv[0], C_CWD) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XCWD) == 0 ||
strcmp(cmd->argv[0], C_XCUP) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0) {
char *tmp = strrchr(cmd->arg, '/');
sstrncpy(argp, tmp ? tmp : cmd->arg, sizeof(arg));
} else {
sstrncpy(argp, "", sizeof(arg));
}
m++;
break;
case META_DIR_PATH:
argp = arg;
if (strcmp(cmd->argv[0], C_CDUP) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XCUP) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0) {
sstrncpy(argp, dir_abs_path(p, cmd->arg, TRUE), sizeof(arg));
} else if (strcmp(cmd->argv[0], C_CWD) == 0 ||
strcmp(cmd->argv[0], C_XCWD) == 0) {
/* Note: by this point in the dispatch cycle, the current working
* directory has already been changed. For the CWD/XCWD commands,
* this means that dir_abs_path() may return an improper path,
* with the target directory being reported twice. To deal with this,
* don't use dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd()
* instead.
*/
if (session.chroot_path) {
/* Chrooted session. */
sstrncpy(arg, strcmp(pr_fs_getvwd(), "/") ?
pdircat(p, session.chroot_path, pr_fs_getvwd(), NULL) :
session.chroot_path, sizeof(arg));
} else
/* Non-chrooted session. */
sstrncpy(arg, pr_fs_getcwd(), sizeof(arg));
} else
sstrncpy(argp, "", sizeof(arg));
m++;
break;
case META_FILENAME:
argp = arg;
if (strcmp(cmd->argv[0], C_RNTO) == 0) {
sstrncpy(argp, dir_abs_path(p, cmd->arg, TRUE), sizeof(arg));
} else if (session.xfer.p &&
session.xfer.path) {
sstrncpy(argp, dir_abs_path(p, session.xfer.path, TRUE), sizeof(arg));
} else {
/* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
* filenames that are not stored in the session.xfer structure; these
* should be expanded properly as well.
*/
if (strcmp(cmd->argv[0], C_DELE) == 0 ||
strcmp(cmd->argv[0], C_MKD) == 0 ||
strcmp(cmd->argv[0], C_RMD) == 0 ||
strcmp(cmd->argv[0], C_XMKD) == 0 ||
strcmp(cmd->argv[0], C_XRMD) == 0)
sstrncpy(arg, dir_abs_path(p, cmd->arg, TRUE), sizeof(arg));
else
/* All other situations get a "-". */
sstrncpy(argp, "-", sizeof(arg));
}
m++;
break;
case META_XFER_PATH:
argp = arg;
if (session.xfer.p && session.xfer.path) {
sstrncpy(argp, session.xfer.path, sizeof(arg));
} else {
/* Some commands (i.e. DELE) have associated filenames that are not
* stored in the session.xfer structure; these should be expanded
* properly as well.
*/
if (strcmp(cmd->argv[0], C_DELE) == 0)
sstrncpy(arg, cmd->arg, sizeof(arg));
else
sstrncpy(argp, "-", sizeof(arg));
}
m++;
break;
case META_ENV_VAR:
argp = arg;
m++;
if (*m == META_START && *(m+1) == META_ARG) {
char *env;
env = getenv(get_next_meta(p,cmd,&m));
sstrncpy(argp, env, sizeof(arg));
}
break;
case META_REMOTE_HOST:
argp = arg;
sstrncpy(argp, session.c->remote_name, sizeof(arg));
m++;
break;
case META_REMOTE_IP:
argp = arg;
sstrncpy(argp, pr_netaddr_get_ipstr(session.c->remote_addr), sizeof(arg));
m++;
break;
case META_IDENT_USER:
argp = arg;
sstrncpy(argp, session.ident_user, sizeof(arg));
m++;
break;
case META_METHOD:
argp = arg;
sstrncpy(argp, cmd->argv[0], sizeof(arg));
m++;
break;
case META_LOCAL_PORT:
argp = arg;
snprintf(argp, sizeof(arg), "%d", cmd->server->ServerPort);
m++;
break;
case META_LOCAL_IP:
argp = arg;
sstrncpy(argp, pr_netaddr_get_ipstr(session.c->local_addr), sizeof(arg));
m++;
break;
case META_LOCAL_FQDN:
argp = arg;
sstrncpy(argp, cmd->server->ServerFQDN, sizeof(arg));
m++;
break;
case META_PID:
argp = arg;
snprintf(argp, sizeof(arg), "%u",(unsigned int)getpid());
m++;
break;
case META_TIME:
{
char *time_fmt = "[%d/%b/%Y:%H:%M:%S ";
struct tm t;
int internal_fmt = 1;
int timz;
char sign;
argp = arg; m++;
if (*m == META_START && *(m+1) == META_ARG) {
time_fmt = get_next_meta(p, cmd, &m);
internal_fmt = 0;
}
t = *_get_gmtoff(&timz);
sign = (timz < 0 ? '-' : '+');
if (timz < 0)
timz = -timz;
strftime(argp, 80, time_fmt, &t);
if (internal_fmt) {
if (strlen(argp) < sizeof(arg))
snprintf(argp + strlen(argp), sizeof(arg) - strlen(argp),
"%c%.2d%.2d]", sign, timz/60, timz%60);
else
pr_log_pri(PR_LOG_NOTICE, "notice: %%t expansion yields excessive "
"string, ignoring");
}
}
break;
case META_SECONDS:
argp = arg;
if (session.xfer.p) {
/* Make sure that session.xfer.start_time actually has values (which
* is not always the case).
*/
if (session.xfer.start_time.tv_sec != 0 ||
session.xfer.start_time.tv_usec != 0) {
struct timeval end_time;
gettimeofday(&end_time,NULL);
end_time.tv_sec -= session.xfer.start_time.tv_sec;
if (end_time.tv_usec >= session.xfer.start_time.tv_usec)
end_time.tv_usec -= session.xfer.start_time.tv_usec;
else {
end_time.tv_usec = 1000000L - (session.xfer.start_time.tv_usec -
end_time.tv_usec);
end_time.tv_sec--;
}
snprintf(argp, sizeof(arg), "%ld.%03ld", (long) end_time.tv_sec,
(long) (end_time.tv_usec / 1000));
} else
sstrncpy(argp, "-", sizeof(arg));
} else
sstrncpy(argp, "-", sizeof(arg));
m++;
break;
case META_COMMAND:
argp = arg;
if (strcasecmp(cmd->argv[0], C_PASS) == 0 &&
session.hide_password) {
sstrncpy(argp, "PASS (hidden)", sizeof(arg));
} else {
sstrncpy(argp, get_full_cmd(cmd), sizeof(arg));
}
m++;
break;
case META_CMD_PARAMS:
argp = arg;
if (strcasecmp(cmd->argv[0], C_PASS) == 0 &&
session.hide_password) {
sstrncpy(argp, "(hidden)", sizeof(arg));
} else {
sstrncpy(argp, cmd->arg, sizeof(arg));
}
m++;
break;
case META_LOCAL_NAME:
argp = arg;
sstrncpy(argp, cmd->server->ServerName, sizeof(arg));
m++;
break;
case META_USER:
argp = arg;
if (!session.user) {
char *u;
u = get_param_ptr(cmd->server->conf,"UserName",FALSE);
if (!u)
u = "root";
sstrncpy(argp, u, sizeof(arg));
} else {
sstrncpy(argp, session.user, sizeof(arg));
}
m++;
break;
case META_ORIGINAL_USER:
{
char *login_user = get_param_ptr(main_server->conf, C_USER, FALSE);
argp = arg;
if (login_user)
sstrncpy(argp, login_user, sizeof(arg));
else
sstrncpy(argp, "(none)", sizeof(arg));
m++;
break;
}
case META_RESPONSE_CODE:
{
pr_response_t *r;
argp = arg;
r = (resp_list ? resp_list : resp_err_list);
for (; r && !r->num; r = r->next) ;
if (r &&
r->num) {
sstrncpy(argp, r->num, sizeof(arg));
/* Hack to add return code for proper logging of QUIT command. */
} else if (strcasecmp(cmd->argv[0], C_QUIT) == 0) {
sstrncpy(argp, R_221, sizeof(arg));
} else {
sstrncpy(argp, "-", sizeof(arg));
}
}
m++;
break;
}
*f = m;
if (argp)
return pstrdup(p, argp);
else
return NULL;
}
/* from src/log.c */
extern int syslog_sockfd;
static void do_log(cmd_rec *cmd, logfile_t *lf) {
unsigned char *f = NULL;
size_t size = EXTENDED_LOG_BUFFER_SIZE-2;
char logbuf[EXTENDED_LOG_BUFFER_SIZE] = {'\0'};
logformat_t *fmt = NULL;
char *s, *bp;
fmt = lf->lf_format;
f = fmt->lf_format;
bp = logbuf;
while (*f && size) {
if (*f == META_START) {
s = get_next_meta(cmd->tmp_pool, cmd, &f);
if (s) {
size_t tmp;
tmp = strlen(s);
if (tmp > size)
tmp = size;
memcpy(bp, s, tmp);
size -= tmp;
bp += tmp;
}
} else {
*bp++ = (char) *f++;
size--;
}
}
*bp++ = '\n';
*bp = '\0';
if (lf->lf_fd != EXTENDED_LOG_SYSLOG)
write(lf->lf_fd, logbuf, strlen(logbuf));
else
pr_syslog(syslog_sockfd, lf->lf_syslog_level, "%s", logbuf);
}
MODRET log_any(cmd_rec *cmd) {
logfile_t *lf = NULL;
/* If not in anon mode, only handle logs for main servers */
for (lf = logs; lf; lf = lf->next)
if (lf->lf_fd != -1 && (cmd->class & lf->lf_classes)) {
if (!session.anon_config && lf->lf_conf &&
lf->lf_conf->config_type == CONF_ANON)
continue;
do_log(cmd, lf);
}
return DECLINED(cmd);
}
static void log_restart_ev(const void *event_data, void *user_data) {
destroy_pool(log_pool);
formats = NULL;
format_set = NULL;
logs = NULL;
log_set = NULL;
log_pool = make_sub_pool(permanent_pool);
pr_pool_tag(log_pool, "mod_log pool");
logformat("", "%h %l %u %t \"%r\" %s %b");
return;
}
static int log_init(void) {
log_pool = make_sub_pool(permanent_pool);
pr_pool_tag(log_pool, "mod_log pool");
/* Add the "default" extendedlog format */
logformat("", "%h %l %u %t \"%r\" %s %b");
pr_event_register(&log_module, "core.restart", log_restart_ev, NULL);
return 0;
}
static void find_extendedlogs(void) {
config_rec *c;
char *logfname;
int logclasses = CL_ALL;
logformat_t *logfmt;
char *logfmt_s = NULL;
logfile_t *extlog = NULL;
/* We _do_ actually want the recursion here. The reason is that we want
* to find _all_ ExtendedLog directives in the configuration, including
* those in <Anonymous> sections. We have the ability to use root privs
* now, to make sure these files can be opened, but after the user has
* authenticated (and we know for sure whether they're anonymous or not),
* root privs may be permanently revoked. Yucky...but necessary, I guess.
*/
c = find_config(main_server->conf, CONF_PARAM, "ExtendedLog", TRUE);
while (c) {
logfname = c->argv[0];
if (c->argc > 1) {
logclasses = _parse_classes(c->argv[1]);
if (c->argc > 2)
logfmt_s = c->argv[2];
}
/* No logging for this round.
*/
if (logclasses == CL_NONE)
goto loop_extendedlogs;
if (logfmt_s) {
/* search for the format-nickname */
for (logfmt = formats; logfmt; logfmt = logfmt->next)
if (strcmp(logfmt->lf_nickname, logfmt_s) == 0)
break;
if (!logfmt) {
pr_log_pri(PR_LOG_NOTICE,
"ExtendedLog '%s' uses unknown format nickname '%s'", logfname,
logfmt_s);
goto loop_extendedlogs;
}
} else {
logfmt = formats;
}
extlog = (logfile_t *) pcalloc(session.pool, sizeof(logfile_t));
extlog->lf_filename = pstrdup(session.pool, logfname);
extlog->lf_fd = -1;
extlog->lf_syslog_level = -1;
extlog->lf_classes = logclasses;
extlog->lf_format = logfmt;
extlog->lf_conf = c->parent;
if (!log_set)
log_set = xaset_create(session.pool, NULL);
xaset_insert(log_set, (xasetmember_t *) extlog);
logs = (logfile_t *) log_set->xas_list;
loop_extendedlogs:
c = find_config_next(c, c->next, CONF_PARAM, "ExtendedLog", TRUE);
}
}
MODRET log_post_pass(cmd_rec *cmd) {
logfile_t *lf;
/* Authentication is complete, if we aren't in anon-mode, close
* all extendedlogs opened inside <Anonymous> blocks.
*/
if (!session.anon_config) {
for (lf = logs; lf; lf = lf->next) {
if (lf->lf_fd != -1 && lf->lf_fd != EXTENDED_LOG_SYSLOG &&
lf->lf_conf && lf->lf_conf->config_type == CONF_ANON) {
pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s'",
lf->lf_filename);
close(lf->lf_fd);
lf->lf_fd = -1;
}
}
} else {
/* Close all logs which were opened inside a _different_ anonymous
* context.
*/
for (lf = logs; lf; lf = lf->next) {
if (lf->lf_fd != -1 && lf->lf_fd != EXTENDED_LOG_SYSLOG &&
lf->lf_conf && lf->lf_conf != session.anon_config) {
pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s'",
lf->lf_filename);
close(lf->lf_fd);
lf->lf_fd = -1;
}
}
/* If any ExtendedLogs set inside our context match an outer log,
* close the outer (this allows overriding inside <Anonymous>).
*/
for (lf = logs; lf; lf = lf->next) {
if (lf->lf_conf && lf->lf_conf == session.anon_config) {
/* This should "override" any lower-level extendedlog with the
* same filename.
*/
logfile_t *lfi = NULL;
for (lfi = logs; lfi; lfi = lfi->next) {
if (lfi->lf_fd != -1 &&
lfi->lf_fd != EXTENDED_LOG_SYSLOG &&
!lfi->lf_conf &&
strcmp(lfi->lf_filename, lf->lf_filename) == 0) {
pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s'",
lf->lf_filename);
close(lfi->lf_fd);
lfi->lf_fd = -1;
}
}
/* Go ahead and close the log if it's CL_NONE */
if (lf->lf_fd != -1 &&
lf->lf_fd != EXTENDED_LOG_SYSLOG &&
lf->lf_classes == CL_NONE) {
pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s'",
lf->lf_filename);
close(lf->lf_fd);
lf->lf_fd = -1;
}
}
}
}
return DECLINED(cmd);
}
/* Open all the log files */
static int log_sess_init(void) {
char *serverlog_name = NULL;
logfile_t *lf = NULL;
/* Open the ServerLog, if present. */
if ((serverlog_name = get_param_ptr(main_server->conf, "ServerLog",
FALSE)) != NULL) {
PRIVS_ROOT
log_closesyslog();
log_opensyslog(serverlog_name);
PRIVS_RELINQUISH
}
/* Open all the ExtendedLog files. */
find_extendedlogs();
for (lf = logs; lf; lf = lf->next) {
if (lf->lf_fd == -1) {
/* Is this ExtendedLog to be written to a file, or to syslog? */
if (strncasecmp(lf->lf_filename, "syslog:", 7) != 0) {
int res = 0;
pr_log_debug(DEBUG7, "mod_log: opening ExtendedLog '%s'",
lf->lf_filename);
pr_signals_block();
PRIVS_ROOT
res = pr_log_openfile(lf->lf_filename, &lf->lf_fd, EXTENDED_LOG_MODE);
PRIVS_RELINQUISH
pr_signals_unblock();
if (res == -1) {
pr_log_pri(PR_LOG_NOTICE, "unable to open ExtendedLog '%s': %s",
lf->lf_filename, strerror(errno));
continue;
} else if (res == PR_LOG_WRITABLE_DIR) {
pr_log_pri(PR_LOG_NOTICE, "unable to open ExtendedLog '%s': "
"containing directory is world writeable", lf->lf_filename);
continue;
} else if (res == PR_LOG_SYMLINK) {
pr_log_pri(PR_LOG_NOTICE, "unable to open ExtendedLog '%s': "
"%s is a symbolic link", lf->lf_filename, lf->lf_filename);
close(lf->lf_fd);
lf->lf_fd = -1;
continue;
}
} else {
char *tmp = strchr(lf->lf_filename, ':');
lf->lf_syslog_level = pr_log_str2sysloglevel(++tmp);
lf->lf_fd = EXTENDED_LOG_SYSLOG;
}
}
}
return 0;
}
/* Module API tables
*/
static conftable log_conftab[] = {
{ "AllowLogSymlinks", set_allowlogsymlinks, NULL },
{ "ExtendedLog", set_extendedlog, NULL },
{ "LogFormat", set_logformat, NULL },
{ "ServerLog", set_serverlog, NULL },
{ "SystemLog", set_systemlog, NULL },
{ NULL, NULL, NULL }
};
static cmdtable log_cmdtab[] = {
{ LOG_CMD, C_ANY, G_NONE, log_any, FALSE, FALSE },
{ LOG_CMD_ERR, C_ANY, G_NONE, log_any, FALSE, FALSE },
{ POST_CMD, C_PASS, G_NONE, log_post_pass, FALSE, FALSE },
{ 0, NULL }
};
module log_module = {
NULL, NULL,
/* Module API version */
0x20,
/* Module name */
"log",
/* Module configuration handler table */
log_conftab,
/* Module command handler table */
log_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization */
log_init,
/* Session initialization */
log_sess_init
};
Last Updated: Thu Feb 23 11:07:05 2006
HTML generated by tj's src2html script