📄 clientserver.c
字号:
/* * The socket based protocol for setting up a connection with rsyncd. * * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org> * Copyright (C) 2001-2002 Martin Pool <mbp@samba.org> * Copyright (C) 2002-2008 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 "ifuncs.h"extern int quiet;extern int verbose;extern int dry_run;extern int output_motd;extern int list_only;extern int am_sender;extern int am_server;extern int am_daemon;extern int am_root;extern int rsync_port;extern int protect_args;extern int ignore_errors;extern int preserve_xattrs;extern int kluge_around_eof;extern int daemon_over_rsh;extern int sanitize_paths;extern int numeric_ids;extern int filesfrom_fd;extern int remote_protocol;extern int protocol_version;extern int io_timeout;extern int no_detach;extern int write_batch;extern int default_af_hint;extern int logfile_format_has_i;extern int logfile_format_has_o_or_i;extern mode_t orig_umask;extern char *bind_address;extern char *sockopts;extern char *config_file;extern char *logfile_format;extern char *files_from;extern char *tmpdir;extern struct chmod_mode_struct *chmod_modes;extern struct filter_list_struct daemon_filter_list;extern char curr_dir[];#ifdef ICONV_OPTIONextern char *iconv_opt;extern iconv_t ic_send, ic_recv;#endifchar *auth_user;int read_only = 0;int module_id = -1;int munge_symlinks = 0;struct chmod_mode_struct *daemon_chmod_modes;/* module_dirlen is the length of the module_dir string when in daemon * mode and module_dir is not "/"; otherwise 0. (Note that a chroot- * enabled module can have a non-"/" module_dir these days.) */char *module_dir = NULL;unsigned int module_dirlen = 0;char *full_module_path;static int rl_nulls = 0;#ifdef HAVE_SIGACTIONstatic struct sigaction sigact;#endif/** * Run a client connected to an rsyncd. The alternative to this * function for remote-shell connections is do_cmd(). * * After negotiating which module to use and reading the server's * motd, this hands over to client_run(). Telling the server the * module will cause it to chroot/setuid/etc. * * Instead of doing a transfer, the client may at this stage instead * get a listing of remote modules and exit. * * @return -1 for error in startup, or the result of client_run(). * Either way, it eventually gets passed to exit_cleanup(). **/int start_socket_client(char *host, int remote_argc, char *remote_argv[], int argc, char *argv[]){ int fd, ret; char *p, *user = NULL; /* This is redundant with code in start_inband_exchange(), but this * short-circuits a problem in the client before we open a socket, * and the extra check won't hurt. */ if (**remote_argv == '/') { rprintf(FERROR, "ERROR: The remote path must start with a module name not a /\n"); return -1; } if ((p = strrchr(host, '@')) != NULL) { user = host; host = p+1; *p = '\0'; } fd = open_socket_out_wrapped(host, rsync_port, bind_address, default_af_hint); if (fd == -1) exit_cleanup(RERR_SOCKETIO);#ifdef ICONV_CONST setup_iconv();#endif ret = start_inband_exchange(fd, fd, user, remote_argc, remote_argv); return ret ? ret : client_run(fd, fd, -1, argc, argv);}static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client){ int remote_sub = -1;#if SUBPROTOCOL_VERSION != 0 int our_sub = protocol_version < PROTOCOL_VERSION ? 0 : SUBPROTOCOL_VERSION;#else int our_sub = 0;#endif char *motd; io_printf(f_out, "@RSYNCD: %d.%d\n", protocol_version, our_sub); if (!am_client) { motd = lp_motd_file(); if (motd && *motd) { FILE *f = fopen(motd,"r"); while (f && !feof(f)) { int len = fread(buf, 1, bufsiz - 1, f); if (len > 0) write_buf(f_out, buf, len); } if (f) fclose(f); write_sbuf(f_out, "\n"); } } /* This strips the \n. */ if (!read_line_old(f_in, buf, bufsiz)) { if (am_client) rprintf(FERROR, "rsync: did not see server greeting\n"); return -1; } if (sscanf(buf, "@RSYNCD: %d.%d", &remote_protocol, &remote_sub) < 1) { if (am_client) rprintf(FERROR, "rsync: server sent \"%s\" rather than greeting\n", buf); else io_printf(f_out, "@ERROR: protocol startup error\n"); return -1; } if (remote_sub < 0) { if (remote_protocol == 30) { if (am_client) rprintf(FERROR, "rsync: server is speaking an incompatible beta of protocol 30\n"); else io_printf(f_out, "@ERROR: your client is speaking an incompatible beta of protocol 30\n"); return -1; } remote_sub = 0; } if (protocol_version > remote_protocol) { protocol_version = remote_protocol; if (remote_sub) protocol_version--; } else if (protocol_version == remote_protocol) { if (remote_sub != our_sub) protocol_version--; }#if SUBPROTOCOL_VERSION != 0 else if (protocol_version < remote_protocol) { if (our_sub) protocol_version--; }#endif if (protocol_version >= 30) rl_nulls = 1; return 0;}int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char *argv[]){ int i, modlen; char line[BIGPATHBUFLEN]; char *sargs[MAX_ARGS]; int sargc = 0; char *p, *modname; assert(argc > 0 && *argv != NULL); if (**argv == '/') { rprintf(FERROR, "ERROR: The remote path must start with a module name\n"); return -1; } if (!(p = strchr(*argv, '/'))) modlen = strlen(*argv); else modlen = p - *argv; if (!(modname = new_array(char, modlen+1+1))) /* room for '/' & '\0' */ out_of_memory("start_inband_exchange"); strlcpy(modname, *argv, modlen + 1); modname[modlen] = '/'; modname[modlen+1] = '\0'; if (!user) user = getenv("USER"); if (!user) user = getenv("LOGNAME"); if (exchange_protocols(f_in, f_out, line, sizeof line, 1) < 0) return -1; /* set daemon_over_rsh to false since we need to build the * true set of args passed through the rsh/ssh connection; * this is a no-op for direct-socket-connection mode */ daemon_over_rsh = 0; server_options(sargs, &sargc); if (sargc >= MAX_ARGS - 2) goto arg_overflow; sargs[sargc++] = "."; while (argc > 0) { if (sargc >= MAX_ARGS - 1) { arg_overflow: rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); exit_cleanup(RERR_SYNTAX); } if (strncmp(*argv, modname, modlen) == 0 && argv[0][modlen] == '\0') sargs[sargc++] = modname; /* we send "modname/" */ else sargs[sargc++] = *argv; argv++; argc--; } sargs[sargc] = NULL; if (verbose > 1) print_child_argv("sending daemon args:", sargs); io_printf(f_out, "%.*s\n", modlen, modname); /* Old servers may just drop the connection here, rather than sending a proper EXIT command. Yuck. */ kluge_around_eof = list_only && protocol_version < 25 ? 1 : 0; while (1) { if (!read_line_old(f_in, line, sizeof line)) { rprintf(FERROR, "rsync: didn't get server startup line\n"); return -1; } if (strncmp(line,"@RSYNCD: AUTHREQD ",18) == 0) { auth_client(f_out, user, line+18); continue; } if (strcmp(line,"@RSYNCD: OK") == 0) break; if (strcmp(line,"@RSYNCD: EXIT") == 0) { /* This is sent by recent versions of the * server to terminate the listing of modules. * We don't want to go on and transfer * anything; just exit. */ exit(0); } if (strncmp(line, "@ERROR", 6) == 0) { rprintf(FERROR, "%s\n", line); /* This is always fatal; the server will now * close the socket. */ return -1; } /* This might be a MOTD line or a module listing, but there is * no way to differentiate it. The manpage mentions this. */ if (output_motd) rprintf(FINFO, "%s\n", line); } kluge_around_eof = 0; if (rl_nulls) { for (i = 0; i < sargc; i++) { if (!sargs[i]) /* stop at --protect-args NULL */ break; write_sbuf(f_out, sargs[i]); write_byte(f_out, 0); } write_byte(f_out, 0); } else { for (i = 0; i < sargc; i++) io_printf(f_out, "%s\n", sargs[i]); write_sbuf(f_out, "\n"); } if (protect_args) send_protected_args(f_out, sargs); if (protocol_version < 23) { if (protocol_version == 22 || !am_sender) io_start_multiplex_in(); } free(modname); return 0;}static char *finish_pre_exec(pid_t pid, int fd, char *request, char **early_argv, char **argv){ int j = 0, status = -1; if (!request) request = "(NONE)"; write_buf(fd, request, strlen(request)+1); if (early_argv) { for ( ; *early_argv; early_argv++) write_buf(fd, *early_argv, strlen(*early_argv)+1); j = 1; /* Skip arg0 name in argv. */ } for ( ; argv[j]; j++) { write_buf(fd, argv[j], strlen(argv[j])+1); if (argv[j][0] == '.' && argv[j][1] == '\0') break; } write_byte(fd, 0); close(fd); if (wait_process(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { char *e; if (asprintf(&e, "pre-xfer exec returned failure (%d)%s%s\n", status, status < 0 ? ": " : "", status < 0 ? strerror(errno) : "") < 0) out_of_memory("finish_pre_exec"); return e; } return NULL;}static int read_arg_from_pipe(int fd, char *buf, int limit){ char *bp = buf, *eob = buf + limit - 1; while (1) { int got = read(fd, bp, 1); if (got != 1) { if (got < 0 && errno == EINTR) continue; return -1; } if (*bp == '\0') break; if (bp < eob) bp++; } *bp = '\0'; return bp - buf;}static int path_failure(int f_out, const char *dir, BOOL was_chdir){ if (was_chdir) rsyserr(FLOG, errno, "chdir %s failed\n", dir); else rprintf(FLOG, "normalize_path(%s) failed\n", dir); io_printf(f_out, "@ERROR: chdir failed\n"); return -1;}static int rsync_module(int f_in, int f_out, int i, char *addr, char *host){ int argc; char **argv, **orig_argv, **orig_early_argv, *module_chdir; char line[BIGPATHBUFLEN]; uid_t uid = (uid_t)-2; /* canonically "nobody" */ gid_t gid = (gid_t)-2; char *p, *err_msg = NULL; char *name = lp_name(i); int use_chroot = lp_use_chroot(i); int ret, pre_exec_fd = -1; pid_t pre_exec_pid = 0; char *request = NULL;#ifdef ICONV_OPTION iconv_opt = lp_charset(i); if (*iconv_opt) setup_iconv(); iconv_opt = NULL;#endif if (!allow_access(addr, host, lp_hosts_allow(i), lp_hosts_deny(i))) { rprintf(FLOG, "rsync denied on module %s from %s (%s)\n", name, host, addr); if (!lp_list(i)) io_printf(f_out, "@ERROR: Unknown module '%s'\n", name); else { io_printf(f_out, "@ERROR: access denied to %s from %s (%s)\n", name, host, addr); } return -1; } if (am_daemon && am_server) { rprintf(FLOG, "rsync allowed access on module %s from %s (%s)\n", name, host, addr); } if (!claim_connection(lp_lock_file(i), lp_max_connections(i))) { if (errno) { rsyserr(FLOG, errno, "failed to open lock file %s", lp_lock_file(i)); io_printf(f_out, "@ERROR: failed to open lock file\n"); } else { rprintf(FLOG, "max connections (%d) reached\n", lp_max_connections(i)); io_printf(f_out, "@ERROR: max connections (%d) reached -- try again later\n", lp_max_connections(i)); } return -1; } auth_user = auth_server(f_in, f_out, i, host, addr, "@RSYNCD: AUTHREQD "); if (!auth_user) { io_printf(f_out, "@ERROR: auth failed on module %s\n", name); return -1; } module_id = i; if (lp_read_only(i)) read_only = 1; if (lp_transfer_logging(i) && !logfile_format) logfile_format = lp_log_format(i); if (log_format_has(logfile_format, 'i')) logfile_format_has_i = 1; if (logfile_format_has_i || log_format_has(logfile_format, 'o')) logfile_format_has_o_or_i = 1; am_root = (MY_UID() == 0); if (am_root) { p = lp_uid(i); if (!name_to_uid(p, &uid)) { if (!isDigit(p)) { rprintf(FLOG, "Invalid uid %s\n", p); io_printf(f_out, "@ERROR: invalid uid %s\n", p); return -1; } uid = atoi(p); } p = lp_gid(i); if (!name_to_gid(p, &gid)) { if (!isDigit(p)) { rprintf(FLOG, "Invalid gid %s\n", p); io_printf(f_out, "@ERROR: invalid gid %s\n", p); return -1; } gid = atoi(p); } } /* TODO: If we're not root, but the configuration requests * that we change to some uid other than the current one, then * log a warning. */ /* TODO: Perhaps take a list of gids, and make them into the * supplementary groups. */ module_dir = lp_path(i); if (use_chroot) { if ((p = strstr(module_dir, "/./")) != NULL) { *p = '\0'; /* Temporary... */ if (!(module_chdir = normalize_path(module_dir, True, NULL))) return path_failure(f_out, module_dir, False); *p = '/'; if (!(p = normalize_path(p + 2, True, &module_dirlen))) return path_failure(f_out, strstr(module_dir, "/./"), False); if (!(full_module_path = normalize_path(module_dir, False, NULL))) full_module_path = module_dir; module_dir = p; } else { if (!(module_chdir = normalize_path(module_dir, False, NULL))) return path_failure(f_out, module_dir, False); full_module_path = module_chdir; module_dir = "/"; module_dirlen = 1; } } else { if (!(module_chdir = normalize_path(module_dir, False, &module_dirlen))) return path_failure(f_out, module_dir, False); full_module_path = module_dir = module_chdir;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -