aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);
}