diff --git a/proftpd-1.3.7rc3-logging-not-systemd.patch b/proftpd-1.3.7rc3-logging-not-systemd.patch new file mode 100644 index 0000000..c7bd00b --- /dev/null +++ b/proftpd-1.3.7rc3-logging-not-systemd.patch @@ -0,0 +1,12 @@ +--- proftpd.conf ++++ proftpd.conf +@@ -114,9 +114,6 @@ UseSendfile off + LogFormat default "%h %l %u %t \"%r\" %s %b" + LogFormat auth "%v [%P] %h %t \"%r\" %s" + +-# Don't log hostname or timestamps because systemd will do that for us +-LogOptions -Timestamp -Hostname +RoleBasedProcessLabels +- + # Enable basic controls via ftpdctl + # (http://www.proftpd.org/docs/modules/mod_ctrls.html) + ControlsEngine on diff --git a/proftpd-1.3.7rc3-logoptions.patch b/proftpd-1.3.7rc3-logoptions.patch new file mode 100644 index 0000000..9cdf288 --- /dev/null +++ b/proftpd-1.3.7rc3-logoptions.patch @@ -0,0 +1,1602 @@ +--- doc/modules/mod_core.html ++++ doc/modules/mod_core.html +@@ -2988,9 +2988,11 @@ adding/remove certain fields of data. + The options supported by the TraceOptions directive are: + +-These options are all disabled by default. ++The Timestamp option is enabled by default; the others ++are disabled by default. + +

