/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include static const char *argv0 = "sshexec"; #define exitf(...) (fprintf(stderr, __VA_ARGS__), exit(255)) static void usage(void) { exitf("usage: %s [{ %s }] [ssh-option] ... destination command [argument] ...\n", argv0, "[ssh=command] [dir=directory] [[fd]{>,>>,>|,<>}[&]=file]"); } struct redirection { const char *asis; const char *escape; }; static const enum optclass { NO_RECOGNISED = 0, NO_ARGUMENT, MANDATORY_ARGUMENT } sshopts[(size_t)1 << CHAR_BIT] = { #define X(F) [F] = NO_ARGUMENT X('4'), X('6'), X('A'), X('a'), X('C'), X('f'), X('G'), X('g'), X('K'), X('k'), X('M'), X('N'), X('n'), X('q'), X('s'), X('T'), X('t'), X('V'), X('v'), X('X'), X('x'), X('Y'), X('y'), #undef X #define X(F) [F] = MANDATORY_ARGUMENT X('B'), X('b'), X('c'), X('D'), X('E'), X('e'), X('F'), X('I'), X('i'), X('J'), X('L'), X('l'), X('m'), X('O'), X('o'), X('P'), X('p'), X('Q'), X('R'), X('S'), X('W'), X('w') #undef X }; static char *command = NULL; static size_t command_size = 0; static size_t command_len = 0; #define build_command(DATA, N)\ do {\ build_command_reserve(N);\ memcpy(&command[command_len], (DATA), (N));\ command_len += (N);\ } while (0) #define build_command_asis(S)\ build_command(S, strlen(S)) #define finalise_command()\ build_command("", 1) static void build_command_reserve(size_t n) { if (n > command_size - command_len) { if (n < 512) n = 512; if (command_len + n > SIZE_MAX) exitf("%s: could not allocate enough memory\n", argv0); command_size = command_len + n; command = realloc(command, command_size); if (!command) exitf("%s: could not allocate enough memory\n", argv0); } } static void build_command_escape(const char *arg) { size_t n = 0; if (!*arg) { build_command_asis("''"); return; } while (isalnum(arg[n]) || arg[n] == '_' || arg[n] == '/') n += 1; if (!arg[n]) { build_command(arg, n); return; } build_command_asis("\"$(printf '"); goto start; while (*arg) { build_command_reserve(4); command[command_len++] = '\\'; command[command_len] = (((unsigned char)*arg >> 6) & 7) + '0'; command_len += (command[command_len] != '0'); command[command_len] = (((unsigned char)*arg >> 3) & 7) + '0'; command_len += (command[command_len] != '0'); command[command_len++] = ((unsigned char)*arg & 7) + '0'; arg = &arg[1]; n = 0; while (isalpha(arg[n]) || arg[n] == '_' || arg[n] == '/') n += 1; if (n) { while (isalnum(arg[n]) || arg[n] == '_' || arg[n] == '/') n += 1; start: build_command(arg, n); arg = &arg[n]; } } build_command_asis("\\n')\""); } int main(int argc_unused, char *argv[]) { const char *dir = NULL; const char *ssh = NULL; struct redirection *redirections = NULL; size_t nredirections = 0; const char *destination; char **opts, *p; size_t nopts; enum optclass class; const char *arg; char opt; const char **args; size_t i; (void) argc_unused; if (*argv) argv0 = *argv++; #define STORE_OPT(VARP, OPT)\ if (!strncmp(*argv, OPT"=", sizeof(OPT))) {\ if (*(VARP) || !*(*(VARP) = &(*argv)[sizeof(OPT)]))\ usage();\ continue;\ } if (*argv && !strcmp(*argv, "{")) { argv++; for (; *argv && strcmp(*argv, "}"); argv++) { STORE_OPT(&ssh, "ssh") STORE_OPT(&dir, "dir") p = *argv; while (isdigit(*p)) p++; if (p[0] == '>') p = &p[1 + (p[1] == '>' || p[1] == '|')]; else if (p[0] == '<') p = &p[1 + (p[1] == '>')]; else usage(); if (p[p[0] == '&'] != '=') usage(); redirections = realloc(redirections, (nredirections + 1U) * sizeof(*redirections)); if (!redirections) exitf("%s: could not allocate enough memory\n", argv0); if (*p == '&') { p = &p[1]; memmove(p, &p[1], strlen(&p[1]) + 1U); if (isdigit(*p)) { p++; while (isdigit(*p)) p++; } else if (*p == '-') { p++; } if (*p) usage(); redirections[nredirections].escape = NULL; } else { *p++ = '\0'; redirections[nredirections].escape = p; } redirections[nredirections++].asis = *argv; } if (!*argv) usage(); argv++; } #undef STORE_OPT if (!ssh) ssh = "ssh"; opts = argv; nopts = 0; while (*argv) { if (!strcmp(*argv, "--")) { argv++; break; } else if ((*argv)[0] != '-' || !(*argv)[1]) { break; } arg = &(*argv++)[1]; nopts++; while (*arg) { opt = *arg++; class = sshopts[(unsigned char)opt]; if (class == MANDATORY_ARGUMENT) { if (*arg) { break; } else if (*argv) { argv++; nopts++; break; } else { exitf("%s: argument missing for option -%c\n", argv0, opt); } } else if (class == NO_RECOGNISED) { exitf("%s: unrecognised option -%c\n", argv0, opt); } } } destination = *argv++; if (!destination && !*argv) usage(); if (dir) { build_command_asis("cd -- "); build_command_escape(dir); build_command_asis(" && "); } build_command_asis("exec --"); for (; *argv; argv++) { build_command_asis(" "); build_command_escape(*argv); } for (i = 0; i < nredirections; i++) { build_command_asis(" "); build_command_asis(redirections[i].asis); if (redirections[i].escape) { build_command_asis(" "); build_command_escape(redirections[i].escape); } } finalise_command(); i = 0; args = calloc(5 + nopts, sizeof(*args)); if (!args) exitf("%s: could not allocate enough memory\n", argv0); args[i++] = ssh; memcpy(&args[i], opts, nopts * sizeof(*opts)); i += nopts; args[i++] = "--"; args[i++] = destination; args[i++] = command; args[i++] = NULL; #if defined(__GNUC__) # pragma GCC diagnostic ignored "-Wcast-qual" #endif execvp(ssh, (char **)args); exitf("%s: failed to execute %s: %s\n", argv0, ssh, strerror(errno)); }