aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ankarström <john@ankarstrom.se>2021-07-16 11:20:55 +0200
committerJohn Ankarström <john@ankarstrom.se>2021-07-17 12:22:57 +0200
commit991f7f569ce0212f46d1dd5d557eb5a36932f887 (patch)
tree321d450754cbc3e78cc613db0159b8fdfb4e05b9
parent78c46e3e6960f4bae0e2ec1aa2f1786715f5922c (diff)
downloadrtty-991f7f569ce0212f46d1dd5d557eb5a36932f887.tar.gz
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
-rw-r--r--Makefile2
-rw-r--r--rtty.c238
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 <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);
}