diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | rtty.c | 238 |
2 files changed, 204 insertions, 36 deletions
@@ -1,3 +1,5 @@ +CFLAGS += -Wall -pedantic + rtty: rtty.c install: rtty @@ -7,25 +7,32 @@ #include <string.h> #include <sys/select.h> #include <sys/stat.h> +#include <sys/wait.h> #include <termios.h> #include <unistd.h> #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); } |