/* * Logging and utility functions. * * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org> * Copyright (C) 2000-2001 Martin Pool <mbp@samba.org> * Copyright (C) 2003-2022 Wayne Davison * * 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 3 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, visit the http://fsf.org website. */ #include "rsync.h"#include "itypes.h"#include "inums.h"#include "rounding.h" /* EXTRA_ROUNDING, so log_delete() aligns its file_struct */ extern int dry_run;extern int am_daemon;extern int am_server;extern int am_sender;extern int am_generator;extern int local_server;extern int quiet;extern int module_id;extern int allow_8bit_chars;extern int protocol_version;extern int always_checksum;extern int preserve_mtimes;extern int msgs2stderr;extern int stdout_format_has_i;extern int stdout_format_has_o_or_i;extern int logfile_format_has_i;extern int logfile_format_has_o_or_i;extern int receiver_symlink_times;extern int64 total_data_written;extern int64 total_data_read;extern mode_t orig_umask;extern char *auth_user;extern char *stdout_format;extern char *logfile_format;extern char *logfile_name;#ifdef ICONV_CONSTextern iconv_t ic_chck;#endif#ifdef ICONV_OPTIONextern iconv_t ic_recv;#endifextern char curr_dir[MAXPATHLEN];extern char *full_module_path;extern unsigned int module_dirlen;extern char sender_file_sum[MAX_DIGEST_LEN];extern const char undetermined_hostname[]; extern struct name_num_item *xfer_sum_nni, *file_sum_nni; static int log_initialised;static int logfile_was_closed;static FILE *logfile_fp;struct stats stats; int got_xfer_error = 0;int output_needs_newline = 0;int send_msgs_to_gen = 0; static int64 initial_data_written;static int64 initial_data_read; struct { int code; char const *name;} const rerr_names[] = { { RERR_SYNTAX , "syntax or usage error" }, { RERR_PROTOCOL , "protocol incompatibility" }, { RERR_FILESELECT , "errors selecting input/output files, dirs" }, { RERR_UNSUPPORTED, "requested action not supported" }, { RERR_STARTCLIENT, "error starting client-server protocol" }, { RERR_SOCKETIO , "error in socket IO" }, { RERR_FILEIO , "error in file IO" }, { RERR_STREAMIO , "error in rsync protocol data stream" }, { RERR_MESSAGEIO , "errors with program diagnostics" }, { RERR_IPC , "error in IPC code" }, { RERR_CRASHED , "sibling process crashed" }, { RERR_TERMINATED , "sibling process terminated abnormally" }, { RERR_SIGNAL1 , "received SIGUSR1" }, { RERR_SIGNAL , "received SIGINT, SIGTERM, or SIGHUP" }, { RERR_WAITCHILD , "waitpid() failed" }, { RERR_MALLOC , "error allocating core memory buffers" }, { RERR_PARTIAL , "some files/attrs were not transferred (see previous errors)" }, { RERR_VANISHED , "some files vanished before they could be transferred" }, { RERR_DEL_LIMIT , "the --max-delete limit stopped deletions" }, { RERR_TIMEOUT , "timeout in data send/receive" }, { RERR_CONTIMEOUT , "timeout waiting for daemon connection" }, { RERR_CMD_FAILED , "remote shell failed" }, { RERR_CMD_KILLED , "remote shell killed" }, { RERR_CMD_RUN , "remote command could not be run" }, { RERR_CMD_NOTFOUND,"remote command not found" }, { 0, NULL }}; /* * Map from rsync error code to name, or return NULL. */static char const *rerr_name(int code){ int i; for (i = 0; rerr_names[i].name; i++) { if (rerr_names[i].code == code) return rerr_names[i].name; } return NULL;} static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char); static void logit(int priority, const char *buf){ if (logfile_was_closed) logfile_reopen(); if (logfile_fp) { /* Escape control chars in the message so an attacker-controlled * filename can't inject terminal escapes into the log an admin later * cat's (CWE-117); keep the trailing newline raw via end_char. */ int len = strlen(buf); char trailing = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0'; fprintf(logfile_fp, "%s [%d] ", timestring(time(NULL)), (int)getpid()); filtered_fwrite(logfile_fp, buf, len, 0, 1, trailing); fflush(logfile_fp); } else { syslog(priority, "%s", buf); }} static void syslog_init(){ int options = LOG_PID; #ifdef LOG_NDELAY options |= LOG_NDELAY;#endif #ifdef LOG_DAEMON openlog(lp_syslog_tag(module_id), options, lp_syslog_facility(module_id));#else openlog(lp_syslog_tag(module_id), options);#endif #ifndef LOG_NDELAY logit(LOG_INFO, "rsyncd started\n");#endif} static void logfile_open(void){ mode_t old_umask = umask(022 | orig_umask); /* --log-file/`log file =` are operator-supplied paths that may transit * attacker-writable dirs; a planted symlink could redirect root's log * into e.g. /root/.ssh/authorized_keys. Refuse symlinks not owned by * uid 0 or our euid. */ int fd = open_no_attacker_symlinks(logfile_name, O_WRONLY | O_APPEND | O_CREAT, 0644); logfile_fp = fd >= 0 ? fdopen(fd, "a") : NULL; if (!logfile_fp && fd >= 0) close(fd); umask(old_umask); if (!logfile_fp) { int fopen_errno = errno; /* Rsync falls back to using syslog on failure. */ syslog_init(); rsyserr(FERROR, fopen_errno, "failed to open log-file %s", logfile_name); rprintf(FINFO, "Ignoring \"log file\" setting.\n"); logfile_name = ""; }} void log_init(int restart){ if (log_initialised) { if (!restart) /* Note: a restart only happens with am_daemon */ return; assert(logfile_name); /* all am_daemon procs got at least an empty string */ if (strcmp(logfile_name, lp_log_file(module_id)) != 0) { if (logfile_fp) { fclose(logfile_fp); logfile_fp = NULL; } else closelog(); logfile_name = NULL; } else if (*logfile_name) return; /* unchanged, non-empty "log file" names */ else if (lp_syslog_facility(-1) != lp_syslog_facility(module_id) || strcmp(lp_syslog_tag(-1), lp_syslog_tag(module_id)) != 0) closelog(); else return; /* unchanged syslog settings */ } else log_initialised = 1; /* This looks pointless, but it is needed in order for the * C library on some systems to fetch the timezone info * before the chroot. */ timestring(time(NULL)); /* Optionally use a log file instead of syslog. (Non-daemon * rsyncs will have already set logfile_name, as needed.) */ if (am_daemon && !logfile_name) logfile_name = lp_log_file(module_id); if (logfile_name && *logfile_name) logfile_open(); else syslog_init();} /* Note that this close & reopen idiom intentionally ignores syslog logging. */void logfile_close(void){ if (logfile_fp) { logfile_was_closed = 1; fclose(logfile_fp); logfile_fp = NULL; }} void logfile_reopen(void){ if (logfile_was_closed) { logfile_was_closed = 0; logfile_open(); }} static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char){ char outbuf[1024], *ob = outbuf; const char *end = in_buf + in_len; while (in_buf < end) { if (ob - outbuf >= (int)sizeof outbuf - 10) { if (fwrite(outbuf, ob - outbuf, 1, f) != 1) exit_cleanup(RERR_MESSAGEIO); ob = outbuf; } if ((in_buf < end - 4 && *in_buf == '\\' && in_buf[1] == '#' && isDigit(in_buf + 2) && isDigit(in_buf + 3) && isDigit(in_buf + 4)) || (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' ' || (escape_c1 && *(uchar*)in_buf >= 0x80 && *(uchar*)in_buf <= 0x9f)))) ob += snprintf(ob, 6, "\\#%03o", *(uchar*)in_buf++); else *ob++ = *in_buf++; } if (end_char) /* The "- 10" above means that there is always room for one more char here. */ *ob++ = end_char; if (ob != outbuf && fwrite(outbuf, ob - outbuf, 1, f) != 1) exit_cleanup(RERR_MESSAGEIO);} /* this is the underlying (unformatted) rsync debugging function. Call * it with FINFO, FERROR_*, FWARNING, FLOG, or FCLIENT. Note: recursion * can happen with certain fatal conditions. */void rwrite(enum logcode code, const char *buf, int len, int is_utf8){ char trailing_CR_or_NL; FILE *f = msgs2stderr == 1 ? stderr : stdout;#ifdef ICONV_OPTION iconv_t ic = is_utf8 && ic_recv != (iconv_t)-1 ? ic_recv : ic_chck;#else#ifdef ICONV_CONST iconv_t ic = ic_chck;#endif#endif if (len < 0) exit_cleanup(RERR_MESSAGEIO); if (msgs2stderr == 1) { /* A normal daemon can get msgs2stderr set if the socket is busted, so we * change the message destination into an FLOG message in order to try to * get some info about an abnormal-exit into the log file. An rsh daemon * can have this set via user request, so we'll leave the code alone so * that the msg gets logged and then sent to stderr after that. */ if (am_daemon > 0 && code != FCLIENT) code = FLOG; } else if (send_msgs_to_gen) { /* Pass the message to our sibling in native charset. is_utf8 * may be set here if a malicious peer sends MSG_INFO/MSG_ERROR * to a daemon receiver (read_a_msg passes !am_generator); the * old assert(!is_utf8) made that a remotely-reachable abort. * Forwarding the bytes raw is safe -- the generator's rwrite() * gets is_utf8=0 and filtered_fwrite escapes non-printables. */ send_msg((enum msgcode)code, buf, len, 0); return; } if (code == FERROR_SOCKET) /* This gets simplified for a non-sibling. */ code = FERROR; else if (code == FERROR_UTF8) { is_utf8 = 1; code = FERROR; } if (code == FCLIENT) code = FINFO; else if (am_daemon || logfile_name) { static int in_block; char msg[2048]; int priority = code == FINFO || code == FLOG ? LOG_INFO : LOG_WARNING; if (in_block) return; in_block = 1; if (!log_initialised) log_init(0); /* buf holds exactly len bytes and is not necessarily NUL-terminated * (e.g. a forwarded MSG_* payload from read_a_msg), so copy by length * rather than strlcpy(), which would strlen() past the end of buf. */ int mlen = MIN((int)sizeof msg - 1, len); memcpy(msg, buf, mlen); msg[mlen] = '\0'; logit(priority, msg); in_block = 0; if (code == FLOG || (am_daemon && !am_server)) return; } else if (code == FLOG) return; switch (code) { case FERROR_XFER: got_xfer_error = 1; /* FALL THROUGH */ case FERROR: case FWARNING: f = stderr; break; case FINFO: if (quiet) return; break; /*case FLOG:*/ /*case FCLIENT:*/ /*case FERROR_UTF8:*/ /*case FERROR_SOCKET:*/ default: fprintf(stderr, "Bad logcode in rwrite(): %d [%s]\n", (int)code, who_am_i()); exit_cleanup(RERR_MESSAGEIO); } if (am_server && msgs2stderr != 1 && (msgs2stderr != 2 || f != stderr)) { enum msgcode msg = (enum msgcode)code; if (protocol_version < 30) { if (msg == MSG_ERROR) msg = MSG_ERROR_XFER; else if (msg == MSG_WARNING) msg = MSG_INFO; } /* Pass the message to the non-server side. */ if (send_msg(msg, buf, len, !is_utf8)) return; if (am_daemon > 0) { /* TODO: can we send the error to the user somehow? */ return; } f = stderr; } if (output_needs_newline) { fputc('\n', f); output_needs_newline = 0; } trailing_CR_or_NL = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0'; if (len && buf[0] == '\r') { fputc('\r', f); buf++; len--; } #ifdef ICONV_CONST if (ic != (iconv_t)-1) { xbuf outbuf, inbuf; char convbuf[1024]; int ierrno; INIT_CONST_XBUF(outbuf, convbuf); INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1); while (inbuf.len) { iconvbufs(ic, &inbuf, &outbuf, inbuf.pos ? 0 : ICB_INIT); ierrno = errno; if (outbuf.len) { char trailing = inbuf.len ? '\0' : trailing_CR_or_NL; filtered_fwrite(f, convbuf, outbuf.len, 0, 0, trailing); if (trailing) { trailing_CR_or_NL = '\0'; fflush(f); } outbuf.len = 0; } /* Log one byte of illegal/incomplete sequence and continue with * the next character. Check that the buffer is non-empty for the * sake of robustness. */ if ((ierrno == EILSEQ || ierrno == EINVAL) && inbuf.len) { fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++)); inbuf.len--; } } if (trailing_CR_or_NL) { fputc(trailing_CR_or_NL, f); fflush(f); } } else#endif { filtered_fwrite(f, buf, len, !allow_8bit_chars, 0, trailing_CR_or_NL); if (trailing_CR_or_NL) fflush(f); }} /* This is the rsync debugging function. Call it with FINFO, FERROR_*, * FWARNING, FLOG, or FCLIENT. */void rprintf(enum logcode code, const char *format, ...){ va_list ap; char buf[BIGPATHBUFLEN]; size_t len; va_start(ap, format); len = vsnprintf(buf, sizeof buf, format, ap); va_end(ap); /* Deal with buffer overruns. Instead of panicking, just * truncate the resulting string. (Note that configure ensures * that we have a vsnprintf() that doesn't ever return -1.) */ if (len > sizeof buf - 1) { static const char ellipsis[] = "[...]"; /* Reset length, and zero-terminate the end of our buffer */ len = sizeof buf - 1; buf[len] = '\0'; /* Copy the ellipsis to the end of the string, but give * us one extra character: * * v--- null byte at buf[sizeof buf - 1] * abcdefghij0 * -> abcd[...]00 <-- now two null bytes at end * * If the input format string has a trailing newline, * we copy it into that extra null; if it doesn't, well, * all we lose is one byte. */ memcpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis); if (format[strlen(format)-1] == '\n') { buf[len-1] = '\n'; } } rwrite(code, buf, len, 0);} /* This is like rprintf, but it also tries to print some * representation of the error code. Normally errcode = errno. * * Unlike rprintf, this always adds a newline and there should not be * one in the format string. * * Note that since strerror might involve dynamically loading a * message catalog we need to call it once before chroot-ing. */void rsyserr(enum logcode code, int errcode, const char *format, ...){ va_list ap; char buf[BIGPATHBUFLEN]; size_t len; /* snprintf returns the would-have-been length on truncation, so * each cumulative call must be guarded; if not, sizeof buf - len * can underflow when promoted to size_t and the next call writes * past the buffer. */ len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i()); if (len < sizeof buf) { va_start(ap, format); len += vsnprintf(buf + len, sizeof buf - len, format, ap); va_end(ap); } if (len < sizeof buf) { len += snprintf(buf + len, sizeof buf - len, ": %s (%d)\n", strerror(errcode), errcode);Lines 1–500 of 963. Load more
/* * Logging and utility functions. * * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org> * Copyright (C) 2000-2001 Martin Pool <mbp@samba.org> * Copyright (C) 2003-2022 Wayne Davison * * 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 3 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, visit the http://fsf.org website. */ #include "rsync.h" #include "itypes.h" #include "inums.h" #include "rounding.h" /* EXTRA_ROUNDING, so log_delete() aligns its file_struct */ extern int dry_run; extern int am_daemon; extern int am_server; extern int am_sender; extern int am_generator; extern int local_server; extern int quiet; extern int module_id; extern int allow_8bit_chars; extern int protocol_version; extern int always_checksum; extern int preserve_mtimes; extern int msgs2stderr; extern int stdout_format_has_i; extern int stdout_format_has_o_or_i; extern int logfile_format_has_i; extern int logfile_format_has_o_or_i; extern int receiver_symlink_times; extern int64 total_data_written; extern int64 total_data_read; extern mode_t orig_umask; extern char *auth_user; extern char *stdout_format; extern char *logfile_format; extern char *logfile_name; #ifdef ICONV_CONST extern iconv_t ic_chck; #endif #ifdef ICONV_OPTION extern iconv_t ic_recv; #endif extern char curr_dir[MAXPATHLEN]; extern char *full_module_path; extern unsigned int module_dirlen; extern char sender_file_sum[MAX_DIGEST_LEN]; extern const char undetermined_hostname[]; extern struct name_num_item *xfer_sum_nni, *file_sum_nni; static int log_initialised; static int logfile_was_closed; static FILE *logfile_fp; struct stats stats; int got_xfer_error = 0; int output_needs_newline = 0; int send_msgs_to_gen = 0; static int64 initial_data_written; static int64 initial_data_read; struct { int code; char const *name; } const rerr_names[] = { { RERR_SYNTAX , "syntax or usage error" }, { RERR_PROTOCOL , "protocol incompatibility" }, { RERR_FILESELECT , "errors selecting input/output files, dirs" }, { RERR_UNSUPPORTED, "requested action not supported" }, { RERR_STARTCLIENT, "error starting client-server protocol" }, { RERR_SOCKETIO , "error in socket IO" }, { RERR_FILEIO , "error in file IO" }, { RERR_STREAMIO , "error in rsync protocol data stream" }, { RERR_MESSAGEIO , "errors with program diagnostics" }, { RERR_IPC , "error in IPC code" }, { RERR_CRASHED , "sibling process crashed" }, { RERR_TERMINATED , "sibling process terminated abnormally" }, { RERR_SIGNAL1 , "received SIGUSR1" }, { RERR_SIGNAL , "received SIGINT, SIGTERM, or SIGHUP" }, { RERR_WAITCHILD , "waitpid() failed" }, { RERR_MALLOC , "error allocating core memory buffers" }, { RERR_PARTIAL , "some files/attrs were not transferred (see previous errors)" }, { RERR_VANISHED , "some files vanished before they could be transferred" }, { RERR_DEL_LIMIT , "the --max-delete limit stopped deletions" }, { RERR_TIMEOUT , "timeout in data send/receive" }, { RERR_CONTIMEOUT , "timeout waiting for daemon connection" }, { RERR_CMD_FAILED , "remote shell failed" }, { RERR_CMD_KILLED , "remote shell killed" }, { RERR_CMD_RUN , "remote command could not be run" }, { RERR_CMD_NOTFOUND,"remote command not found" }, { 0, NULL } }; /* * Map from rsync error code to name, or return NULL. */ static char const *rerr_name(int code) { int i; for (i = 0; rerr_names[i].name; i++) { if (rerr_names[i].code == code) return rerr_names[i].name; } return NULL; } static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char); static void logit(int priority, const char *buf) { if (logfile_was_closed) logfile_reopen(); if (logfile_fp) { /* Escape control chars in the message so an attacker-controlled * filename can't inject terminal escapes into the log an admin later * cat's (CWE-117); keep the trailing newline raw via end_char. */ int len = strlen(buf); char trailing = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0'; fprintf(logfile_fp, "%s [%d] ", timestring(time(NULL)), (int)getpid()); filtered_fwrite(logfile_fp, buf, len, 0, 1, trailing); fflush(logfile_fp); } else { syslog(priority, "%s", buf); } } static void syslog_init() { int options = LOG_PID; #ifdef LOG_NDELAY options |= LOG_NDELAY; #endif #ifdef LOG_DAEMON openlog(lp_syslog_tag(module_id), options, lp_syslog_facility(module_id)); #else openlog(lp_syslog_tag(module_id), options); #endif #ifndef LOG_NDELAY logit(LOG_INFO, "rsyncd started\n"); #endif } static void logfile_open(void) { mode_t old_umask = umask(022 | orig_umask); /* --log-file/`log file =` are operator-supplied paths that may transit * attacker-writable dirs; a planted symlink could redirect root's log * into e.g. /root/.ssh/authorized_keys. Refuse symlinks not owned by * uid 0 or our euid. */ int fd = open_no_attacker_symlinks(logfile_name, O_WRONLY | O_APPEND | O_CREAT, 0644); logfile_fp = fd >= 0 ? fdopen(fd, "a") : NULL; if (!logfile_fp && fd >= 0) close(fd); umask(old_umask); if (!logfile_fp) { int fopen_errno = errno; /* Rsync falls back to using syslog on failure. */ syslog_init(); rsyserr(FERROR, fopen_errno, "failed to open log-file %s", logfile_name); rprintf(FINFO, "Ignoring \"log file\" setting.\n"); logfile_name = ""; } } void log_init(int restart) { if (log_initialised) { if (!restart) /* Note: a restart only happens with am_daemon */ return; assert(logfile_name); /* all am_daemon procs got at least an empty string */ if (strcmp(logfile_name, lp_log_file(module_id)) != 0) { if (logfile_fp) { fclose(logfile_fp); logfile_fp = NULL; } else closelog(); logfile_name = NULL; } else if (*logfile_name) return; /* unchanged, non-empty "log file" names */ else if (lp_syslog_facility(-1) != lp_syslog_facility(module_id) || strcmp(lp_syslog_tag(-1), lp_syslog_tag(module_id)) != 0) closelog(); else return; /* unchanged syslog settings */ } else log_initialised = 1; /* This looks pointless, but it is needed in order for the * C library on some systems to fetch the timezone info * before the chroot. */ timestring(time(NULL)); /* Optionally use a log file instead of syslog. (Non-daemon * rsyncs will have already set logfile_name, as needed.) */ if (am_daemon && !logfile_name) logfile_name = lp_log_file(module_id); if (logfile_name && *logfile_name) logfile_open(); else syslog_init(); } /* Note that this close & reopen idiom intentionally ignores syslog logging. */ void logfile_close(void) { if (logfile_fp) { logfile_was_closed = 1; fclose(logfile_fp); logfile_fp = NULL; } } void logfile_reopen(void) { if (logfile_was_closed) { logfile_was_closed = 0; logfile_open(); } } static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char) { char outbuf[1024], *ob = outbuf; const char *end = in_buf + in_len; while (in_buf < end) { if (ob - outbuf >= (int)sizeof outbuf - 10) { if (fwrite(outbuf, ob - outbuf, 1, f) != 1) exit_cleanup(RERR_MESSAGEIO); ob = outbuf; } if ((in_buf < end - 4 && *in_buf == '\\' && in_buf[1] == '#' && isDigit(in_buf + 2) && isDigit(in_buf + 3) && isDigit(in_buf + 4)) || (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' ' || (escape_c1 && *(uchar*)in_buf >= 0x80 && *(uchar*)in_buf <= 0x9f)))) ob += snprintf(ob, 6, "\\#%03o", *(uchar*)in_buf++); else *ob++ = *in_buf++; } if (end_char) /* The "- 10" above means that there is always room for one more char here. */ *ob++ = end_char; if (ob != outbuf && fwrite(outbuf, ob - outbuf, 1, f) != 1) exit_cleanup(RERR_MESSAGEIO); } /* this is the underlying (unformatted) rsync debugging function. Call * it with FINFO, FERROR_*, FWARNING, FLOG, or FCLIENT. Note: recursion * can happen with certain fatal conditions. */ void rwrite(enum logcode code, const char *buf, int len, int is_utf8) { char trailing_CR_or_NL; FILE *f = msgs2stderr == 1 ? stderr : stdout; #ifdef ICONV_OPTION iconv_t ic = is_utf8 && ic_recv != (iconv_t)-1 ? ic_recv : ic_chck; #else #ifdef ICONV_CONST iconv_t ic = ic_chck; #endif #endif if (len < 0) exit_cleanup(RERR_MESSAGEIO); if (msgs2stderr == 1) { /* A normal daemon can get msgs2stderr set if the socket is busted, so we * change the message destination into an FLOG message in order to try to * get some info about an abnormal-exit into the log file. An rsh daemon * can have this set via user request, so we'll leave the code alone so * that the msg gets logged and then sent to stderr after that. */ if (am_daemon > 0 && code != FCLIENT) code = FLOG; } else if (send_msgs_to_gen) { /* Pass the message to our sibling in native charset. is_utf8 * may be set here if a malicious peer sends MSG_INFO/MSG_ERROR * to a daemon receiver (read_a_msg passes !am_generator); the * old assert(!is_utf8) made that a remotely-reachable abort. * Forwarding the bytes raw is safe -- the generator's rwrite() * gets is_utf8=0 and filtered_fwrite escapes non-printables. */ send_msg((enum msgcode)code, buf, len, 0); return; } if (code == FERROR_SOCKET) /* This gets simplified for a non-sibling. */ code = FERROR; else if (code == FERROR_UTF8) { is_utf8 = 1; code = FERROR; } if (code == FCLIENT) code = FINFO; else if (am_daemon || logfile_name) { static int in_block; char msg[2048]; int priority = code == FINFO || code == FLOG ? LOG_INFO : LOG_WARNING; if (in_block) return; in_block = 1; if (!log_initialised) log_init(0); /* buf holds exactly len bytes and is not necessarily NUL-terminated * (e.g. a forwarded MSG_* payload from read_a_msg), so copy by length * rather than strlcpy(), which would strlen() past the end of buf. */ int mlen = MIN((int)sizeof msg - 1, len); memcpy(msg, buf, mlen); msg[mlen] = '\0'; logit(priority, msg); in_block = 0; if (code == FLOG || (am_daemon && !am_server)) return; } else if (code == FLOG) return; switch (code) { case FERROR_XFER: got_xfer_error = 1; /* FALL THROUGH */ case FERROR: case FWARNING: f = stderr; break; case FINFO: if (quiet) return; break; /*case FLOG:*/ /*case FCLIENT:*/ /*case FERROR_UTF8:*/ /*case FERROR_SOCKET:*/ default: fprintf(stderr, "Bad logcode in rwrite(): %d [%s]\n", (int)code, who_am_i()); exit_cleanup(RERR_MESSAGEIO); } if (am_server && msgs2stderr != 1 && (msgs2stderr != 2 || f != stderr)) { enum msgcode msg = (enum msgcode)code; if (protocol_version < 30) { if (msg == MSG_ERROR) msg = MSG_ERROR_XFER; else if (msg == MSG_WARNING) msg = MSG_INFO; } /* Pass the message to the non-server side. */ if (send_msg(msg, buf, len, !is_utf8)) return; if (am_daemon > 0) { /* TODO: can we send the error to the user somehow? */ return; } f = stderr; } if (output_needs_newline) { fputc('\n', f); output_needs_newline = 0; } trailing_CR_or_NL = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0'; if (len && buf[0] == '\r') { fputc('\r', f); buf++; len--; } #ifdef ICONV_CONST if (ic != (iconv_t)-1) { xbuf outbuf, inbuf; char convbuf[1024]; int ierrno; INIT_CONST_XBUF(outbuf, convbuf); INIT_XBUF(inbuf, (char*)buf, len, (size_t)-1); while (inbuf.len) { iconvbufs(ic, &inbuf, &outbuf, inbuf.pos ? 0 : ICB_INIT); ierrno = errno; if (outbuf.len) { char trailing = inbuf.len ? '\0' : trailing_CR_or_NL; filtered_fwrite(f, convbuf, outbuf.len, 0, 0, trailing); if (trailing) { trailing_CR_or_NL = '\0'; fflush(f); } outbuf.len = 0; } /* Log one byte of illegal/incomplete sequence and continue with * the next character. Check that the buffer is non-empty for the * sake of robustness. */ if ((ierrno == EILSEQ || ierrno == EINVAL) && inbuf.len) { fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++)); inbuf.len--; } } if (trailing_CR_or_NL) { fputc(trailing_CR_or_NL, f); fflush(f); } } else #endif { filtered_fwrite(f, buf, len, !allow_8bit_chars, 0, trailing_CR_or_NL); if (trailing_CR_or_NL) fflush(f); } } /* This is the rsync debugging function. Call it with FINFO, FERROR_*, * FWARNING, FLOG, or FCLIENT. */ void rprintf(enum logcode code, const char *format, ...) { va_list ap; char buf[BIGPATHBUFLEN]; size_t len; va_start(ap, format); len = vsnprintf(buf, sizeof buf, format, ap); va_end(ap); /* Deal with buffer overruns. Instead of panicking, just * truncate the resulting string. (Note that configure ensures * that we have a vsnprintf() that doesn't ever return -1.) */ if (len > sizeof buf - 1) { static const char ellipsis[] = "[...]"; /* Reset length, and zero-terminate the end of our buffer */ len = sizeof buf - 1; buf[len] = '\0'; /* Copy the ellipsis to the end of the string, but give * us one extra character: * * v--- null byte at buf[sizeof buf - 1] * abcdefghij0 * -> abcd[...]00 <-- now two null bytes at end * * If the input format string has a trailing newline, * we copy it into that extra null; if it doesn't, well, * all we lose is one byte. */ memcpy(buf+len-sizeof ellipsis, ellipsis, sizeof ellipsis); if (format[strlen(format)-1] == '\n') { buf[len-1] = '\n'; } } rwrite(code, buf, len, 0); } /* This is like rprintf, but it also tries to print some * representation of the error code. Normally errcode = errno. * * Unlike rprintf, this always adds a newline and there should not be * one in the format string. * * Note that since strerror might involve dynamically loading a * message catalog we need to call it once before chroot-ing. */ void rsyserr(enum logcode code, int errcode, const char *format, ...) { va_list ap; char buf[BIGPATHBUFLEN]; size_t len; /* snprintf returns the would-have-been length on truncation, so * each cumulative call must be guarded; if not, sizeof buf - len * can underflow when promoted to size_t and the next call writes * past the buffer. */ len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i()); if (len < sizeof buf) { va_start(ap, format); len += vsnprintf(buf + len, sizeof buf - len, format, ap); va_end(ap); } if (len < sizeof buf) { len += snprintf(buf + len, sizeof buf - len, ": %s (%d)\n", strerror(errcode), errcode);