+ To enable an option, preface the option name with a '+' (plus) character; +--- doc/modules/mod_log.html ++++ doc/modules/mod_log.html +@@ -21,6 +21,7 @@ ProFTPD 1.3.x, and is compiled by +

  • AllowLogSymlinks +
  • ExtendedLog +
  • LogFormat ++
  • LogOptions +
  • ServerLog +
  • SystemLog + +@@ -480,6 +481,58 @@ See also: E + +

    +


    ++

    LogOptions

    ++Syntax: LogOptions opt1 ... optN
    ++Default: None
    ++Context: server config, <VirtualHost>, <Global>
    ++Module: mod_core
    ++Compatibility: 1.3.7rc4 and later ++ ++

    ++The LogOptions directive can be used to change the format ++of the SystemLog messages, e.g. ++adding/remove certain fields of data. These options also apply to ++all module logging; ProFTPD logging is centralized, and the ++LogOptions are applied to any/all logging. ++ ++

    ++The options supported by the LogOptions directive are: ++

    ++All of these options are enabled by default, except for the ++RoleBasedProcessLabels option. ++ ++

    ++To enable an option, preface the option name with a '+' (plus) character; ++to disable the option, use a '-' (minus) character prefix. For example: ++

    ++  # Log messages without timestamps or hostname
    ++  LogOptions -Hostname -Timestamp
    ++
    ++ ++

    ++The RoleBasedProcessLabels option changes the label ++just for the PID, from e.g.: ++

    ++  proftpd[1234]
    ++
    ++to one of the following, depending on whether the process is the master ++daemon process, or a forked session process: ++
    ++  daemon[2345]
    ++  session[34567]
    ++
    ++This is useful, for example, when relying on systemd logging: ++
    ++  LogOptions -Timestamp -Hostname +RoleBasedProcessLabels
    ++
    ++ ++

    ++


    +

    ServerLog

    + Syntax: ServerLog path|"none"
    + Default: None
    +--- include/log.h ++++ include/log.h +@@ -2,7 +2,7 @@ + * ProFTPD - FTP server daemon + * Copyright (c) 1997, 1998 Public Flood Software + * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu +- * Copyright (c) 2001-2016 The ProFTPD Project team ++ * Copyright (c) 2001-2020 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 +@@ -99,7 +99,6 @@ + # endif + #endif + +- + int log_lastlog(uid_t uid, const char *user_name, const char *tty, + const pr_netaddr_t *remote_addr); + #endif /* PR_USE_LASTLOG */ +@@ -224,4 +223,12 @@ int pr_log_event_listening(unsigned int + */ + void pr_log_stacktrace(int fd, const char *name); + ++/* Set options that affect the format of the logged messages. */ ++int pr_log_set_options(unsigned long log_opts); ++#define PR_LOG_OPT_USE_TIMESTAMP 0x0001 ++#define PR_LOG_OPT_USE_HOSTNAME 0x0002 ++#define PR_LOG_OPT_USE_VHOST 0x0004 ++#define PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS 0x0008 ++#define PR_LOG_OPT_DEFAULT (PR_LOG_OPT_USE_TIMESTAMP|PR_LOG_OPT_USE_HOSTNAME|PR_LOG_OPT_USE_VHOST) ++ + #endif /* PR_LOG_H */ +--- include/trace.h ++++ include/trace.h +@@ -1,6 +1,6 @@ + /* + * ProFTPD - FTP server daemon +- * Copyright (c) 2006-2016 The ProFTPD Project team ++ * Copyright (c) 2006-2020 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 +@@ -50,8 +50,9 @@ int pr_trace_use_stderr(int); + + int pr_trace_set_options(unsigned long trace_opts); + #define PR_TRACE_OPT_LOG_CONN_IPS 0x0001 +-#define PR_TRACE_OPT_USE_TIMESTAMP_MILLIS 0x0002 +-#define PR_TRACE_OPT_DEFAULT PR_TRACE_OPT_USE_TIMESTAMP_MILLIS ++#define PR_TRACE_OPT_USE_TIMESTAMP 0x0002 ++#define PR_TRACE_OPT_USE_TIMESTAMP_MILLIS 0x0004 ++#define PR_TRACE_OPT_DEFAULT (PR_TRACE_OPT_USE_TIMESTAMP|PR_TRACE_OPT_USE_TIMESTAMP_MILLIS) + + int pr_trace_msg(const char *, int, const char *, ...) + #ifdef __GNUC__ +--- modules/mod_core.c ++++ modules/mod_core.c +@@ -1695,7 +1695,7 @@ MODRET set_traceoptions(cmd_rec *cmd) { + + opt++; + +- if (strncasecmp(opt, "ConnIPs", 8) == 0) { ++ if (strcasecmp(opt, "ConnIPs") == 0) { + switch (action) { + case '-': + trace_opts &= ~PR_TRACE_OPT_LOG_CONN_IPS; +@@ -1706,7 +1706,18 @@ MODRET set_traceoptions(cmd_rec *cmd) { + break; + } + +- } else if (strncasecmp(opt, "TimestampMillis", 16) == 0) { ++ } else if (strcasecmp(opt, "Timestamp") == 0) { ++ switch (action) { ++ case '-': ++ trace_opts &= ~PR_TRACE_OPT_USE_TIMESTAMP; ++ break; ++ ++ case '+': ++ trace_opts |= PR_TRACE_OPT_USE_TIMESTAMP; ++ break; ++ } ++ ++ } else if (strcasecmp(opt, "TimestampMillis") == 0) { + switch (action) { + case '-': + trace_opts &= ~PR_TRACE_OPT_USE_TIMESTAMP_MILLIS; +--- modules/mod_log.c ++++ modules/mod_log.c +@@ -216,6 +216,104 @@ MODRET set_logformat(cmd_rec *cmd) { + return PR_HANDLED(cmd); + } + ++/* usage: LogOptions opt1 ... */ ++MODRET set_logoptions(cmd_rec *cmd) { ++ register unsigned int i; ++ int ctx; ++ unsigned long log_opts = PR_LOG_OPT_DEFAULT; ++ config_rec *c; ++ ++ if (cmd->argc < 2) { ++ CONF_ERROR(cmd, "wrong number of parameters"); ++ } ++ ++ CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL|CONF_VIRTUAL); ++ ++ for (i = 1; i < cmd->argc; i++) { ++ char action, *opt; ++ ++ opt = cmd->argv[i]; ++ action = *opt; ++ ++ if (action != '+' && ++ action != '-') { ++ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad LogOption: '", opt, "'", ++ NULL)); ++ } ++ ++ opt++; ++ ++ if (strcasecmp(opt, "Timestamp") == 0) { ++ switch (action) { ++ case '-': ++ log_opts &= ~PR_LOG_OPT_USE_TIMESTAMP; ++ break; ++ ++ case '+': ++ log_opts |= PR_LOG_OPT_USE_TIMESTAMP; ++ break; ++ } ++ ++ } else if (strcasecmp(opt, "Hostname") == 0) { ++ switch (action) { ++ case '-': ++ log_opts &= ~PR_LOG_OPT_USE_HOSTNAME; ++ break; ++ ++ case '+': ++ log_opts |= PR_LOG_OPT_USE_HOSTNAME; ++ break; ++ } ++ ++ } else if (strcasecmp(opt, "VirtualHost") == 0) { ++ switch (action) { ++ case '-': ++ log_opts &= ~PR_LOG_OPT_USE_VHOST; ++ break; ++ ++ case '+': ++ log_opts |= PR_LOG_OPT_USE_VHOST; ++ break; ++ } ++ ++ } else if (strcasecmp(opt, "RoleBasedProcessLabels") == 0) { ++ switch (action) { ++ case '-': ++ log_opts &= ~PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS; ++ break; ++ ++ case '+': ++ log_opts |= PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS; ++ break; ++ } ++ ++ } else { ++ CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown LogOption: '", ++ opt, "'", NULL)); ++ } ++ } ++ ++ c = add_config_param(cmd->argv[0], 1, NULL); ++ c->argv[0] = palloc(c->pool, sizeof(unsigned long)); ++ *((unsigned long *) c->argv[0]) = log_opts; ++ ++ ctx = (cmd->config && cmd->config->config_type != CONF_PARAM ? ++ cmd->config->config_type : cmd->server->config_type ? ++ cmd->server->config_type : CONF_ROOT); ++ ++ if (ctx == CONF_ROOT) { ++ /* If we're the "server config" context, set the LogOptions here, ++ * too. This will apply these LogOptions to the daemon process. ++ */ ++ if (pr_log_set_options(log_opts) < 0) { ++ pr_log_debug(DEBUG6, "%s: error setting LogOptions (%lu): %s", ++ (char *) cmd->argv[0], log_opts, strerror(errno)); ++ } ++ } ++ ++ return PR_HANDLED(cmd); ++} ++ + /* Syntax: ExtendedLog file [ []] */ + MODRET set_extendedlog(cmd_rec *cmd) { + config_rec *c = NULL; +@@ -832,6 +930,9 @@ static void log_sess_reinit_ev(const voi + } + } + ++ /* Restore original LogOptions settings. */ ++ (void) pr_log_set_options(PR_LOG_OPT_DEFAULT); ++ + res = log_sess_init(); + if (res < 0) { + pr_session_disconnect(&log_module, +@@ -1066,12 +1167,24 @@ MODRET log_post_pass(cmd_rec *cmd) { + static int dispatched_connect = FALSE; + + static int log_sess_init(void) { ++ config_rec *c; + char *serverlog_name = NULL; + logfile_t *lf = NULL; + + pr_event_register(&log_module, "core.session-reinit", log_sess_reinit_ev, + NULL); + ++ c = find_config(main_server->conf, CONF_PARAM, "LogOptions", FALSE); ++ if (c != NULL) { ++ unsigned long log_opts; ++ ++ log_opts = *((unsigned long *) c->argv[0]); ++ if (pr_log_set_options(log_opts) < 0) { ++ pr_log_debug(DEBUG6, "%s: error setting LogOptions (%lu): %s", ++ c->name, log_opts, strerror(errno)); ++ } ++ } ++ + /* Open the ServerLog, if present. */ + serverlog_name = get_param_ptr(main_server->conf, "ServerLog", FALSE); + if (serverlog_name != NULL) { +@@ -1238,6 +1351,7 @@ static conftable log_conftab[] = { + { "AllowLogSymlinks", set_allowlogsymlinks, NULL }, + { "ExtendedLog", set_extendedlog, NULL }, + { "LogFormat", set_logformat, NULL }, ++ { "LogOptions", set_logoptions, NULL }, + { "ServerLog", set_serverlog, NULL }, + { "SystemLog", set_systemlog, NULL }, + { NULL, NULL, NULL } +--- src/ftpdctl.c ++++ src/ftpdctl.c +@@ -35,6 +35,7 @@ static const char *program = "ftpdctl"; + + /* NOTE: these empty stubs are needed for proper linking. What a mess. */ + ++unsigned char is_master; + session_t session; + server_rec *main_server = NULL; + +--- src/log.c ++++ src/log.c +@@ -34,8 +34,12 @@ + + #define LOGBUFFER_SIZE (PR_TUNABLE_PATH_MAX * 4) + ++/* From src/main.c */ ++extern unsigned char is_master; ++ + static int syslog_open = FALSE; + static int syslog_discard = FALSE; ++static unsigned long log_opts = PR_LOG_OPT_DEFAULT; + static int logstderr = TRUE; + static int debug_level = DEBUG0; + static int default_level = PR_LOG_NOTICE; +@@ -483,24 +487,26 @@ static void log_write(int priority, int + main_server->ServerFQDN) { + const pr_netaddr_t *remote_addr; + const char *remote_name; ++ size_t buflen = 0; + + remote_addr = pr_netaddr_get_sess_remote_addr(); + remote_name = pr_netaddr_get_sess_remote_name(); + +- pr_snprintf(serverinfo, sizeof(serverinfo)-1, "%s", +- main_server->ServerFQDN); ++ if (log_opts & PR_LOG_OPT_USE_VHOST) { ++ size_t len; ++ ++ len = pr_snprintf(serverinfo, sizeof(serverinfo)-1, "%s", ++ main_server->ServerFQDN); ++ buflen += len; ++ } ++ + serverinfo[sizeof(serverinfo)-1] = '\0'; + + if (remote_addr != NULL && + remote_name != NULL) { +- size_t serverinfo_len; +- +- serverinfo_len = strlen(serverinfo); +- +- pr_snprintf(serverinfo + serverinfo_len, +- sizeof(serverinfo) - serverinfo_len, " (%s[%s])", ++ pr_snprintf(serverinfo + buflen, sizeof(serverinfo) - buflen, ++ "%s(%s[%s])", (log_opts & PR_LOG_OPT_USE_VHOST) ? " " : "", + remote_name, pr_netaddr_get_ipstr(remote_addr)); +- + serverinfo[sizeof(serverinfo)-1] = '\0'; + } + } +@@ -508,37 +514,63 @@ static void log_write(int priority, int + if (!discard && + (logstderr || !main_server)) { + char buf[LOGBUFFER_SIZE] = {'\0'}; +- size_t buflen, len; +- struct timeval now; +- struct tm *tm = NULL; +- unsigned long millis; +- +- gettimeofday(&now, NULL); +- tm = pr_localtime(NULL, (const time_t *) &(now.tv_sec)); +- if (tm == NULL) { +- return; ++ size_t buflen = 0, len = 0; ++ pid_t log_pid; ++ const char *process_label = "proftpd"; ++ ++ if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { ++ pool *tmp_pool; ++ struct timeval now; ++ struct tm *tm = NULL; ++ unsigned long millis; ++ ++ tmp_pool = make_sub_pool(permanent_pool); ++ pr_pool_tag(tmp_pool, "Log message pool"); ++ ++ gettimeofday(&now, NULL); ++ tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); ++ if (tm == NULL) { ++ destroy_pool(tmp_pool); ++ return; ++ } ++ ++ len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); ++ buflen = len; ++ buf[sizeof(buf)-1] = '\0'; ++ destroy_pool(tmp_pool); ++ ++ /* Convert microsecs to millisecs. */ ++ millis = now.tv_usec / 1000; ++ ++ len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); ++ buflen += len; + } + +- len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); +- buflen = len; +- buf[sizeof(buf)-1] = '\0'; ++ if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { ++ len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", ++ systemlog_host); ++ buflen += len; ++ } + +- /* Convert microsecs to millisecs. */ +- millis = now.tv_usec / 1000; ++ log_pid = session.pid ? session.pid : getpid(); + +- len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); +- buflen += len; +- buf[sizeof(buf)-1] = '\0'; ++ if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { ++ if (is_master) { ++ process_label = "daemon"; ++ ++ } else { ++ process_label = "session"; ++ } ++ } + + if (*serverinfo) { + len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, +- "%s proftpd[%u] %s: %s\n", systemlog_host, +- (unsigned int) (session.pid ? session.pid : getpid()), serverinfo, s); ++ "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, ++ s); + + } else { + len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, +- "%s proftpd[%u]: %s\n", systemlog_host, +- (unsigned int) (session.pid ? session.pid : getpid()), s); ++ "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); + } + + buflen += len; +@@ -589,37 +621,65 @@ static void log_write(int priority, int + + if (systemlog_fd != -1) { + char buf[LOGBUFFER_SIZE] = {'\0'}; +- size_t buflen, len; +- struct timeval now; +- struct tm *tm; +- unsigned long millis; +- +- gettimeofday(&now, NULL); +- tm = pr_localtime(NULL, (const time_t *) &(now.tv_sec)); +- if (tm == NULL) { +- return; ++ size_t buflen = 0, len = 0; ++ pid_t log_pid; ++ const char *process_label = "proftpd"; ++ ++ if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { ++ pool *tmp_pool; ++ struct timeval now; ++ struct tm *tm; ++ unsigned long millis; ++ ++ tmp_pool = make_sub_pool(permanent_pool); ++ pr_pool_tag(tmp_pool, "Log message pool"); ++ ++ gettimeofday(&now, NULL); ++ tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); ++ if (tm == NULL) { ++ destroy_pool(tmp_pool); ++ return; ++ } ++ ++ len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); ++ buflen = len; ++ buf[sizeof(buf) - 1] = '\0'; ++ destroy_pool(tmp_pool); ++ ++ /* Convert microsecs to millisecs. */ ++ millis = now.tv_usec / 1000; ++ ++ len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); ++ buflen += len; + } + +- len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); +- buflen = len; + buf[sizeof(buf) - 1] = '\0'; + +- /* Convert microsecs to millisecs. */ +- millis = now.tv_usec / 1000; ++ if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { ++ len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", ++ systemlog_host); ++ buflen += len; ++ } ++ ++ log_pid = session.pid ? session.pid : getpid(); + +- len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); +- buflen += len; +- buf[sizeof(buf) - 1] = '\0'; ++ if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { ++ if (is_master) { ++ process_label = "daemon"; ++ ++ } else { ++ process_label = "session"; ++ } ++ } + + if (*serverinfo) { + len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, +- "%s proftpd[%u] %s: %s\n", systemlog_host, +- (unsigned int) (session.pid ? session.pid : getpid()), serverinfo, s); ++ "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, ++ s); + + } else { + len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, +- "%s proftpd[%u]: %s\n", systemlog_host, +- (unsigned int) (session.pid ? session.pid : getpid()), s); ++ "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); + } + + buflen += len; +@@ -745,6 +805,11 @@ int pr_log_setdefaultlevel(int level) { + return old_level; + } + ++int pr_log_set_options(unsigned long opts) { ++ log_opts = opts; ++ return 0; ++} ++ + /* Convert a string into the matching syslog level value. Return -1 + * if no matching level is found. + */ +--- src/trace.c ++++ src/trace.c +@@ -93,7 +93,7 @@ static void trace_restart_ev(const void + static int trace_write(const char *channel, int level, const char *msg, + int discard) { + char buf[PR_TUNABLE_BUFFER_SIZE * 2]; +- size_t buflen, len; ++ size_t buflen = 0, len = 0; + struct tm *tm; + int use_conn_ips = FALSE; + +@@ -103,31 +103,32 @@ static int trace_write(const char *chann + + memset(buf, '\0', sizeof(buf)); + +- if (!(trace_opts & PR_TRACE_OPT_USE_TIMESTAMP_MILLIS)) { +- time_t now; ++ if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP) { ++ if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP_MILLIS) { ++ struct timeval now; ++ unsigned long millis; + +- now = time(NULL); +- tm = pr_localtime(NULL, &now); ++ gettimeofday(&now, NULL); ++ tm = pr_localtime(NULL, (const time_t *) &(now.tv_sec)); + +- len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); +- buflen = len; ++ len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); ++ buflen = len; + +- } else { +- struct timeval now; +- unsigned long millis; +- +- gettimeofday(&now, NULL); ++ /* Convert microsecs to millisecs. */ ++ millis = now.tv_usec / 1000; + +- tm = pr_localtime(NULL, (const time_t *) &(now.tv_sec)); ++ len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, ",%03lu ", millis); ++ buflen += len; + +- len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); +- buflen = len; ++ } else { ++ time_t now; + +- /* Convert microsecs to millisecs. */ +- millis = now.tv_usec / 1000; ++ now = time(NULL); ++ tm = pr_localtime(NULL, &now); + +- len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, ",%03lu", millis); +- buflen += len; ++ len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S ", tm); ++ buflen = len; ++ } + } + + if ((trace_opts & PR_TRACE_OPT_LOG_CONN_IPS) && +@@ -140,7 +141,7 @@ static int trace_write(const char *chann + } + + if (use_conn_ips == FALSE) { +- len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, " [%u] <%s:%d>: %s", ++ len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "[%u] <%s:%d>: %s", + (unsigned int) (session.pid ? session.pid : getpid()), channel, level, + msg); + buflen += len; +@@ -154,7 +155,7 @@ static int trace_write(const char *chann + server_port = pr_netaddr_get_port(session.c->local_addr); + + len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, +- " [%u] (client %s, server %s:%d) <%s:%d>: %s", ++ "[%u] (client %s, server %s:%d) <%s:%d>: %s", + (unsigned int) (session.pid ? session.pid : getpid()), + client_ip != NULL ? client_ip : "none", + server_ip != NULL ? server_ip : "none", server_port, channel, level, msg); +--- tests/t/config/logoptions.t ++++ tests/t/config/logoptions.t +@@ -0,0 +1,11 @@ ++#!/usr/bin/env perl ++ ++use lib qw(t/lib); ++use strict; ++ ++use Test::Unit::HarnessUnit; ++ ++$| = 1; ++ ++my $r = Test::Unit::HarnessUnit->new(); ++$r->start("ProFTPD::Tests::Config::LogOptions"); +--- tests/tests.pl ++++ tests/tests.pl +@@ -141,6 +141,7 @@ if (scalar(@ARGV) > 0) { + t/config/ifdefine.t + t/config/include.t + t/config/listoptions.t ++ t/config/logoptions.t + t/config/masqueradeaddress.t + t/config/maxclients.t + t/config/maxclientsperclass.t +--- tests/t/lib/ProFTPD/Tests/Config/LogOptions.pm ++++ tests/t/lib/ProFTPD/Tests/Config/LogOptions.pm +@@ -0,0 +1,452 @@ ++package ProFTPD::Tests::Config::LogOptions; ++ ++use lib qw(t/lib); ++use base qw(ProFTPD::TestSuite::Child); ++use strict; ++ ++use File::Spec; ++use IO::Handle; ++ ++use ProFTPD::TestSuite::FTP; ++use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); ++ ++$| = 1; ++ ++my $order = 0; ++ ++my $TESTS = { ++ logoptions_no_timestamp => { ++ order => ++$order, ++ test_class => [qw(forking)], ++ }, ++ ++ logoptions_no_hostname => { ++ order => ++$order, ++ test_class => [qw(forking)], ++ }, ++ ++ logoptions_no_virtualhost => { ++ order => ++$order, ++ test_class => [qw(forking)], ++ }, ++ ++ logoptions_use_rolebasedprocesslabels => { ++ order => ++$order, ++ test_class => [qw(forking)], ++ }, ++}; ++ ++sub new { ++ return shift()->SUPER::new(@_); ++} ++ ++sub list_tests { ++ return testsuite_get_runnable_tests($TESTS); ++} ++ ++sub logoptions_no_timestamp { ++ my $self = shift; ++ my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'log'); ++ ++ my $config = { ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, ++ ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, ++ ++ LogOptions => '-Timestamp', ++ ++ IfModules => { ++ 'mod_delay.c' => { ++ DelayEngine => 'off', ++ }, ++ }, ++ }; ++ ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); ++ ++ # Open pipes, for use between the parent and child processes. Specifically, ++ # the child will indicate when it's done with its test by writing a message ++ # to the parent. ++ my ($rfh, $wfh); ++ unless (pipe($rfh, $wfh)) { ++ die("Can't open pipe: $!"); ++ } ++ ++ my $ex; ++ ++ # Fork child ++ $self->handle_sigchld(); ++ defined(my $pid = fork()) or die("Can't fork: $!"); ++ if ($pid) { ++ eval { ++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); ++ $client->login($setup->{user}, $setup->{passwd}); ++ $client->quit(); ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ $wfh->print("done\n"); ++ $wfh->flush(); ++ ++ } else { ++ eval { server_wait($setup->{config_file}, $rfh) }; ++ if ($@) { ++ warn($@); ++ exit 1; ++ } ++ ++ exit 0; ++ } ++ ++ # Stop server ++ server_stop($setup->{pid_file}); ++ $self->assert_child_ok($pid); ++ ++ eval { ++ if (open(my $fh, "< $setup->{log_file}")) { ++ my $ok = 0; ++ ++ while (my $line = <$fh>) { ++ chomp($line); ++ ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } ++ ++ my $expected = '^\S+ proftpd\[\d+\] .*?$'; ++ ++ if ($line =~ /$expected/) { ++ $ok = 1; ++ last; ++ } ++ } ++ ++ close($fh); ++ ++ $self->assert($ok, test_msg("Missing expected LogOptions messages")); ++ ++ } else { ++ die("Can't read $setup->{log_file}: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ test_cleanup($setup->{log_file}, $ex); ++} ++ ++sub logoptions_no_hostname { ++ my $self = shift; ++ my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'log'); ++ ++ my $config = { ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, ++ ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, ++ ++ LogOptions => '-Hostname', ++ ++ IfModules => { ++ 'mod_delay.c' => { ++ DelayEngine => 'off', ++ }, ++ }, ++ }; ++ ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); ++ ++ # Open pipes, for use between the parent and child processes. Specifically, ++ # the child will indicate when it's done with its test by writing a message ++ # to the parent. ++ my ($rfh, $wfh); ++ unless (pipe($rfh, $wfh)) { ++ die("Can't open pipe: $!"); ++ } ++ ++ my $ex; ++ ++ # Fork child ++ $self->handle_sigchld(); ++ defined(my $pid = fork()) or die("Can't fork: $!"); ++ if ($pid) { ++ eval { ++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); ++ $client->login($setup->{user}, $setup->{passwd}); ++ $client->quit(); ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ $wfh->print("done\n"); ++ $wfh->flush(); ++ ++ } else { ++ eval { server_wait($setup->{config_file}, $rfh) }; ++ if ($@) { ++ warn($@); ++ exit 1; ++ } ++ ++ exit 0; ++ } ++ ++ # Stop server ++ server_stop($setup->{pid_file}); ++ $self->assert_child_ok($pid); ++ ++ eval { ++ if (open(my $fh, "< $setup->{log_file}")) { ++ my $ok = 0; ++ ++ while (my $line = <$fh>) { ++ chomp($line); ++ ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } ++ ++ my $expected = '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} proftpd\[\d+\] .*?$'; ++ ++ if ($line =~ /$expected/) { ++ my $pid = $1; ++ $ok = 1; ++ last; ++ } ++ } ++ ++ close($fh); ++ ++ $self->assert($ok, test_msg("Missing expected LogOptions messages")); ++ ++ } else { ++ die("Can't read $setup->{log_file}: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ test_cleanup($setup->{log_file}, $ex); ++} ++ ++sub logoptions_no_virtualhost { ++ my $self = shift; ++ my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'log'); ++ ++ my $config = { ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, ++ ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, ++ ++ LogOptions => '-VirtualHost', ++ ++ IfModules => { ++ 'mod_delay.c' => { ++ DelayEngine => 'off', ++ }, ++ }, ++ }; ++ ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); ++ ++ # Open pipes, for use between the parent and child processes. Specifically, ++ # the child will indicate when it's done with its test by writing a message ++ # to the parent. ++ my ($rfh, $wfh); ++ unless (pipe($rfh, $wfh)) { ++ die("Can't open pipe: $!"); ++ } ++ ++ my $ex; ++ ++ # Fork child ++ $self->handle_sigchld(); ++ defined(my $pid = fork()) or die("Can't fork: $!"); ++ if ($pid) { ++ eval { ++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); ++ $client->login($setup->{user}, $setup->{passwd}); ++ $client->quit(); ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ $wfh->print("done\n"); ++ $wfh->flush(); ++ ++ } else { ++ eval { server_wait($setup->{config_file}, $rfh) }; ++ if ($@) { ++ warn($@); ++ exit 1; ++ } ++ ++ exit 0; ++ } ++ ++ # Stop server ++ server_stop($setup->{pid_file}); ++ $self->assert_child_ok($pid); ++ ++ eval { ++ if (open(my $fh, "< $setup->{log_file}")) { ++ my $ok = 0; ++ ++ while (my $line = <$fh>) { ++ chomp($line); ++ ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } ++ ++ my $expected = '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \S+ proftpd\[\d+\] \(.*?$'; ++ ++ if ($line =~ /$expected/) { ++ $ok = 1; ++ last; ++ } ++ } ++ ++ close($fh); ++ ++ $self->assert($ok, test_msg("Missing expected LogOptions messages")); ++ ++ } else { ++ die("Can't read $setup->{log_file}: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ test_cleanup($setup->{log_file}, $ex); ++} ++ ++sub logoptions_use_rolebasedprocesslabels { ++ my $self = shift; ++ my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'log'); ++ ++ my $config = { ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, ++ ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, ++ ++ LogOptions => '+RoleBasedProcessLabels', ++ ++ IfModules => { ++ 'mod_delay.c' => { ++ DelayEngine => 'off', ++ }, ++ }, ++ }; ++ ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); ++ ++ # Open pipes, for use between the parent and child processes. Specifically, ++ # the child will indicate when it's done with its test by writing a message ++ # to the parent. ++ my ($rfh, $wfh); ++ unless (pipe($rfh, $wfh)) { ++ die("Can't open pipe: $!"); ++ } ++ my $ex; ++ # Fork child ++ $self->handle_sigchld(); ++ defined(my $pid = fork()) or die("Can't fork: $!"); ++ if ($pid) { ++ eval { ++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); ++ $client->login($setup->{user}, $setup->{passwd}); ++ $client->quit(); ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ $wfh->print("done\n"); ++ $wfh->flush(); ++ ++ } else { ++ eval { server_wait($setup->{config_file}, $rfh) }; ++ if ($@) { ++ warn($@); ++ exit 1; ++ } ++ ++ exit 0; ++ } ++ ++ # Stop server ++ server_stop($setup->{pid_file}); ++ $self->assert_child_ok($pid); ++ ++ eval { ++ if (open(my $fh, "< $setup->{log_file}")) { ++ my $saw_daemon_label = 0; ++ my $saw_session_label = 0; ++ ++ while (my $line = <$fh>) { ++ chomp($line); ++ ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } ++ ++ my $expected = '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \S+ daemon\[\d+\] '; ++ if ($line =~ /$expected/) { ++ $saw_daemon_label = 1; ++ } ++ ++ $expected = '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \S+ session\[\d+\] '; ++ if ($line =~ /$expected/) { ++ $saw_session_label = 1; ++ } ++ ++ if ($saw_daemon_label == 1 && ++ $saw_session_label) { ++ last; ++ } ++ } ++ ++ close($fh); ++ ++ $self->assert($saw_daemon_label && $saw_session_label, ++ test_msg("Missing expected LogOptions messages")); ++ ++ } else { ++ die("Can't read $setup->{log_file}: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ test_cleanup($setup->{log_file}, $ex); ++} ++ ++1; +--- tests/t/lib/ProFTPD/Tests/Config/TraceOptions.pm ++++ tests/t/lib/ProFTPD/Tests/Config/TraceOptions.pm +@@ -24,6 +24,11 @@ my $TESTS = { + order => ++$order, + test_class => [qw(feature_trace forking)], + }, ++ ++ traceoptions_no_timestamp => { ++ order => ++$order, ++ test_class => [qw(feature_trace forking)], ++ }, + }; + + sub new { +@@ -37,51 +42,137 @@ sub list_tests { + sub traceoptions_conn_ips { + my $self = shift; + my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'trace'); ++ ++ my $trace_log = File::Spec->rel2abs("$tmpdir/trace.log"); ++ ++ my $config = { ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, ++ TraceLog => $trace_log, ++ Trace => 'DEFAULT:10', ++ TraceOptions => '+ConnIPs', ++ ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, ++ ++ IfModules => { ++ 'mod_delay.c' => { ++ DelayEngine => 'off', ++ }, ++ }, ++ }; ++ ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); ++ ++ # Open pipes, for use between the parent and child processes. Specifically, ++ # the child will indicate when it's done with its test by writing a message ++ # to the parent. ++ my ($rfh, $wfh); ++ unless (pipe($rfh, $wfh)) { ++ die("Can't open pipe: $!"); ++ } ++ ++ my $ex; + +- my $config_file = "$tmpdir/trace.conf"; +- my $pid_file = File::Spec->rel2abs("$tmpdir/trace.pid"); +- my $scoreboard_file = File::Spec->rel2abs("$tmpdir/trace.scoreboard"); +- +- my $log_file = File::Spec->rel2abs('tests.log'); +- +- my $auth_user_file = File::Spec->rel2abs("$tmpdir/trace.passwd"); +- my $auth_group_file = File::Spec->rel2abs("$tmpdir/trace.group"); +- +- my $user = 'proftpd'; +- my $passwd = 'test'; +- my $group = 'ftpd'; +- my $home_dir = File::Spec->rel2abs($tmpdir); +- my $uid = 500; +- my $gid = 500; +- +- # Make sure that, if we're running as root, that the home directory has +- # permissions/privs set for the account we create +- if ($< == 0) { +- unless (chmod(0755, $home_dir)) { +- die("Can't set perms on $home_dir to 0755: $!"); ++ # Fork child ++ $self->handle_sigchld(); ++ defined(my $pid = fork()) or die("Can't fork: $!"); ++ if ($pid) { ++ eval { ++ my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); ++ $client->login($setup->{user}, $setup->{passwd}); ++ $client->quit(); ++ }; ++ if ($@) { ++ $ex = $@; ++ } ++ ++ $wfh->print("done\n"); ++ $wfh->flush(); ++ ++ } else { ++ eval { server_wait($setup->{config_file}, $rfh) }; ++ if ($@) { ++ warn($@); ++ exit 1; + } + +- unless (chown($uid, $gid, $home_dir)) { +- die("Can't set owner of $home_dir to $uid/$gid: $!"); ++ exit 0; ++ } ++ ++ # Stop server ++ server_stop($setup->{pid_file}); ++ $self->assert_child_ok($pid); ++ ++ eval { ++ if (open(my $fh, "< $trace_log")) { ++ my $ok = 0; ++ ++ while (my $line = <$fh>) { ++ chomp($line); ++ ++ my $expected = '\[\d+\]\s+\(client\s+(\S+), server\s+\S+\)\s+<(\S+):(\d+)>: (.*?)$'; ++ ++ if ($line =~ /$expected/) { ++ my $client_ip = $1; ++ my $trace_channel = $2; ++ my $trace_level = $3; ++ my $trace_msg = $4; ++ ++ next unless $trace_channel eq 'command'; ++ ++ my $expected = 'command'; ++ $self->assert($expected eq $trace_channel, ++ test_msg("Expected '$expected', got '$trace_channel'")); ++ ++ $expected = 7; ++ $self->assert($expected >= $trace_level, ++ test_msg("Expected >= $expected, got $trace_level")); ++ ++ $expected = '127.0.0.1'; ++ $self->assert($expected eq $client_ip, ++ test_msg("Expected '$expected', got '$client_ip'")); ++ ++ $ok = 1; ++ last; ++ } ++ } ++ ++ close($fh); ++ ++ $self->assert($ok, test_msg("Missing expected TraceLog messages")); ++ ++ } else { ++ die("Can't read $trace_log: $!"); + } ++ }; ++ if ($@) { ++ $ex = $@; + } + +- auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, +- '/bin/bash'); +- auth_group_write($auth_group_file, $group, $gid, $user); ++ test_cleanup($setup->{log_file}, $ex); ++} ++ ++sub traceoptions_timestamp_millis { ++ my $self = shift; ++ my $tmpdir = $self->{tmpdir}; ++ my $setup = test_setup($tmpdir, 'trace'); + + my $trace_log = File::Spec->rel2abs("$tmpdir/trace.log"); + + my $config = { +- PidFile => $pid_file, +- ScoreboardFile => $scoreboard_file, +- SystemLog => $log_file, ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, + TraceLog => $trace_log, + Trace => 'DEFAULT:10', +- TraceOptions => '+ConnIPs', ++ TraceOptions => '+TimestampMillis', + +- AuthUserFile => $auth_user_file, +- AuthGroupFile => $auth_group_file, ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { +@@ -90,7 +181,8 @@ sub traceoptions_conn_ips { + }, + }; + +- my ($port, $config_user, $config_group) = config_write($config_file, $config); ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message +@@ -108,10 +200,9 @@ sub traceoptions_conn_ips { + if ($pid) { + eval { + my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); +- $client->login($user, $passwd); ++ $client->login($setup->{user}, $setup->{passwd}); + $client->quit(); + }; +- + if ($@) { + $ex = $@; + } +@@ -120,7 +211,7 @@ sub traceoptions_conn_ips { + $wfh->flush(); + + } else { +- eval { server_wait($config_file, $rfh) }; ++ eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; +@@ -130,106 +221,74 @@ sub traceoptions_conn_ips { + } + + # Stop server +- server_stop($pid_file); +- ++ server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + +- if (open(my $fh, "< $trace_log")) { +- my $ok = 0; ++ eval { ++ if (open(my $fh, "< $trace_log")) { ++ my $ok = 0; + +- while (my $line = <$fh>) { +- chomp($line); ++ while (my $line = <$fh>) { ++ chomp($line); + +- my $expected = '\[\d+\]\s+\(client\s+(\S+), server\s+\S+\)\s+<(\S+):(\d+)>: (.*?)$'; ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } + +- if ($line =~ /$expected/) { +- my $client_ip = $1; +- my $trace_channel = $2; +- my $trace_level = $3; +- my $trace_msg = $4; ++ my $expected = '\d+:\d+:\d+,\d+\s+\[\d+\]\s+<(\S+):(\d+)>: (.*?)$'; + +- next unless $trace_channel eq 'command'; ++ if ($line =~ /$expected/) { ++ my $trace_channel = $1; ++ my $trace_level = $2; ++ my $trace_msg = $3; + +- my $expected = 'command'; +- $self->assert($expected eq $trace_channel, +- test_msg("Expected '$expected', got '$trace_channel'")); ++ next unless $trace_channel eq 'command'; + +- $expected = 7; +- $self->assert($expected >= $trace_level, +- test_msg("Expected >= $expected, got $trace_level")); ++ my $expected = 'command'; ++ $self->assert($expected eq $trace_channel, ++ test_msg("Expected '$expected', got '$trace_channel'")); + +- $expected = '127.0.0.1'; +- $self->assert($expected eq $client_ip, +- test_msg("Expected '$expected', got '$client_ip'")); ++ $expected = 7; ++ $self->assert($expected >= $trace_level, ++ test_msg("Expected >= $expected, got $trace_level")); + +- $ok = 1; +- last; ++ $ok = 1; ++ last; ++ } + } +- } +- +- close($fh); + +- $self->assert($ok, test_msg("Missing expected TraceLog messages")); ++ close($fh); + +- } else { +- die("Can't read $trace_log: $!"); +- } ++ $self->assert($ok, test_msg("Missing expected TraceLog messages")); + +- if ($ex) { +- die($ex); ++ } else { ++ die("Can't read $trace_log: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; + } + +- unlink($log_file); ++ test_cleanup($setup->{log_file}, $ex); + } + +-sub traceoptions_timestamp_millis { ++sub traceoptions_no_timestamp { + my $self = shift; + my $tmpdir = $self->{tmpdir}; +- +- my $config_file = "$tmpdir/trace.conf"; +- my $pid_file = File::Spec->rel2abs("$tmpdir/trace.pid"); +- my $scoreboard_file = File::Spec->rel2abs("$tmpdir/trace.scoreboard"); +- +- my $log_file = File::Spec->rel2abs('tests.log'); +- +- my $auth_user_file = File::Spec->rel2abs("$tmpdir/trace.passwd"); +- my $auth_group_file = File::Spec->rel2abs("$tmpdir/trace.group"); +- +- my $user = 'proftpd'; +- my $passwd = 'test'; +- my $group = 'ftpd'; +- my $home_dir = File::Spec->rel2abs($tmpdir); +- my $uid = 500; +- my $gid = 500; +- +- # Make sure that, if we're running as root, that the home directory has +- # permissions/privs set for the account we create +- if ($< == 0) { +- unless (chmod(0755, $home_dir)) { +- die("Can't set perms on $home_dir to 0755: $!"); +- } +- +- unless (chown($uid, $gid, $home_dir)) { +- die("Can't set owner of $home_dir to $uid/$gid: $!"); +- } +- } +- +- auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, +- '/bin/bash'); +- auth_group_write($auth_group_file, $group, $gid, $user); ++ my $setup = test_setup($tmpdir, 'trace'); + + my $trace_log = File::Spec->rel2abs("$tmpdir/trace.log"); + + my $config = { +- PidFile => $pid_file, +- ScoreboardFile => $scoreboard_file, +- SystemLog => $log_file, ++ PidFile => $setup->{pid_file}, ++ ScoreboardFile => $setup->{scoreboard_file}, ++ SystemLog => $setup->{log_file}, + TraceLog => $trace_log, + Trace => 'DEFAULT:10', +- TraceOptions => '+TimestampMillis', ++ TraceOptions => '-Timestamp', + +- AuthUserFile => $auth_user_file, +- AuthGroupFile => $auth_group_file, ++ AuthUserFile => $setup->{auth_user_file}, ++ AuthGroupFile => $setup->{auth_group_file}, + + IfModules => { + 'mod_delay.c' => { +@@ -238,7 +297,8 @@ sub traceoptions_timestamp_millis { + }, + }; + +- my ($port, $config_user, $config_group) = config_write($config_file, $config); ++ my ($port, $config_user, $config_group) = config_write($setup->{config_file}, ++ $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message +@@ -256,10 +316,9 @@ sub traceoptions_timestamp_millis { + if ($pid) { + eval { + my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); +- $client->login($user, $passwd); ++ $client->login($setup->{user}, $setup->{passwd}); + $client->quit(); + }; +- + if ($@) { + $ex = $@; + } +@@ -268,7 +327,7 @@ sub traceoptions_timestamp_millis { + $wfh->flush(); + + } else { +- eval { server_wait($config_file, $rfh) }; ++ eval { server_wait($setup->{config_file}, $rfh) }; + if ($@) { + warn($@); + exit 1; +@@ -278,51 +337,55 @@ sub traceoptions_timestamp_millis { + } + + # Stop server +- server_stop($pid_file); +- ++ server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + +- if (open(my $fh, "< $trace_log")) { +- my $ok = 0; ++ eval { ++ if (open(my $fh, "< $trace_log")) { ++ my $ok = 0; + +- while (my $line = <$fh>) { +- chomp($line); ++ while (my $line = <$fh>) { ++ chomp($line); + +- my $expected = '\d+:\d+:\d+,\d+\s+\[\d+\]\s+<(\S+):(\d+)>: (.*?)$'; ++ if ($ENV{TEST_VERBOSE}) { ++ print STDERR "# $line\n"; ++ } + +- if ($line =~ /$expected/) { +- my $trace_channel = $1; +- my $trace_level = $2; +- my $trace_msg = $3; ++ my $expected = '\[\d+\]\s+<(\S+):(\d+)>: (.*?)$'; + +- next unless $trace_channel eq 'command'; ++ if ($line =~ /$expected/) { ++ my $trace_channel = $1; ++ my $trace_level = $2; ++ my $trace_msg = $3; + +- my $expected = 'command'; +- $self->assert($expected eq $trace_channel, +- test_msg("Expected '$expected', got '$trace_channel'")); ++ next unless $trace_channel eq 'command'; + +- $expected = 7; +- $self->assert($expected >= $trace_level, +- test_msg("Expected >= $expected, got $trace_level")); ++ my $expected = 'command'; ++ $self->assert($expected eq $trace_channel, ++ test_msg("Expected '$expected', got '$trace_channel'")); + +- $ok = 1; +- last; +- } +- } ++ $expected = 7; ++ $self->assert($expected >= $trace_level, ++ test_msg("Expected >= $expected, got $trace_level")); + +- close($fh); ++ $ok = 1; ++ last; ++ } ++ } + +- $self->assert($ok, test_msg("Missing expected TraceLog messages")); ++ close($fh); + +- } else { +- die("Can't read $trace_log: $!"); +- } ++ $self->assert($ok, test_msg("Missing expected TraceLog messages")); + +- if ($ex) { +- die($ex); ++ } else { ++ die("Can't read $trace_log: $!"); ++ } ++ }; ++ if ($@) { ++ $ex = $@; + } + +- unlink($log_file); ++ test_cleanup($setup->{log_file}, $ex); + } + + 1; diff --git a/proftpd.conf b/proftpd.conf index d96ebda..edb0f0c 100644 --- a/proftpd.conf +++ b/proftpd.conf @@ -114,6 +114,9 @@ UseSendfile off LogFormat default "%h %l %u %t \"%r\" %s %b" LogFormat auth "%v [%P] %h %t \"%r\" %s" +# Don't log hostname or timestamps because systemd will do that for us +LogOptions -Timestamp -Hostname +RoleBasedProcessLabels + # Enable basic controls via ftpdctl # (http://www.proftpd.org/docs/modules/mod_ctrls.html) ControlsEngine on diff --git a/proftpd.spec b/proftpd.spec index 34c8933..ed4ed1d 100644 --- a/proftpd.spec +++ b/proftpd.spec @@ -66,7 +66,7 @@ %undefine _strict_symbol_defs_build %global prever rc3 -%global baserelease 2 +%global baserelease 3 %global mod_vroot_version 0.9.5 Summary: Flexible, stable and highly-configurable FTP server @@ -93,6 +93,8 @@ Patch3: proftpd-1.3.4rc1-mod_vroot-test.patch Patch4: proftpd-1.3.6-no-mod-wrap.patch Patch5: proftpd-1.3.6-no-mod-geoip.patch Patch6: https://github.com/proftpd/proftpd/commit/f8047a1e.patch +Patch7: proftpd-1.3.7rc3-logoptions.patch +Patch8: proftpd-1.3.7rc3-logging-not-systemd.patch BuildRequires: coreutils BuildRequires: gcc @@ -305,16 +307,26 @@ mv contrib/README contrib/README.contrib # LogFormat functionality still works %patch6 -p1 +# Add options to vary logging format +# https://bugzilla.redhat.com/show_bug.cgi?id=1808989 +# http://bugs.proftpd.org/show_bug.cgi?id=4185 +# https://github.com/proftpd/proftpd/issues/1002 +# https://github.com/proftpd/proftpd/pull/1009 +%patch7 + # OpenSSL Cipher Profiles introduced in Fedora 21 # Elsewhere, we use the default of DEFAULT:!ADH:!EXPORT:!DES %if (0%{?rhel} && 0%{?rhel} <= 7) || (0%{?fedora} && 0%{?fedora} <= 20) sed -i -e '/^[[:space:]]*TLSCipherSuite[[:space:]]*PROFILE=SYSTEM$/d' mod_tls.conf %endif -# Tweak logrotate script for systemd compatibility (#802178) %if %{use_systemd} +# Tweak logrotate script for systemd compatibility (#802178) sed -i -e '/killall/s/test.*/systemctl reload proftpd.service/' \ contrib/dist/rpm/proftpd.logrotate +%else +# Not using systemd, so we want hostname and timestamp in log messages +%patch8 %endif # Avoid docfile dependencies @@ -590,6 +602,12 @@ fi %{_mandir}/man1/ftpwho.1* %changelog +* Sat May 9 2020 Paul Howarth - 1.3.7-0.3.rc3 +- Avoid duplicate hostname and timestamps in syslog (#1808989) + http://bugs.proftpd.org/show_bug.cgi?id=4185 + https://github.com/proftpd/proftpd/issues/1002 + https://github.com/proftpd/proftpd/pull/1009 + * Mon Apr 20 2020 Paul Howarth - 1.3.7-0.2.rc3 - Retain a memory pool after an aborted transfer so that the %%{transfer-status} LogFormat functionality still works