/*------------------------------------------------------------------------- * * remote.c * * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * *------------------------------------------------------------------------- */ #include #include #include #include #include #include #ifdef WIN32 #define __thread __declspec(thread) #else #include #endif #include "pg_probackup.h" #include "file.h" #include "common/fe_memutils.h" #define MAX_CMDLINE_LENGTH 4096 #define MAX_CMDLINE_OPTIONS 256 /* is not used now #define ERR_BUF_SIZE 4096 may be removed in the future*/ #define PIPE_SIZE (64*1024) static int split_options(int argc, char* argv[], int max_options, char* options) { char* opt = options; char in_quote = '\0'; while (true) { switch (*opt) { case '\'': case '\"': if (!in_quote) { in_quote = *opt++; continue; } if (*opt == in_quote && *++opt != in_quote) { in_quote = '\0'; continue; } break; case '\0': if (opt != options) { argv[argc++] = options; if (argc >= max_options) elog(ERROR, "Too much options"); } return argc; case ' ': argv[argc++] = options; if (argc >= max_options) elog(ERROR, "Too much options"); *opt++ = '\0'; options = opt; continue; default: break; } opt += 1; } return argc; } static __thread int child_pid; void wait_ssh(void) { /* * We need to wait termination of SSH process to eliminate zombies. * There is no waitpid() function at Windows but there are no zombie processes caused by lack of wait/waitpid. * So just disable waitpid for Windows. */ #ifndef WIN32 int status; waitpid(child_pid, &status, 0); elog(LOG, "SSH process %d is terminated with status %d", child_pid, status); #endif } #ifdef WIN32 void launch_ssh(char* argv[]) { int infd = atoi(argv[2]); int outfd = atoi(argv[3]); SYS_CHECK(close(STDIN_FILENO)); SYS_CHECK(close(STDOUT_FILENO)); SYS_CHECK(dup2(infd, STDIN_FILENO)); SYS_CHECK(dup2(outfd, STDOUT_FILENO)); SYS_CHECK(execvp(argv[4], argv+4)); } #endif static bool needs_quotes(char const* path) { return strchr(path, ' ') != NULL; } bool launch_agent(void) { char cmd[MAX_CMDLINE_LENGTH]; char* ssh_argv[MAX_CMDLINE_OPTIONS]; char libenv[MAX_CMDLINE_LENGTH] = {0}; int ssh_argc; int outfd[2]; int infd[2]; int errfd[2]; int agent_version; errno_t rc = 0; ssh_argc = 0; #ifdef WIN32 ssh_argv[ssh_argc++] = PROGRAM_NAME_FULL; ssh_argv[ssh_argc++] = (char *)"ssh"; ssh_argc += 2; /* reserve space for pipe descriptors */ #endif ssh_argv[ssh_argc++] = (char *)instance_config.remote.proto; if (instance_config.remote.port != NULL) { ssh_argv[ssh_argc++] = (char *)"-p"; ssh_argv[ssh_argc++] = (char *)instance_config.remote.port; } if (instance_config.remote.user != NULL) { ssh_argv[ssh_argc++] = (char *)"-l"; ssh_argv[ssh_argc++] = (char *)instance_config.remote.user; } if (instance_config.remote.ssh_config != NULL) { ssh_argv[ssh_argc++] = (char *)"-F"; ssh_argv[ssh_argc++] = (char *)instance_config.remote.ssh_config; } if (instance_config.remote.ssh_options != NULL) { ssh_argc = split_options(ssh_argc, (char **)const_cast(ssh_argv), MAX_CMDLINE_OPTIONS, pg_strdup(instance_config.remote.ssh_options)); } ssh_argv[ssh_argc++] = (char *)"-o"; ssh_argv[ssh_argc++] = (char *)"PasswordAuthentication=no"; ssh_argv[ssh_argc++] = (char *)"-o"; ssh_argv[ssh_argc++] = (char *)"Compression=no"; ssh_argv[ssh_argc++] = (char *)"-o"; ssh_argv[ssh_argc++] = (char *)"LogLevel=error"; ssh_argv[ssh_argc++] = (char *)instance_config.remote.host; ssh_argv[ssh_argc++] = (char *)cmd; ssh_argv[ssh_argc] = NULL; if (instance_config.remote.libpath) { #ifdef WIN32 rc = snprintf_s(libenv, sizeof(libenv), sizeof(libenv) - 1, "set LD_LIBRARY_PATH=%s &&", instance_config.remote.libpath); securec_check_ss_c(rc, "\0", "\0"); #else rc = snprintf_s(libenv, sizeof(libenv), sizeof(libenv) - 1, "export LD_LIBRARY_PATH=%s &&", instance_config.remote.libpath); securec_check_ss_c(rc, "\0", "\0"); #endif } if (instance_config.remote.path) { char const* probackup = PROGRAM_NAME_FULL; char* sep = (char *)strrchr(probackup, '/'); if (sep != NULL) { probackup = sep + 1; } #ifdef WIN32 else { sep = strrchr(probackup, '\\'); if (sep != NULL) { probackup = sep + 1; } } if (needs_quotes(instance_config.remote.path) || needs_quotes(PROGRAM_NAME_FULL)) { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s %s\\%s\" agent", libenv, instance_config.remote.path, probackup); securec_check_ss_c(rc, "\0", "\0"); } else { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "%s %s\\%s agent", libenv, instance_config.remote.path, probackup); securec_check_ss_c(rc, "\0", "\0"); } #else if (needs_quotes(instance_config.remote.path) || needs_quotes(PROGRAM_NAME_FULL)) { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s %s/%s\" agent", libenv, instance_config.remote.path, probackup); securec_check_ss_c(rc, "\0", "\0"); } else { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "%s %s/%s agent", libenv, instance_config.remote.path, probackup); securec_check_ss_c(rc, "\0", "\0"); } #endif } else { if (needs_quotes(PROGRAM_NAME_FULL)) { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s\" agent", PROGRAM_NAME_FULL); securec_check_ss_c(rc, "\0", "\0"); } else { rc = snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "%s agent", PROGRAM_NAME_FULL); securec_check_ss_c(rc, "\0", "\0"); } } #ifdef WIN32 SYS_CHECK(_pipe(infd, PIPE_SIZE, _O_BINARY)) ; SYS_CHECK(_pipe(outfd, PIPE_SIZE, _O_BINARY)); ssh_argv[2] = (const char *)format_text("%d", outfd[0]); ssh_argv[3] = (const char *)format_text("%d", infd[1]); { intptr_t pid = _spawnvp(_P_NOWAIT, ssh_argv[0], ssh_argv); if (pid < 0) return false; child_pid = GetProcessId((HANDLE)pid); #else SYS_CHECK(pipe(infd)); SYS_CHECK(pipe(outfd)); SYS_CHECK(pipe(errfd)); SYS_CHECK(child_pid = fork()); if (child_pid == 0) { /* child */ SYS_CHECK(close(STDIN_FILENO)); SYS_CHECK(close(STDOUT_FILENO)); SYS_CHECK(close(STDERR_FILENO)); SYS_CHECK(dup2(outfd[0], STDIN_FILENO)); SYS_CHECK(dup2(infd[1], STDOUT_FILENO)); SYS_CHECK(dup2(errfd[1], STDERR_FILENO)); SYS_CHECK(close(infd[0])); SYS_CHECK(close(infd[1])); SYS_CHECK(close(outfd[0])); SYS_CHECK(close(outfd[1])); SYS_CHECK(close(errfd[0])); SYS_CHECK(close(errfd[1])); if (execvp(ssh_argv[0], ssh_argv) < 0) { return false; } } else { #endif elog(LOG, "Start SSH client process, pid %d", child_pid); SYS_CHECK(close(infd[1])); /* These are being used by the child */ SYS_CHECK(close(outfd[0])); SYS_CHECK(close(errfd[1])); fio_redirect(infd[0], outfd[1], errfd[0]); /* write to stdout */ } /* Make sure that remote agent has the same version * TODO: we must also check PG version and fork edition */ agent_version = fio_get_agent_version(); if (agent_version != AGENT_PROTOCOL_VERSION) { char agent_version_str[1024]; errno_t rc = sprintf_s(agent_version_str, 1024, "%d.%d.%d", agent_version / 10000, (agent_version / 100) % 100, agent_version % 100); securec_check_ss_c(rc, "\0", "\0"); elog(ERROR, "Remote agent version %s does not match local program version %s", agent_version_str, PROGRAM_VERSION); } return true; }