963 lines · 26.8 KB · C
RawDownload
/* * 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.