*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for writing log files. The code for maintaining datestamped
US"Negotiation failed for proxy configured host",
US"Authenticator 'other' failure",
US"target not supporting SMTPUTF8",
- US"",
+ US"host is local",
+ US"tainted filename",
US"Not time for routing",
US"Not time for local delivery",
US"Local-only delivery",
US"Domain in queue_domains",
US"Transport concurrency limit",
+ US"Event requests alternate response",
};
static void
write_syslog(int priority, const uschar *s)
{
-int len, pass;
+int len;
int linecount = 0;
if (!syslog_pid && LOGGING(pid))
/* First do a scan through the message in order to determine how many lines
it is going to end up as. Then rescan to output it. */
-for (pass = 0; pass < 2; pass++)
+for (int pass = 0; pass < 2; pass++)
{
- int i;
- int tlen;
const uschar * ss = s;
- for (i = 1, tlen = len; tlen > 0; i++)
+ for (int i = 1, tlen = len; tlen > 0; i++)
{
int plen = tlen;
uschar *nlptr = Ustrchr(ss, '\n');
}
if (f.receive_call_bombout) receive_bomb_out(NULL, s2); /* does not return */
if (smtp_input) smtp_closedown(s2);
-exim_exit(EXIT_FAILURE, NULL);
+exim_exit(EXIT_FAILURE);
}
Returns: a file descriptor, or < 0 on failure (errno set)
*/
-int
-log_create(uschar *name)
+static int
+log_open_already_exim(uschar * const name)
{
-int fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+int fd = -1;
+const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK;
+
+if (geteuid() != exim_uid)
+ {
+ errno = EACCES;
+ return -1;
+ }
+
+fd = Uopen(name, flags, LOG_MODE);
/* If creation failed, attempt to build a log directory in case that is the
problem. */
DEBUG(D_any) debug_printf("%s log directory %s\n",
created ? "created" : "failed to create", name);
*lastslash = '/';
- if (created) fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
+ if (created) fd = Uopen(name, flags, LOG_MODE);
}
return fd;
+/* Inspired by OpenSSH's mm_send_fd(). Thanks!
+Send fd over socketpair.
+Return: true iff good.
+*/
+
+static BOOL
+log_send_fd(const int sock, const int fd)
+{
+struct msghdr msg;
+union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+} cmsgbuf;
+struct cmsghdr *cmsg;
+char ch = 'A';
+struct iovec vec = {.iov_base = &ch, .iov_len = 1};
+ssize_t n;
+
+memset(&msg, 0, sizeof(msg));
+memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+msg.msg_control = &cmsgbuf.buf;
+msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+cmsg = CMSG_FIRSTHDR(&msg);
+cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+cmsg->cmsg_level = SOL_SOCKET;
+cmsg->cmsg_type = SCM_RIGHTS;
+*(int *)CMSG_DATA(cmsg) = fd;
+
+msg.msg_iov = &vec;
+msg.msg_iovlen = 1;
+
+while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR);
+return n == 1;
+}
+
+/* Inspired by OpenSSH's mm_receive_fd(). Thanks!
+Return fd passed over socketpair, or -1 on error.
+*/
+
+static int
+log_recv_fd(const int sock)
+{
+struct msghdr msg;
+union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+} cmsgbuf;
+struct cmsghdr *cmsg;
+char ch = '\0';
+struct iovec vec = {.iov_base = &ch, .iov_len = 1};
+ssize_t n;
+int fd;
+
+memset(&msg, 0, sizeof(msg));
+msg.msg_iov = &vec;
+msg.msg_iovlen = 1;
+
+memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+msg.msg_control = &cmsgbuf.buf;
+msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR) ;
+if (n != 1 || ch != 'A') return -1;
+
+if (!(cmsg = CMSG_FIRSTHDR(&msg))) return -1;
+if (cmsg->cmsg_type != SCM_RIGHTS) return -1;
+if ((fd = *(const int *)CMSG_DATA(cmsg)) < 0) return -1;
+return fd;
+}
+
+
+
/*************************************************
* Create a log file as the exim user *
*************************************************/
*/
int
-log_create_as_exim(uschar *name)
+log_open_as_exim(uschar * const name)
{
-pid_t pid = fork();
-int status = 1;
int fd = -1;
+const uid_t euid = geteuid();
-/* In the subprocess, change uid/gid and do the creation. Return 0 from the
-subprocess on success. If we don't check for setuid failures, then the file
-can be created as root, so vulnerabilities which cause setuid to fail mean
-that the Exim user can use symlinks to cause a file to be opened/created as
-root. We always open for append, so can't nuke existing content but it would
-still be Rather Bad. */
-
-if (pid == 0)
+if (euid == exim_uid)
+ fd = log_open_already_exim(name);
+else if (euid == root_uid)
{
- if (setgid(exim_gid) < 0)
- die(US"exim: setgid for log-file creation failed, aborting",
- US"Unexpected log failure, please try later");
- if (setuid(exim_uid) < 0)
- die(US"exim: setuid for log-file creation failed, aborting",
- US"Unexpected log failure, please try later");
- _exit((log_create(name) < 0)? 1 : 0);
- }
-
-/* If we created a subprocess, wait for it. If it succeeded, try the open. */
+ int sock[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0)
+ {
+ const pid_t pid = fork();
+ if (pid == 0)
+ {
+ (void)close(sock[0]);
+ if ( setgroups(1, &exim_gid) != 0
+ || setgid(exim_gid) != 0
+ || setuid(exim_uid) != 0
+
+ || getuid() != exim_uid || geteuid() != exim_uid
+ || getgid() != exim_gid || getegid() != exim_gid
+
+ || (fd = log_open_already_exim(name)) < 0
+ || !log_send_fd(sock[1], fd)
+ ) _exit(EXIT_FAILURE);
+ (void)close(sock[1]);
+ _exit(EXIT_SUCCESS);
+ }
-while (pid > 0 && waitpid(pid, &status, 0) != pid);
-if (status == 0) fd = Uopen(name,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_APPEND|O_WRONLY, LOG_MODE);
+ (void)close(sock[1]);
+ if (pid > 0)
+ {
+ fd = log_recv_fd(sock[0]);
+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
+ }
+ (void)close(sock[0]);
+ }
+ }
-/* If we failed to create a subprocess, we are in a bad way. We return
-with fd still < 0, and errno set, letting the caller handle the error. */
+if (fd >= 0)
+ {
+ int flags;
+ flags = fcntl(fd, F_GETFD);
+ if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ flags = fcntl(fd, F_GETFL);
+ if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+ }
+else
+ errno = EACCES;
return fd;
}
ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]);
-/* Save the name of the mainlog for rollover processing. Without a datestamp,
-it gets statted to see if it has been cycled. With a datestamp, the datestamp
-will be compared. The static slot for saving it is the same size as buffer,
-and the text has been checked above to fit, so this use of strcpy() is OK. */
-
-if (type == lt_main && string_datestamp_offset >= 0)
+switch (type)
{
- Ustrcpy(mainlog_name, buffer);
- mainlog_datestamp = mainlog_name + string_datestamp_offset;
- }
-
-/* Ditto for the reject log */
-
-else if (type == lt_reject && string_datestamp_offset >= 0)
- {
- Ustrcpy(rejectlog_name, buffer);
- rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
- }
-
-/* and deal with the debug log (which keeps the datestamp, but does not
-update it) */
+ case lt_main:
+ /* Save the name of the mainlog for rollover processing. Without a datestamp,
+ it gets statted to see if it has been cycled. With a datestamp, the datestamp
+ will be compared. The static slot for saving it is the same size as buffer,
+ and the text has been checked above to fit, so this use of strcpy() is OK. */
+ Ustrcpy(mainlog_name, buffer);
+ if (string_datestamp_offset > 0)
+ mainlog_datestamp = mainlog_name + string_datestamp_offset;
+ break;
-else if (type == lt_debug)
- {
- Ustrcpy(debuglog_name, buffer);
- if (tag)
- {
- /* this won't change the offset of the datestamp */
- ok2 = string_format(buffer, sizeof(buffer), "%s%s",
- debuglog_name, tag);
- if (ok2)
- Ustrcpy(debuglog_name, buffer);
- }
- }
+ case lt_reject:
+ /* Ditto for the reject log */
+ Ustrcpy(rejectlog_name, buffer);
+ if (string_datestamp_offset > 0)
+ rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
+ break;
-/* Remove any datestamp if this is the panic log. This is rare, so there's no
-need to optimize getting the datestamp length. We remove one non-alphanumeric
-char afterwards if at the start, otherwise one before. */
+ case lt_debug:
+ /* and deal with the debug log (which keeps the datestamp, but does not
+ update it) */
+ Ustrcpy(debuglog_name, buffer);
+ if (tag)
+ {
+ /* this won't change the offset of the datestamp */
+ ok2 = string_format(buffer, sizeof(buffer), "%s%s",
+ debuglog_name, tag);
+ if (ok2)
+ Ustrcpy(debuglog_name, buffer);
+ }
+ break;
-else if (string_datestamp_offset >= 0)
- {
- uschar * from = buffer + string_datestamp_offset;
- uschar * to = from + string_datestamp_length;
+ default:
+ /* Remove any datestamp if this is the panic log. This is rare, so there's no
+ need to optimize getting the datestamp length. We remove one non-alphanumeric
+ char afterwards if at the start, otherwise one before. */
+ if (string_datestamp_offset >= 0)
+ {
+ uschar * from = buffer + string_datestamp_offset;
+ uschar * to = from + string_datestamp_length;
- if (from == buffer || from[-1] == '/')
- {
- if (!isalnum(*to)) to++;
- }
- else
- if (!isalnum(from[-1])) from--;
+ if (from == buffer || from[-1] == '/')
+ {
+ if (!isalnum(*to)) to++;
+ }
+ else
+ if (!isalnum(from[-1])) from--;
- /* This copy is ok, because we know that to is a substring of from. But
- due to overlap we must use memmove() not Ustrcpy(). */
- memmove(from, to, Ustrlen(to)+1);
+ /* This copy is ok, because we know that to is a substring of from. But
+ due to overlap we must use memmove() not Ustrcpy(). */
+ memmove(from, to, Ustrlen(to)+1);
+ }
+ break;
}
/* If the file name is too long, it is an unrecoverable disaster */
die(US"exim: log file path too long: aborting",
US"Logging failure; please try later");
-/* We now have the file name. Try to open an existing file. After a successful
-open, arrange for automatic closure on exec(), and then return. */
-
-*fd = Uopen(buffer,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_APPEND|O_WRONLY, LOG_MODE);
+/* We now have the file name. After a successful open, return. */
-if (*fd >= 0)
- {
-#ifndef O_CLOEXEC
- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
-#endif
+if ((*fd = log_open_as_exim(buffer)) >= 0)
return;
- }
-
-/* Open was not successful: try creating the file. If this is a root process,
-we must do the creating in a subprocess set to exim:exim in order to ensure
-that the file is created with the right ownership. Otherwise, there can be a
-race if another Exim process is trying to write to the log at the same time.
-The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous
-writing. */
euid = geteuid();
-/* If we are already running as the Exim user (even if that user is root),
-we can go ahead and create in the current process. */
-
-if (euid == exim_uid) *fd = log_create(buffer);
-
-/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we
-are neither exim nor root, creation is not attempted. */
-
-else if (euid == root_uid) *fd = log_create_as_exim(buffer);
-
-/* If we now have an open file, set the close-on-exec flag and return. */
-
-if (*fd >= 0)
- {
-#ifndef O_CLOEXEC
- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC);
-#endif
- return;
- }
-
/* Creation failed. There are some circumstances in which we get here when
the effective uid is not root or exim, which is the problem. (For example, a
non-setuid binary with log_arguments set, called in certain ways.) Rather than
just bombing out, force the log to stderr and carry on if stderr is available.
*/
-if (euid != root_uid && euid != exim_uid && log_stderr != NULL)
+if (euid != root_uid && euid != exim_uid && log_stderr)
{
*fd = fileno(log_stderr);
return;
/* Otherwise this is a disaster. This call is deliberately ONLY to the panic
log. If possible, save a copy of the original line that was being logged. If we
are recursing (can't open the panic log either), the pointer will already be
-set. */
+set. Also, when we had to use a subprocess for the create we didn't retrieve
+errno from it, so get the error from the open attempt above (which is often
+meaningful enough, so leave it). */
if (!panic_save_buffer)
if ((panic_save_buffer = US malloc(LOG_BUFFER_SIZE)))
Returns: updated pointer
*/
-static uschar *
-log_config_info(uschar *ptr, int flags)
+static gstring *
+log_config_info(gstring * g, int flags)
{
-Ustrcpy(ptr, "Exim configuration error");
-ptr += 24;
+g = string_cat(g, US"Exim configuration error");
-if ((flags & (LOG_CONFIG_FOR & ~LOG_CONFIG)) != 0)
- {
- Ustrcpy(ptr, " for ");
- return ptr + 5;
- }
+if (flags & (LOG_CONFIG_FOR & ~LOG_CONFIG))
+ return string_cat(g, US" for ");
-if ((flags & (LOG_CONFIG_IN & ~LOG_CONFIG)) != 0)
- ptr += sprintf(CS ptr, " in line %d of %s", config_lineno, config_filename);
+if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
+ g = string_fmt_append(g, " in line %d of %s", config_lineno, config_filename);
-Ustrcpy(ptr, ":\n ");
-return ptr + 4;
+return string_catn(g, US":\n ", 4);
}
void
log_write(unsigned int selector, int flags, const char *format, ...)
{
-uschar * ptr;
-int length;
int paniclogfd;
ssize_t written_len;
+gstring gs = { .size = LOG_BUFFER_SIZE-1, .ptr = 0, .s = log_buffer };
+gstring * g;
va_list ap;
/* If panic_recurseflag is set, we have failed to open the panic log. This is
if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
{
fprintf(stderr, "exim: failed to get store for log buffer\n");
- exim_exit(EXIT_FAILURE, NULL);
+ exim_exit(EXIT_FAILURE);
}
/* If we haven't already done so, inspect the setting of log_file_path to
"More than one path given in log_file_path: using %s", file_path);
}
+/* Optionally trigger debug */
+
+if (flags & LOG_PANIC && dtrigger_selector & BIT(DTi_panictrigger))
+ debug_trigger_fire();
+
/* If debugging, show all log entries, but don't show headers. Do it all
in one go so that it doesn't get split when multi-processing. */
DEBUG(D_any|D_v)
{
int i;
- ptr = log_buffer;
- Ustrcpy(ptr, "LOG:");
- ptr += 4;
+ g = string_catn(&gs, US"LOG:", 4);
/* Show the selector that was passed into the call. */
{
unsigned int bitnum = log_options[i].bit;
if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
- {
- *ptr++ = ' ';
- Ustrcpy(ptr, log_options[i].name);
- while (*ptr) ptr++;
- }
+ g = string_fmt_append(g, " %s", log_options[i].name);
}
- ptr += sprintf(CS ptr, "%s%s%s%s\n ",
+ g = string_fmt_append(g, "%s%s%s%s\n ",
flags & LOG_MAIN ? " MAIN" : "",
flags & LOG_PANIC ? " PANIC" : "",
(flags & LOG_PANIC_DIE) == LOG_PANIC_DIE ? " DIE" : "",
flags & LOG_REJECT ? " REJECT" : "");
- if (flags & LOG_CONFIG) ptr = log_config_info(ptr, flags);
+ if (flags & LOG_CONFIG) g = log_config_info(g, flags);
+
+ /* We want to be able to log tainted info, but log_buffer is directly
+ malloc'd. So use deliberately taint-nonchecking routines to build into
+ it, trusting that we will never expand the results. */
va_start(ap, format);
- if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
- Ustrcpy(ptr, "**** log string overflowed log buffer ****");
+ i = g->ptr;
+ if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
+ {
+ g->ptr = i;
+ g = string_cat(g, US"**** log string overflowed log buffer ****");
+ }
va_end(ap);
- while(*ptr) ptr++;
- Ustrcat(ptr, "\n");
- debug_printf("%s", log_buffer);
- }
+ g->size = LOG_BUFFER_SIZE;
+ g = string_catn(g, US"\n", 1);
+ debug_printf("%s", string_from_gstring(g));
+ gs.size = LOG_BUFFER_SIZE-1; /* Having used the buffer for debug output, */
+ gs.ptr = 0; /* reset it for the real use. */
+ gs.s = log_buffer;
+ }
/* If no log file is specified, we are in a mess. */
if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
if (f.disable_logging)
{
DEBUG(D_any) debug_printf("log writing disabled\n");
+ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE);
return;
}
/* Create the main message in the log buffer. Do not include the message id
when called by a utility. */
-ptr = log_buffer;
-ptr += sprintf(CS ptr, "%s ", tod_stamp(tod_log));
+g = string_fmt_append(&gs, "%s ", tod_stamp(tod_log));
if (LOGGING(pid))
{
- if (!syslog_pid) pid_position[0] = ptr - log_buffer; /* remember begin … */
- ptr += sprintf(CS ptr, "[%d] ", (int)getpid());
- if (!syslog_pid) pid_position[1] = ptr - log_buffer; /* … and end+1 of the PID */
+ if (!syslog_pid) pid_position[0] = g->ptr; /* remember begin … */
+ g = string_fmt_append(g, "[%d] ", (int)getpid());
+ if (!syslog_pid) pid_position[1] = g->ptr; /* … and end+1 of the PID */
}
if (f.really_exim && message_id[0] != 0)
- ptr += sprintf(CS ptr, "%s ", message_id);
+ g = string_fmt_append(g, "%s ", message_id);
-if (flags & LOG_CONFIG) ptr = log_config_info(ptr, flags);
+if (flags & LOG_CONFIG)
+ g = log_config_info(g, flags);
va_start(ap, format);
-if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
- Ustrcpy(ptr, "**** log string overflowed log buffer ****\n");
-while(*ptr) ptr++;
+ {
+ int i = g->ptr;
+
+ /* We want to be able to log tainted info, but log_buffer is directly
+ malloc'd. So use deliberately taint-nonchecking routines to build into
+ it, trusting that we will never expand the results. */
+
+ if (!string_vformat(g, SVFMT_TAINT_NOCHK, format, ap))
+ {
+ g->ptr = i;
+ g = string_cat(g, US"**** log string overflowed log buffer ****\n");
+ }
+ }
va_end(ap);
/* Add the raw, unrewritten, sender to the message if required. This is done
this way because it kind of fits with LOG_RECIPIENTS. */
if ( flags & LOG_SENDER
- && ptr < log_buffer + LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
- ptr += sprintf(CS ptr, " from <%s>", raw_sender);
+ && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
+ g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " from <%s>", raw_sender);
/* Add list of recipients to the message if required; the raw list,
before rewriting, was saved in raw_recipients. There may be none, if an ACL
discarded them all. */
if ( flags & LOG_RECIPIENTS
- && ptr < log_buffer + LOG_BUFFER_SIZE - 6
+ && g->ptr < LOG_BUFFER_SIZE - 6
&& raw_recipients_count > 0)
{
int i;
- ptr += sprintf(CS ptr, " for");
+ g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " for", NULL);
for (i = 0; i < raw_recipients_count; i++)
{
uschar * s = raw_recipients[i];
- if (log_buffer + LOG_BUFFER_SIZE - ptr < Ustrlen(s) + 3) break;
- ptr += sprintf(CS ptr, " %s", s);
+ if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
+ g = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " %s", s);
}
}
-ptr += sprintf(CS ptr, "\n");
-length = ptr - log_buffer;
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
/* Handle loggable errors when running a utility, or when address testing.
Write to log_stderr unless debugging (when it will already have been written),
else
fprintf(log_stderr, "%s", CS log_buffer);
- if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, US"");
+ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE);
return;
}
/* Failing to write to the log is disastrous */
- written_len = write_to_fd_buf(mainlogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(mainlogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
- log_write_failed(US"main log", length, written_len);
+ log_write_failed(US"main log", g->ptr, written_len);
/* That function does not return */
}
}
if (flags & LOG_REJECT)
{
- header_line *h;
-
if (header_list && LOGGING(rejected_header))
{
+ gstring * g2;
+ int i;
+
if (recipients_count > 0)
{
- int i;
-
/* List the sender */
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
- "Envelope-from: <%s>\n", sender_address);
- while (*ptr) ptr++;
+ g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+ "Envelope-from: <%s>\n", sender_address);
+ if (g2) g = g2;
/* List up to 5 recipients */
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
- "Envelope-to: <%s>\n", recipients_list[0].address);
- while (*ptr) ptr++;
+ g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+ "Envelope-to: <%s>\n", recipients_list[0].address);
+ if (g2) g = g2;
for (i = 1; i < recipients_count && i < 5; i++)
{
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer), " <%s>\n",
- recipients_list[i].address);
- while (*ptr) ptr++;
+ g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+ " <%s>\n", recipients_list[i].address);
+ if (g2) g = g2;
}
if (i < recipients_count)
{
- (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
- " ...\n");
- while (*ptr) ptr++;
+ g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK, " ...\n", NULL);
+ if (g2) g = g2;
}
}
/* A header with a NULL text is an unfilled in Received: header */
- for (h = header_list; h; h = h->next) if (h->text)
+ for (header_line * h = header_list; h; h = h->next) if (h->text)
{
- BOOL fitted = string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
- "%c %s", h->type, h->text);
- while(*ptr) ptr++;
- if (!fitted) /* Buffer is full; truncate */
+ g2 = string_fmt_append_f(g, SVFMT_TAINT_NOCHK,
+ "%c %s", h->type, h->text);
+ if (g2)
+ g = g2;
+ else /* Buffer is full; truncate */
{
- ptr -= 100; /* For message and separator */
- if (ptr[-1] == '\n') ptr--;
- Ustrcpy(ptr, "\n*** truncated ***\n");
- while (*ptr) ptr++;
+ g->ptr -= 100; /* For message and separator */
+ if (g->s[g->ptr-1] == '\n') g->ptr--;
+ g = string_cat(g, US"\n*** truncated ***\n");
break;
}
}
-
- length = ptr - log_buffer;
}
/* Write to syslog or to a log file */
if ( logging_mode & LOG_MODE_SYSLOG
&& (syslog_duplication || !(flags & LOG_PANIC)))
- write_syslog(LOG_NOTICE, log_buffer);
+ write_syslog(LOG_NOTICE, string_from_gstring(g));
/* Check for a change to the rejectlog file name when datestamping is in
operation. This happens at midnight, at which point we want to roll over
if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
}
- written_len = write_to_fd_buf(rejectlogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(rejectlogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
- log_write_failed(US"reject log", length, written_len);
+ log_write_failed(US"reject log", g->ptr, written_len);
/* That function does not return */
}
}
if (flags & LOG_PANIC)
{
if (log_stderr && log_stderr != debug_file && !mua_wrapper)
- fprintf(log_stderr, "%s", CS log_buffer);
+ fprintf(log_stderr, "%s", CS string_from_gstring(g));
if (logging_mode & LOG_MODE_SYSLOG)
write_syslog(LOG_ALERT, log_buffer);
panic_recurseflag = FALSE;
if (panic_save_buffer)
- {
- int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
- i = i; /* compiler quietening */
- }
+ (void) write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
- written_len = write_to_fd_buf(paniclogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
int save_errno = errno;
write_syslog(LOG_CRIT, log_buffer);
sprintf(CS log_buffer, "write failed on panic log: length=%d result=%d "
- "errno=%d (%s)", length, (int)written_len, save_errno, strerror(save_errno));
- write_syslog(LOG_CRIT, log_buffer);
+ "errno=%d (%s)", g->ptr, (int)written_len, save_errno, strerror(save_errno));
+ write_syslog(LOG_CRIT, string_from_gstring(g));
flags |= LOG_PANIC_DIE;
}
uschar *string, bit_table *options, int count, uschar *which, int flags)
{
uschar *errmsg;
-if (string == NULL) return;
+if (!string) return;
if (*string == '=')
{
char *end; /* Not uschar */
memset(selector, 0, sizeof(*selector)*selsize);
*selector = strtoul(CS string+1, &end, 0);
- if (*end == 0) return;
+ if (!*end) return;
errmsg = string_sprintf("malformed numeric %s_selector setting: %s", which,
string);
goto ERROR_RETURN;
int len;
bit_table *start, *end;
- while (isspace(*string)) string++;
- if (*string == 0) return;
+ Uskip_whitespace(&string);
+ if (!*string) return;
if (*string != '+' && *string != '-')
{
bit_table *middle = start + (end - start)/2;
int c = Ustrncmp(s, middle->name, len);
if (c == 0)
- {
if (middle->name[len] != 0) c = -1; else
{
unsigned int bit = middle->bit;
break; /* Out of loop to match selector name */
}
- }
if (c < 0) end = middle; else start = middle + 1;
} /* Loop to match selector name */
The first use of this is in ACL logic, "control = debug/tag=foo/opts=+expand"
which can be combined with conditions, etc, to activate extra logging only
-for certain sources. The second use is inetd wait mode debug preservation. */
+for certain sources. The second use is inetd wait mode debug preservation.
+
+It might be nice, in ACL-initiated pretrigger mode, to not create the file
+immediately but only upon a trigger - but we'd need another cmdline option
+to pass the name through child_exxec_exim(). */
void
debug_logging_activate(uschar *tag_name, uschar *opts)
{
-int fd = -1;
-
if (debug_file)
{
debug_printf("DEBUGGING ACTIVATED FROM WITHIN CONFIG.\n"
return;
}
-if (tag_name != NULL && (Ustrchr(tag_name, '/') != NULL))
+if (tag_name && (Ustrchr(tag_name, '/') != NULL))
{
log_write(0, LOG_MAIN|LOG_PANIC, "debug tag may not contain a '/' in: %s",
tag_name);
if (!*file_path) set_file_path();
-open_log(&fd, lt_debug, tag_name);
+open_log(&debug_fd, lt_debug, tag_name);
-if (fd != -1)
- debug_file = fdopen(fd, "w");
+if (debug_fd != -1)
+ debug_file = fdopen(debug_fd, "w");
else
log_write(0, LOG_MAIN|LOG_PANIC, "unable to open debug log");
}
void
-debug_logging_stop(void)
+debug_logging_stop(BOOL kill)
{
+debug_pretrigger_discard();
if (!debug_file || !debuglog_name[0]) return;
debug_selector = 0;
fclose(debug_file);
debug_file = NULL;
-unlink_log(lt_debug);
+debug_fd = -1;
+if (kill) unlink_log(lt_debug);
}