aboutsummaryrefslogblamecommitdiffstats
path: root/sshexec.c
blob: 1ad2cf82afd89dfadf4ea1aa2dee9d5042f1ae44 (plain) (tree)



















                                                            
                                                                                         
                                                                                 

 






                           































                                                               
                                   


































































                                                                                 

                                                
                                
                        

















                                                                    
 




                                                             
































                                                                                                           




                                
 
















































                                                                                                   







                                                                     



















                                                                             
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


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));
}