zhenorzz / goploy

Devops, Deploy, CI/CD, Terminal, Sftp, Server monitor, Crontab Manager, Nginx Manager.
http://www.goploy.icu
GNU General Public License v3.0
1.1k stars 172 forks source link

fix: quote in rsync remote shell #107

Closed motuwe closed 6 months ago

motuwe commented 6 months ago

当使用 rsync 方式传输,-e 选项中有开始引号,而没有结束引号时,会报错。尤其是 ssh 密码比较复杂的情况下,比如 U6rOp>+d'z"NF7_P。报错如下

err: exit status 1
output: Missing trailing-' in remote-shell command.
rsync error: syntax or usage error (code 1) at main.c(432) [sender=3.1.2]

根据 man rsync 描述

-e, --rsh=COMMAND
              This option allows you to choose an alternative remote shell program to use for com‐
              munication between the local and remote copies of rsync. Typically, rsync is config‐
              ured to use ssh by default, but you may prefer to use rsh on a local network.

              If this option is used with [user@]host::module/path, then the remote shell  COMMAND
              will  be used to run an rsync daemon on the remote host, and all data will be trans‐
              mitted through that remote shell connection, rather than  through  a  direct  socket
              connection  to  a  running  rsync daemon on the remote host.  See the section "USING
              RSYNC-DAEMON FEATURES VIA A REMOTE-SHELL CONNECTION" above.

              Command-line arguments are permitted in COMMAND provided that COMMAND  is  presented
              to  rsync  as a single argument.  You must use spaces (not tabs or other whitespace)
              to separate the command and args from each other, and you  can  use  single-  and/or
              double-quotes  to  preserve  spaces in an argument (but not backslashes).  Note that
              doubling a single-quote inside a single-quoted  string  gives  you  a  single-quote;
              likewise  for  double-quotes  (though you need to pay attention to which quotes your
              shell is parsing and which quotes rsync is parsing).

其中 Note that doubling a single-quote inside a single-quoted string gives you a single-quote 表示在单引号中重复单引号会得到一个单引号。

rysnc 处理引号源码 https://github.com/RsyncProject/rsync/blob/4592aa770d51d5e83845b032feea1de441f03ee7/main.c#L528 测试用例

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_ARGS 10
#define MAX_SERVER_ARGS 5

void parse_cmd(char* cmd, char* args[]) {
    int argc = 0;
    char* t;
    char* f;
    char in_quote = 0;
    for (t = f = cmd; *f; f++) {
        if (*f == ' ')
            continue;
        /* Comparison leaves rooms for server_options(). */
        if (argc >= MAX_ARGS - MAX_SERVER_ARGS)
            goto arg_overflow;
        args[argc++] = t;
        while (*f != ' ' || in_quote) {
            if (!*f) {
                if (in_quote) {
                    fprintf(stderr, "Missing trailing-%c in remote-shell command.\n", in_quote);
                    exit(1);
                }
                f--;
                break;
            }
            if (*f == '\'' || *f == '"') {
                if (!in_quote) {
                    in_quote = *f++;
                    continue;
                }
                if (*f == in_quote && *++f != in_quote) {
                    in_quote = '\0';
                    continue;
                }
            }
            *t++ = *f++;
        }
        *t++ = '\0';
    }

arg_overflow:
    // handle argument overflow if necessary
}

int main() {
    // char cmd[] = "echo U6rOp>+d'z\"NF7_P"; // error
    char cmd[] = "echo 'U6rOp>+d''z\"NF7_P'"; // ok
    char* args[MAX_ARGS];
    parse_cmd(cmd, args);
    for (int i = 0; i < MAX_ARGS; i++) {
        if (args[i] != NULL) {
            printf("Arg %d: %s\n", i, args[i]);
        }
    }
    return 0;
}

综上所述,处理逻辑应该把 rsync -e 选项中最大变数 ssh 密码用单引号括起来,然后转义其中的单引号(重复单引号)即可。

zhenorzz commented 6 months ago

你说的很有道理

zhenorzz commented 6 months ago

这两天验证一下