From 991f7f569ce0212f46d1dd5d557eb5a36932f887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Ankarstr=C3=B6m?= Date: Fri, 16 Jul 2021 11:20:55 +0200 Subject: Add +x (escaped command) flag Here is a summary of the squashed commits: Fix comparison (line 55) Temporarily suspend SIGCHLD handler Support only one file argument (it gets WAY too complex otherwise) Clean up properly Clean up code Properly handle SIGINT during escaped command Abort escaped command if previous command failed --- Makefile | 2 + rtty.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 204 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 8992ccf..1068da9 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +CFLAGS += -Wall -pedantic + rtty: rtty.c install: rtty diff --git a/rtty.c b/rtty.c index dbb309e..12a9350 100644 --- a/rtty.c +++ b/rtty.c @@ -7,25 +7,32 @@ #include #include #include +#include #include #include #define INIT "export TERM=tty43 EDITOR=ed PAGER='pr -ptl22'\n" + #define MAXBUF 2048 +#define MAXEARG 32 +#define MAXWD 255 char *getpw(char *); -void sigchld(); +void noop(int); +void killall(int); int main(int argc, char *argv[]) { - char bufin[MAXBUF], bufout[MAXBUF], *bi, *bo, in[30], **nargv, - out[30], pw[255]; + char *address, bufin[MAXBUF], bufout[MAXBUF], dir[30], + *eargv[MAXEARG], in[30], **nargv, out[30], *p, pw[255], *q, + wd[MAXWD]; fd_set rfds0, rfds1; - int dumb, fdin, fdout, i, n, offset, sshpass; + int child, dumb, escape, fdin, fdout, i, n, offset, s, sshpass; struct timeval tv; - signal(SIGCHLD, sigchld); + signal(SIGINT, killall); + signal(SIGCHLD, killall); /* Create named pipes. */ sprintf(in, "/var/tmp/rtty.in.%d", getpid()); @@ -35,9 +42,10 @@ main(int argc, char *argv[]) err(1, "mkfifo"); /* Process rtty-specific flags. */ - dumb = sshpass = 0; + escape = dumb = sshpass = 0; for(;argv[1][0] == '+';){ if(strchr(argv[1], 'd')) dumb = 1; + if(strchr(argv[1], 'x')) escape = 1; if(strchr(argv[1], 'p')) sshpass = 1; /* Remove flag argument from argv. */ @@ -45,7 +53,15 @@ main(int argc, char *argv[]) argv++; argc--; } - /* Ask for password on +p. */ + /* Save pointer to address argument. */ + address = NULL; + for(i = 1; i < argc; i++) + if(argv[i][0] != '-'){ + address = argv[i]; + break; + } + + /* If +p is given, immediately ask for password. */ if(sshpass){ printf("password: "); getpw(pw); @@ -53,8 +69,12 @@ main(int argc, char *argv[]) sshpass = 1; } + /* + * A child ssh process is created, with standard in connected to + * the input pipe (fdin) and standard out connected to the output + * pipe (fdout). + */ if(fork() == 0){ - /* Redirect standard in, out. */ if(!(fdin = open(in, O_RDONLY))) err(1, "open"); if(!(fdout = open(out, O_WRONLY))) @@ -78,6 +98,8 @@ main(int argc, char *argv[]) nargv[i+offset] = argv[i]; nargv[argc+offset] = NULL; + signal(SIGINT, SIG_IGN); + /* Exec into ssh. */ execvp(nargv[0], nargv); err(1, "%s", nargv[0]); @@ -88,6 +110,7 @@ main(int argc, char *argv[]) if(!(fdout = open(out, O_RDONLY))) err(1, "open"); + /* Print initialization command. */ dprintf(fdin, INIT); FD_ZERO(&rfds0); @@ -98,15 +121,149 @@ main(int argc, char *argv[]) for(;;){ /* - * User input is read from standard in and copied the + * User input is read from standard in and copied to the * input pipe. It is saved in bufin for later use. */ FD_SET(0, &rfds0); if(select(0+1, &rfds0, NULL, NULL, &tv) > 0){ n = read(0, bufin, MAXBUF); bufin[n] = 0; - dprintf(fdin, "%s", bufin); - fflush(stdout); + + /* + * As +x is given, escaped commands are enabled, + * prefixed with !. Escaped commands are run + * locally, but operate on remote files. Any + * specified remote files will be downloaded and + * re-uploaded when the escaped command finishes. + * (Escaped commands are recognized only after + * shell prompt.) + */ + if(!(escape && bufin[0] == '!' + && (strcmp(bufout+strlen(bufout)-2, "$ ") == 0 + || strcmp(bufout+strlen(bufout)-2, "% ") == 0 + || strcmp(bufout+strlen(bufout)-2, "# ") == 0))){ + dprintf(fdin, "%s", bufin); + continue; + } + + /* + * The remote server is queried for the current + * working directory. The response contains the + * query ("pwd\n"), which is skipped, followed by + * the working directory ("/some/path\n") and then + * a shell prompt, which is skipped. + */ + dprintf(fdin, "pwd\n"); + for(i = 0; i < MAXWD-1; i++){ + read(fdout, wd, 1); + if(*wd == '\n') break; + } + n = read(fdout, wd, MAXWD); + wd[strcspn(wd, "\n\015")] = 0; + + /* Construct corresponding argument vector. */ + bufin[strcspn(bufin, "\n")] = 0; + p = bufin+1; + i = 0; + for(q = strtok(p, " "); q; + (q = strtok(NULL, " ")), i++) + if(i < MAXEARG-1) + eargv[i] = q; + eargv[i] = NULL; + + for(i = 1; eargv[i]; i++) + if(eargv[i][0] != '-' + && eargv[i][0] != '+') + goto found; + fprintf(stderr, "no file specified\n"); + goto done; +found: + if(eargv[i+1]){ + fprintf(stderr, + "more than one file given\n"); + goto done; + } + + /* Create a temporary local directory. */ + strcpy(dir, "/tmp/rtty.XXXXXX"); + if(!mkdtemp(dir)){ + warn("mkdtemp"); + continue; + } + chdir(dir); + + /* Construct argument to scp. */ + if(!(p = malloc(strlen(address)-1+strlen(wd)-1 + +strlen(eargv[i])-1+3))) + err(1, "malloc"); + if(eargv[i][0] == '/'){ + sprintf(p, "%s:%s", address, eargv[i]); + eargv[i]++; + }else + sprintf(p, "%s:%s/%s", address, wd, eargv[i]); + + /* + * During the execution of the external commands, + * the SIGCHLD handler is temporarily disabled + * and the SIGINT handler is set to do nothing. + */ + signal(SIGCHLD, NULL); + signal(SIGINT, noop); + + /* Download file. */ + if((child = fork()) == 0){ + execlp("scp", "scp", p, ".", NULL); + err(1, "scp"); + } + waitpid(child, &s, WALLSIG); + if(WIFEXITED(s) && WEXITSTATUS(s)){ + fprintf(stderr, "scp terminated abnormally\n"); + fprintf(stderr, "file saved in %s\n", dir); + goto done; + } + + /* Run escaped command. */ + if((child = fork()) == 0){ + signal(SIGINT, SIG_IGN); + execvp(eargv[0], eargv); + err(1, "%s", eargv[0]); + } + waitpid(child, &s, WALLSIG); + if(WIFEXITED(s) && WEXITSTATUS(s)){ + fprintf(stderr, "%s terminated abnormally\n", + eargv[0]); + fprintf(stderr, "file saved in %s\n", dir); + goto done; + } + + /* Upload file. */ + if((child = fork()) == 0){ + execlp("scp", "scp", eargv[i], p, NULL); + err(1, "scp"); + } + waitpid(child, &s, WALLSIG); + if(WIFEXITED(s) && WEXITSTATUS(s)){ + fprintf(stderr, "scp terminated abnormally\n"); + fprintf(stderr, "file saved in %s\n", dir); + goto done; + } + + /* Remove temporary directory. */ + unlink(eargv[i]); + rmdir(dir); + + /* Clean up. */ +done: + signal(SIGINT, killall); + signal(SIGCHLD, killall); + free(p); + + /* + * After an escaped command, an empty line is sent + * to the remote server, in order for a new shell + * prompt to be triggered. + */ + dprintf(fdin, "\n"); } /* @@ -116,40 +273,39 @@ main(int argc, char *argv[]) FD_SET(fdout, &rfds1); if(select(fdout+1, &rfds1, NULL, NULL, &tv) > 0){ n = read(fdout, bufout, MAXBUF); - if(!n) continue; bufout[n] = 0; /* * Bufin and bufout are copied to the temporary - * pointers bi and bo. Bo is incremented in order - * to skip any potential repetition of the command + * pointers p and q. p is incremented in order to + * skip any potential repetition of the command * given by the user on standard in. (It is assumed * that the typed command fits in bufin, i.e. is * not larger than MAXBUF.) */ - bi = bufin; - bo = bufout; + p = bufin; + q = bufout; for(;;){ - if(*bo == '\n'){ - bo++; + if(*q == '\n'){ + q++; break; } - if(*bo == 13){ - bo++; + if(*q == 13){ + q++; continue; } - if(*bi != *bo){ - bo = bufout; + if(*p != *q){ + q = bufout; break; } - bi++; bo++; + p++; q++; } /* * Bo is printed and bufin is cleared, so as not * to perform the skip again, incorrectly. */ - printf("%s", bo); + printf("%s", q); fflush(stdout); bufin[0] = 0; @@ -159,16 +315,16 @@ main(int argc, char *argv[]) */ if(dumb) continue; - if(bo[strlen(bo)-1] == ':' - || strcmp(bo+strlen(bo)-2, ": ") == 0){ - if(!(bo = strrchr(bufout, '\n'))) - bo = bufout; - if(strstr(bo, "password") - || strstr(bo, "Password") - || strstr(bo, "passphrase") - || strstr(bo, "Passphrase") - || strstr(bo, "pass phrase") - || strstr(bo, "Pass phrase")){ + if(q[strlen(q)-1] == ':' + || strcmp(q+strlen(q)-2, ": ") == 0){ + if(!(q = strrchr(bufout, '\n'))) + q = bufout; + if(strstr(q, "password") + || strstr(q, "Password") + || strstr(q, "passphrase") + || strstr(q, "Passphrase") + || strstr(q, "pass phrase") + || strstr(q, "Pass phrase")){ getpw(pw); dprintf(fdin, "%s\n", pw); } @@ -198,7 +354,17 @@ getpw(char *pw) } void -sigchld() +noop(int s) +{ + /* + * This handler is active in the parent when an escaped command + * is running. SIGINTs should not affect the parent in any way. + */ +} + +void +killall(int s) { - exit(0); + kill(0, SIGTERM); + exit(1); } -- cgit v1.2.3