X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/c85631421deb9eb49abff012f7bbd0dc96278b1b..b64ee91a4138babc77ea06bb48bdad327a8b99b3:/main.c diff --git a/main.c b/main.c index 0ba67f84..3e190956 100644 --- a/main.c +++ b/main.c @@ -1,23 +1,25 @@ -/* -*- c-file-style: "linux" -*- - - Copyright (C) 1996-2001 by Andrew Tridgell - Copyright (C) Paul Mackerras 1996 - Copyright (C) 2001, 2002 by Martin Pool - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ +/* + * The startup routines, including main(), for rsync. + * + * Copyright (C) 1996-2001 Andrew Tridgell + * Copyright (C) 1996 Paul Mackerras + * Copyright (C) 2001, 2002 Martin Pool + * Copyright (C) 2003, 2004, 2005, 2006 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ #include "rsync.h" #if defined CONFIG_LOCALE && defined HAVE_LOCALE_H @@ -40,13 +42,16 @@ extern int kluge_around_eof; extern int do_stats; extern int log_got_error; extern int module_id; -extern int orig_umask; extern int copy_links; +extern int copy_dirlinks; extern int keep_dirlinks; extern int preserve_hard_links; extern int protocol_version; extern int recurse; extern int relative_paths; +extern int sanitize_paths; +extern int curr_dir_depth; +extern int module_id; extern int rsync_port; extern int whole_file; extern int read_batch; @@ -57,11 +62,15 @@ extern int filesfrom_fd; extern pid_t cleanup_child_pid; extern struct stats stats; extern char *filesfrom_host; +extern char *partial_dir; +extern char *basis_dir[]; extern char *rsync_path; extern char *shell_cmd; extern char *batch_name; +extern struct filter_list_struct server_filter_list; int local_server = 0; +mode_t orig_umask = 0; struct file_list *the_file_list; /* There's probably never more than at most 2 outstanding child processes, @@ -88,14 +97,14 @@ static int64 total_read, total_written; static void show_malloc_stats(void); /* Works like waitpid(), but if we already harvested the child pid in our - * sigchld_handler(), we succeed instead of returning an error. */ + * remember_children(), we succeed instead of returning an error. */ pid_t wait_process(pid_t pid, int *status_ptr, int flags) { pid_t waited_pid = waitpid(pid, status_ptr, flags); if (waited_pid == -1 && errno == ECHILD) { /* Status of requested child no longer available: check to - * see if it was processed by sigchld_handler(). */ + * see if it was processed by remember_children(). */ int cnt; for (cnt = 0; cnt < MAXCHILDPROCS; cnt++) { if (pid == pid_stat_table[cnt].pid) { @@ -167,7 +176,6 @@ static void handle_stats(int f) return; if (am_daemon) { - log_exit(0, __FILE__, __LINE__); if (f == -1 || !am_sender) return; } @@ -188,7 +196,7 @@ static void handle_stats(int f) /* this is the client */ if (f < 0 && !am_sender) /* e.g. when we got an empty file list. */ - ; + ; else if (!am_sender) { /* Read the first two in opposite order because the meaning of * read/write swaps when switching from sender to receiver. */ @@ -215,7 +223,8 @@ static void handle_stats(int f) static void output_summary(void) { if (do_stats) { - rprintf(FINFO,"\nNumber of files: %d\n", stats.num_files); + rprintf(FCLIENT, "\n"); + rprintf(FINFO,"Number of files: %d\n", stats.num_files); rprintf(FINFO,"Number of files transferred: %d\n", stats.num_transferred_files); rprintf(FINFO,"Total file size: %s bytes\n", @@ -242,8 +251,9 @@ static void output_summary(void) } if (verbose || do_stats) { + rprintf(FCLIENT, "\n"); rprintf(FINFO, - "\nsent %s bytes received %s bytes %s bytes/sec\n", + "sent %s bytes received %s bytes %s bytes/sec\n", human_num(total_written), human_num(total_read), human_dnum((total_written + total_read)/(0.5 + (endtime - starttime)), 2)); rprintf(FINFO, "total size is %s speedup is %.2f\n", @@ -266,7 +276,8 @@ static void show_malloc_stats(void) mi = mallinfo(); - rprintf(FINFO, "\n" RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", + rprintf(FCLIENT, "\n"); + rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n", getpid(), am_server ? "server " : "", am_daemon ? "daemon " : "", who_am_i()); rprintf(FINFO, " arena: %10ld (bytes from sbrk)\n", @@ -402,8 +413,8 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, if (verbose > 3) { for (i = 0; i < argc; i++) - rprintf(FINFO, "cmd[%d]=%s ", i, args[i]); - rprintf(FINFO, "\n"); + rprintf(FCLIENT, "cmd[%d]=%s ", i, args[i]); + rprintf(FCLIENT, "\n"); } if (read_batch) { @@ -437,8 +448,8 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char *path, /* The receiving side operates in one of two modes: * - * 1. it enters a directory and receives one or more files, placing them - * according to their names in the file-list. + * 1. it receives any number of files into a destination directory, + * placing them according to their names in the file-list. * * 2. it receives a single file and saves it using the name in the * destination path instead of its file-list name. This requires a @@ -457,15 +468,17 @@ static char *get_local_name(struct file_list *flist, char *dest_path) flist->count, NS(dest_path)); } - if (!dest_path) + if (!dest_path || list_only) return NULL; /* If the destination path refers to an existing directory, enter * it and use mode 1. If there is something other than a directory * at the destination path, we must be transferring one file * (anything at the destination will be overwritten). */ - if (do_stat(dest_path, &st) == 0) { + if (safe_stat(dest_path, &st) == 0) { if (S_ISDIR(st.st_mode)) { + if (sanitize_paths) + die_on_unsafe_path(dest_path, 0); if (!push_dir(dest_path)) { rsyserr(FERROR, errno, "push_dir#1 %s failed", full_fname(dest_path)); @@ -473,12 +486,25 @@ static char *get_local_name(struct file_list *flist, char *dest_path) } return NULL; } + if (sanitize_paths && S_ISLNK(st.st_mode)) + die_on_unsafe_path(dest_path, 0); if (flist->count > 1) { rprintf(FERROR, "ERROR: destination must be a directory when" " copying more than 1 file\n"); exit_cleanup(RERR_FILESELECT); } + /* Caution: flist->count could be 0! */ + if (flist->count == 1 && S_ISDIR(flist->files[0]->mode)) { + rprintf(FERROR, + "ERROR: cannot overwrite non-directory" + " with a directory\n"); + exit_cleanup(RERR_FILESELECT); + } + } else if (errno != ENOENT) { + rsyserr(FERROR, errno, "cannot stat destination %s", + full_fname(dest_path)); + exit_cleanup(RERR_FILESELECT); } cp = strrchr(dest_path, '/'); @@ -491,13 +517,11 @@ static char *get_local_name(struct file_list *flist, char *dest_path) if (cp && !cp[1]) *cp = '\0'; - umask(orig_umask); - if (do_mkdir(dest_path, 0777) != 0) { + if (mkdir_defmode(dest_path) != 0) { rsyserr(FERROR, errno, "mkdir %s failed", full_fname(dest_path)); exit_cleanup(RERR_FILEIO); } - umask(0); if (verbose) rprintf(FINFO, "created directory %s\n", dest_path); @@ -509,6 +533,8 @@ static char *get_local_name(struct file_list *flist, char *dest_path) return NULL; } + if (sanitize_paths) + die_on_unsafe_path(dest_path, 0); if (!push_dir(dest_path)) { rsyserr(FERROR, errno, "push_dir#2 %s failed", full_fname(dest_path)); @@ -529,6 +555,8 @@ static char *get_local_name(struct file_list *flist, char *dest_path) dest_path = "/"; *cp = '\0'; + if (sanitize_paths) + die_on_unsafe_path(dest_path, 0); if (!push_dir(dest_path)) { rsyserr(FERROR, errno, "push_dir#3 %s failed", full_fname(dest_path)); @@ -586,10 +614,14 @@ static void do_server_sender(int f_in, int f_out, int argc, char *argv[]) return; } - if (!relative_paths && !push_dir(dir)) { - rsyserr(FERROR, errno, "push_dir#3 %s failed", - full_fname(dir)); - exit_cleanup(RERR_FILESELECT); + if (!relative_paths) { + if (sanitize_paths) + die_on_unsafe_path(dir, 0); + if (!push_dir(dir)) { + rsyserr(FERROR, errno, "push_dir#3 %s failed", + full_fname(dir)); + exit_cleanup(RERR_FILESELECT); + } } argc--; argv++; @@ -627,7 +659,7 @@ static int do_recv(int f_in,int f_out,struct file_list *flist,char *local_name) /* The receiving side mustn't obey this, or an existing symlink that * points to an identical file won't be replaced by the referent. */ - copy_links = 0; + copy_links = copy_dirlinks = 0; if (preserve_hard_links) init_hard_links(); @@ -773,6 +805,38 @@ static void do_server_recv(int f_in, int f_out, int argc,char *argv[]) if (argc > 0) local_name = get_local_name(flist,argv[0]); + /* Now that we know what our destination directory turned out to be, + * we can sanitize the --link-/copy-/compare-dest args correctly. */ + if (sanitize_paths) { + char **dir; + for (dir = basis_dir; *dir; dir++) { + *dir = sanitize_path(NULL, *dir, NULL, curr_dir_depth, NULL); + die_on_unsafe_path(*dir, 0); + } + if (partial_dir) { + partial_dir = sanitize_path(NULL, partial_dir, NULL, curr_dir_depth, NULL); + /* A relative path gets this checked at every dir change. */ + if (*partial_dir == '/') + die_on_unsafe_path(partial_dir, 0); + } + } + if (server_filter_list.head) { + char **dir; + struct filter_list_struct *elp = &server_filter_list; + + for (dir = basis_dir; *dir; dir++) { + if (check_filter(elp, *dir, 1) < 0) + goto options_rejected; + } + if (partial_dir && *partial_dir == '/' + && check_filter(elp, partial_dir, 1) < 0) { + options_rejected: + rprintf(FERROR, + "Your options have been rejected by the server.\n"); + exit_cleanup(RERR_SYNTAX); + } + } + exit_code = do_recv(f_in,f_out,flist,local_name); exit_cleanup(exit_code); } @@ -792,7 +856,7 @@ void start_server(int f_in, int f_out, int argc, char *argv[]) io_set_sock_fds(f_in, f_out); setup_protocol(f_out, f_in); -#ifdef HAVE_ICONV_OPEN +#if defined HAVE_ICONV_OPEN && defined HAVE_ICONV_H setup_iconv(); #endif @@ -831,7 +895,7 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[]) io_set_sock_fds(f_in, f_out); setup_protocol(f_out,f_in); -#ifdef HAVE_ICONV_OPEN +#if defined HAVE_ICONV_OPEN && defined HAVE_ICONV_H setup_iconv(); #endif @@ -960,12 +1024,12 @@ static int start_client(int argc, char *argv[]) return rc; if (!read_batch) { /* for read_batch, NO source is specified */ - argc--; shell_path = check_for_hostspec(argv[0], &shell_machine, &rsync_port); if (shell_path) { /* source is remote */ char *dummy1; int dummy2; - if (argc && check_for_hostspec(argv[argc], &dummy1, &dummy2)) { + if (--argc + && check_for_hostspec(argv[argc], &dummy1, &dummy2)) { rprintf(FERROR, "The source and destination cannot both be remote.\n"); exit_cleanup(RERR_SYNTAX); @@ -990,12 +1054,14 @@ static int start_client(int argc, char *argv[]) } else { /* source is local, check dest arg */ am_sender = 1; - if (argc < 1) { /* destination required */ - usage(FERROR); - exit_cleanup(RERR_SYNTAX); + if (argc > 1) + p = argv[--argc]; + else { + p = "."; + list_only = 1; } - shell_path = check_for_hostspec(argv[argc], &shell_machine, &rsync_port); + shell_path = check_for_hostspec(p, &shell_machine, &rsync_port); if (shell_path && filesfrom_host && *filesfrom_host && strcmp(filesfrom_host, shell_machine) != 0) { rprintf(FERROR, @@ -1010,7 +1076,7 @@ static int start_client(int argc, char *argv[]) exit_cleanup(RERR_SYNTAX); } shell_machine = NULL; - shell_path = argv[argc]; + shell_path = p; } else if (rsync_port) { if (!shell_cmd) { return start_socket_client(shell_machine, @@ -1093,7 +1159,7 @@ static RETSIGTYPE sigusr2_handler(UNUSED(int val)) _exit(0); } -static RETSIGTYPE sigchld_handler(UNUSED(int val)) +RETSIGTYPE remember_children(UNUSED(int val)) { #ifdef WNOHANG int cnt, status; @@ -1115,7 +1181,7 @@ static RETSIGTYPE sigchld_handler(UNUSED(int val)) } #endif #ifndef HAVE_SIGACTION - signal(SIGCHLD, sigchld_handler); + signal(SIGCHLD, remember_children); #endif } @@ -1186,7 +1252,7 @@ int main(int argc,char *argv[]) #endif SIGACTMASK(SIGUSR1, sigusr1_handler); SIGACTMASK(SIGUSR2, sigusr2_handler); - SIGACTMASK(SIGCHLD, sigchld_handler); + SIGACTMASK(SIGCHLD, remember_children); #ifdef MAINTAINER_MODE SIGACTMASK(SIGSEGV, rsync_panic_handler); SIGACTMASK(SIGFPE, rsync_panic_handler); @@ -1206,7 +1272,7 @@ int main(int argc,char *argv[]) /* we set a 0 umask so that correct file permissions can be * carried across */ - orig_umask = (int)umask(0); + orig_umask = umask(0); #if defined CONFIG_LOCALE && defined HAVE_SETLOCALE setlocale(LC_CTYPE, ""); @@ -1229,6 +1295,9 @@ int main(int argc,char *argv[]) /* Ignore SIGPIPE; we consistently check error codes and will * see the EPIPE. */ SIGACTION(SIGPIPE, SIG_IGN); +#ifdef SIGXFSZ + SIGACTION(SIGXFSZ, SIG_IGN); +#endif /* Initialize push_dir here because on some old systems getcwd * (implemented by forking "pwd" and reading its output) doesn't