#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef PTYS_ARE_PTMX # include /* for struct rlimit */ # include /* for I_PUSH */ # define _NEW_TTY_CTRL /* to get proper defines in */ #endif #define MAX_TIMEOUT 240 /* #define DEBUG */ #ifndef FALSE #define FALSE (0) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #ifndef IGNORE #define IGNORE 1 #endif #ifndef SAVE #define SAVE 2 #endif #ifndef RESTORE #define RESTORE 3 #endif #ifndef O_NDELAY #define O_NDELAY O_NONBLOCK #endif extern char password[128]; extern char passphrase[128]; extern char * hom_str; extern void print_dbg (const char *fmt, ...); extern void get_pw(); extern void get_pp(); extern void privileges(int mode); extern pid_t child_pid; extern int opt_tarflag; static char *ttydev = NULL; /* pty/tty name */ static struct stat ttyfd_stat; /* original status of the tty we will use */ static short changettyowner = 1; /* ssh reads the password not from stdin, but from /dev/tty * therefore, we put the subprocess on a virtual console (what a pain ...) */ /* the following two subroutines are from rxvt */ /*{{{ Acquire a pseudo-teletype from the system. */ /* * On failure, returns -1. * On success, returns the file descriptor. * * If successful, ttydev and ptydev point to the names of the * master and slave parts */ /* INTPROTO */ static int get_pty(void) { int fd; char *ptydev; #ifdef PTYS_ARE__GETPTY if ((ptydev = ttydev = _getpty(&fd, O_RDWR | O_NDELAY, 0622, 0)) != NULL) goto Found; #endif #ifdef PTYS_ARE_GETPTY while ((ptydev = getpty()) != NULL) if ((fd = open(ptydev, O_RDWR)) >= 0) { ttydev = ptydev; goto Found; } #endif #if defined(HAVE_GRANTPT) && defined(HAVE_UNLOCKPT) # if defined(PTYS_ARE_GETPT) || defined(PTYS_ARE_PTMX) { /*@i@*/ extern char *ptsname(); # ifdef PTYS_ARE_GETPT /*@-unrecog@*/ if ((fd = getpt()) >= 0) { /*@+unrecog@*/ # else if ((fd = open("/dev/ptmx", O_RDWR)) >= 0) { # endif if (grantpt(fd) == 0 /* change slave permissions */ && unlockpt(fd) == 0) { /* slave now unlocked */ ptydev = ttydev = ptsname(fd); /* get slave's name */ changettyowner = 0; goto Found; } close(fd); } } # endif #endif #ifdef PTYS_ARE_PTC if ((fd = open("/dev/ptc", O_RDWR)) >= 0) { ptydev = ttydev = ttyname(fd); goto Found; } #endif #ifdef PTYS_ARE_CLONE if ((fd = open("/dev/ptym/clone", O_RDWR)) >= 0) { ptydev = ttydev = ptsname(fd); goto Found; } #endif #ifdef PTYS_ARE_NUMERIC { int idx; char *c1, *c2; char pty_name[] = "/dev/ptyp???"; char tty_name[] = "/dev/ttyp???"; ptydev = pty_name; ttydev = tty_name; c1 = &(pty_name[sizeof(pty_name) - 4]); c2 = &(tty_name[sizeof(tty_name) - 4]); for (idx = 0; idx < 256; idx++) { sprintf(c1, "%d", idx); sprintf(c2, "%d", idx); if (access(ttydev, F_OK) < 0) { idx = 256; break; } if ((fd = open(ptydev, O_RDWR)) >= 0) { if (access(ttydev, R_OK | W_OK) == 0) { ttydev = strdup(tty_name); goto Found; } close(fd); } } } #endif #ifdef PTYS_ARE_SEARCHED { size_t len; const char *c1, *c2; char pty_name[] = "/dev/pty??"; char tty_name[] = "/dev/tty??"; len = sizeof(pty_name) - 3; ptydev = pty_name; ttydev = tty_name; # define PTYCHAR1 "pqrstuvwxyz" # define PTYCHAR2 "0123456789abcdef" for (c1 = PTYCHAR1; *c1; c1++) { ptydev[len] = ttydev[len] = *c1; for (c2 = PTYCHAR2; *c2; c2++) { ptydev[len + 1] = ttydev[len + 1] = *c2; fd = open(ptydev, O_RDWR); /* fprintf(stderr, "%s %s %d\n", ttydev, ptydev, fd); */ if (fd >= 0) { if (access(ttydev, R_OK | W_OK) == 0) { ttydev = strdup(tty_name); goto Found; } close(fd); } } } } #endif print_dbg("can't open pseudo-tty"); return -1; Found: fcntl(fd, F_SETFL, O_NDELAY); return fd; } /*}}} */ /*{{{ establish a controlling teletype for new session */ /* * On some systems this can be done with ioctl() but on others we * need to re-open the slave tty. */ /* INTPROTO */ static int get_tty(void) { int fd, i; pid_t pid; /* * setsid() [or setpgrp] must be before open of the terminal, * otherwise there is no controlling terminal (Solaris 2.4, HP-UX 9) */ #ifndef ultrix # ifdef NO_SETSID pid = setpgrp(0, 0); # else pid = setsid(); # endif if (pid < 0) perror("get_tty: setsid: "); # ifdef DEBUG_TTYMODE print_dbg("(%s: line %d): PID = %d\n", __FILE__, __LINE__, pid); # endif #endif /* ultrix */ if ((fd = open(ttydev, O_RDWR)) < 0 && (fd = open("/dev/tty", O_RDWR)) < 0) { print_dbg("can't open slave tty %s", ttydev); exit(EXIT_FAILURE); } #ifdef PTYS_ARE_PTMX /* * Push STREAMS modules: * ptem: pseudo-terminal hardware emulation module. * ldterm: standard terminal line discipline. * ttcompat: V7, 4BSD and XENIX STREAMS compatibility module. */ if (!changettyowner) { ioctl(fd, I_PUSH, "ptem"); ioctl(fd, I_PUSH, "ldterm"); #if !defined(sgi) && !defined(__sgi) ioctl(fd, I_PUSH, "ttcompat"); #endif } #endif if (changettyowner) { /* change ownership of tty to real uid and real group */ unsigned int mode = 0622; gid_t gid = getgid(); #ifdef TTY_GID_SUPPORT { struct group *gr = getgrnam("tty"); if (gr) { /* change ownership of tty to real uid, "tty" gid */ gid = gr->gr_gid; mode = 0620; } } #endif /* TTY_GID_SUPPORT */ #ifndef __CYGWIN32__ { privileges(RESTORE); i = fchown(fd, getuid(), gid); /* fprintf(stderr, "fchown fd=%d uid=%d, gid=%d, res=%d\n", fd, (int) getuid(), (int) gid, i); */ i = fchmod(fd, mode); /* fprintf(stderr, "fchmod fd=%d res=%d\n", fd, i); */ privileges(IGNORE); } #endif } /* * Close all file descriptors. If only stdin/out/err are closed, * child processes remain alive upon deletion of the window. */ /* for (i = 3; i < 256; i++) if (i != fd) close(i); */ /* Reopen stdin, stdout and stderr over the tty file descriptor */ dup2(fd, 0); /* stdin */ #ifdef OPEN_SSH_D dup2(fd, 1); /* stdout */ /* TRY */ dup2(fd, 2); /* stderr */ /* TRY */ #endif if (fd > 2) close(fd); #ifdef ultrix if ((fd = open("/dev/tty", O_RDONLY)) >= 0) { ioctl(fd, /*@i@*/ TIOCNOTTY, 0); close(fd); } else { pid = setpgrp(0, 0); if (pid < 0) perror("get_tty: setpgrp: "); } /* no error, we could run with no tty to begin with */ #else /* ultrix */ # ifdef TIOCSCTTY ioctl(0, /*@i@*/ TIOCSCTTY, 0); # endif /* set process group */ # if defined (_POSIX_VERSION) || defined (__svr4__) tcsetpgrp(0, pid); # elif defined (TIOCSPGRP) ioctl(0, /*@i@*/ TIOCSPGRP, &pid); # endif /* svr4 problems: reports no tty, no job control */ /* # if !defined (__svr4__) && defined (TIOCSPGRP) */ close(open(ttydev, O_RDWR, 0)); /* # endif */ #endif /* ultrix */ return fd; } /*}}} */ static void restore_tty (char * slave) { /* fail silently if unprivileged */ privileges (RESTORE); chmod(slave, ttyfd_stat.st_mode); chown(slave, ttyfd_stat.st_uid, ttyfd_stat.st_gid); privileges (IGNORE); return; } static int errflag = 0; /* Read the ssh subprocess response. Braindead ssh expects password * from /dev/tty, and does not fallback on stdin. * Even more braindead: OpenSSH **PRINTS** the password prompt to /dev/tty !! */ static char * fdgets (char * buf, int size, int fd, int fderr #ifdef OPEN_SSH , int fdmaster #endif ) { fd_set my_set; struct timeval tv; char * p; int countbytes; ssize_t count; int select_now; int num_sel; int num_err; int status; long exstat; int maxnum; errflag = 0; countbytes = 0; select_now = 0; tv.tv_sec = 1; tv.tv_usec = 0; buf[0] = '\0'; p = buf; if (fd > fderr) maxnum = fd; else maxnum = fderr; #ifdef OPEN_SSH if (maxnum > fdmaster) maxnum = maxnum; else maxnum = fdmaster; #endif while ( countbytes < (size - 2)) { FD_ZERO(&my_set); FD_SET(fd, &my_set); FD_SET(fderr, &my_set); #ifdef OPEN_SSH FD_SET(fdmaster, &my_set); #endif if ( (num_sel = select (maxnum+1, &my_set, NULL, NULL, &tv)) == -1) { if ( errno == EINTR ) /* try again */ continue; num_err = errno; fprintf (stderr, "Error in select(): %s\n", strerror(num_err)); buf[countbytes] = '\0'; return NULL; } /* on Linux, timeout is modified */ tv.tv_sec = 1; tv.tv_usec = 0; #ifdef DEBUG fprintf (stderr, "SE: %d selected.\n", num_sel); #endif if (num_sel == 0) /* timeout */ { if (NULL != strstr (buf, "password: ") ) { buf[countbytes] = '\0'; return buf; } if (NULL != strstr (buf, "Enter passphrase") ) { buf[countbytes] = '\0'; return buf; } if (NULL != strstr (buf, "(yes/no)? ") ) { buf[countbytes] = '\0'; return buf; } ++select_now; if ( select_now > MAX_TIMEOUT ) { fprintf (stderr, "Timeout after %d seconds.\n", MAX_TIMEOUT); buf[countbytes] = '\0'; if (countbytes == 0) return NULL; else return buf; } } if ( FD_ISSET (fd, &my_set)) { count = read (fd, p, 1); /* --- READ --- */ if (count > 0) { countbytes += count; #ifdef DEBUG fprintf (stderr, "S-: |%c|\n", p); #endif if (*p == '\n') /* newline, return */ { buf[countbytes] = '\0'; return buf; } p += count; /* move buffer pointer forward */ FD_SET( fd, &my_set ); } else if (count < 0) { num_err = errno; if (0 < (exstat = (long) waitpid (child_pid, &status, WNOHANG|WUNTRACED))) { /* Child has exited. Check exit value. */ child_pid = 0; if (WIFEXITED(status) != 0 && 0 != WEXITSTATUS(status)) fprintf (stderr, "return code: %d\n", WEXITSTATUS(status)); } else { fprintf (stderr, "Error reading from pipe: %s\n", strerror(num_err)); } buf[countbytes] = '\0'; return NULL; } else /* count == 0; broken pipe -> */ { num_err = errno; buf[countbytes] = '\0'; /* Child has exited. Check exit value. */ if (0 < waitpid (child_pid, &status, WNOHANG|WUNTRACED)) { if (WIFEXITED(status) != 0 && 0 != WEXITSTATUS(status)) fprintf (stderr, "return code: %d\n", WEXITSTATUS(status)); } child_pid = 0; return NULL; } } if ( FD_ISSET (fderr, &my_set) #ifdef OPEN_SSH || FD_ISSET (fdmaster, &my_set) #endif ) { #ifdef OPEN_SSH if (FD_ISSET (fdmaster, &my_set)) count = read (fdmaster, p, 1); else #endif count = read (fderr, p, 1); /* --- READ --- */ #ifdef DEBUG fprintf (stderr, "E-: |%c|\n", p); #endif if (count > 0) { countbytes += count; if (*p == '\n') /* newline, return */ { buf[countbytes] = '\0'; errflag = 1; return buf; } p += count; /* move buffer pointer forward */ FD_SET( fderr, &my_set ); } else if (count < 0) { num_err = errno; /* Child has exited. Check exit value. */ if (0 < (exstat = (long) waitpid (child_pid, &status, WNOHANG|WUNTRACED))) { if (WIFEXITED(status) != 0 && 0 != WEXITSTATUS(status)) fprintf (stderr, "return code: %d\n", WEXITSTATUS(status)); } else { fprintf (stderr, "Error reading from pipe: %s\n", strerror(num_err)); } buf[countbytes] = '\0'; errflag = 1; return NULL; } else /* count == 0; broken pipe -> */ { ; /* do nothing */ } } } buf[countbytes] = '\0'; return buf; } static int user_handler(int mode, int master, int input_pipe[], int output_pipe[], int inerr_pipe[]) { ssize_t rc; /* return values of functions. */ char line[256]; char * p; char c; int i = 0; /* first, close unnecessary file descriptors */ close ( input_pipe[1]); /* we don't need to write to this pipe. */ close ( inerr_pipe[1]); /* we don't need to write to this pipe. */ close (output_pipe[0]); /* we don't need to read from this pipe. */ fcntl (input_pipe[0], F_SETFL, O_NONBLOCK); fcntl (inerr_pipe[0], F_SETFL, O_NONBLOCK); /* loop: read input, send via one pipe, read via other */ /* pipe, and write to stdout. exit on EOF from user. */ while ( NULL != fdgets (line, 256, input_pipe[0], inerr_pipe[0] #ifdef OPEN_SSH , master #endif ) ) { #ifdef DEBUG print_dbg ("line in: [%d] <%s> \n", strlen(line), line); #endif if (NULL != strstr (line, "password: ") ) { /* 'master' is the controlling terminal for * the spawned subprocess that runs ssh */ if (password[0] == '\0') { get_pw(); } rc = write(master, password, strlen(password)); rc += write(master, "\n", 1); if (rc <= -1) { perror("user_handler: write"); close(input_pipe[0]); close(output_pipe[1]); memset (password, 0, 128); memset (passphrase, 0, 128); return -2; } print_dbg ("Password sent: %d bytes.", rc-1); continue; } if (NULL != strstr (line, "Enter passphrase") ) { /* 'master' is the controlling terminal for * the spawned subprocess that runs ssh */ if (passphrase[0] == '\0') { get_pp(line); } rc = write(master, passphrase, strlen(passphrase)); rc = write(master, "\n", 1); if (rc == -1) { perror("user_handler: write"); close(input_pipe[0]); close(output_pipe[1]); memset (password, 0, 128); memset (passphrase, 0, 128); return -2; } print_dbg ("Using passphrase to unlock private key."); continue; } if (NULL != strstr (line, "(yes/no)? ") ) { /* 'master' is the controlling terminal for * the spawned subprocess that runs ssh */ rc = write(master, "yes", 3); rc = write(master, "\n", 1); if (rc == -1) { perror("user_handler: write"); close(input_pipe[0]); close(output_pipe[1]); memset (password, 0, 128); memset (passphrase, 0, 128); return -2; } print_dbg ("Login confirmation sent."); continue; } if (errflag == 0 && mode == 0) { strncpy(hom_str, line, 256); hom_str[255] = '\0'; p = &hom_str[strlen(hom_str)-1]; while (*p == ' ' || *p == '\n') *p = '\0'; print_dbg ("Received path of home directory <%s>", hom_str); close(input_pipe[0]); close(inerr_pipe[0]); close(output_pipe[1]); close(master); return 0; } if (errflag == 1 && line[0] != '\n') { #ifdef OPEN_SSH if (strlen(line) == 2) continue; #endif if (line[strlen(line)-1] == '\n') fprintf(stderr, "%s", line); else fprintf(stderr, "%s\n", line); /* Permission denied. */ if (NULL != strstr (line, "Permission denied")) { memset (password, 0, 128); memset (passphrase, 0, 128); exit (EXIT_FAILURE); } /* stty thingy */ if (NULL != strstr (line, "stty:")) continue; /* more fun */ if (NULL != strstr (line, "Trying")) continue; /* more fun */ if (NULL != strstr (line, "Logging in")) continue; /* bad passphrase */ if (NULL != strstr (line, "Bad passphrase")) continue; /* new seed */ if (NULL != strstr (line, "Creating random seed file")) continue; /* blurb */ if (NULL != strstr (line, "Received signal")) continue; /* blurb */ if (NULL != strstr (line, "OpenSSH") && NULL != strstr (line, "SSH protocols")) continue; /* new host */ if (NULL != strstr (line, "list of known hosts")) continue; /* tar */ if (NULL != strstr (line, "tar: B: unknown option")) { opt_tarflag = FALSE; close(input_pipe[0]); close(inerr_pipe[0]); close(output_pipe[1]); close(master); return -1; } /* some other error */ fprintf(stderr, "Continue (Y/n) ? "); while (1 == 1) { c = getc(stdin); if (c == 'Y' || c == 'y' || c == '\n') break; else if (c == 'N' || c == 'n') { close(input_pipe[0]); close(inerr_pipe[0]); close(output_pipe[1]); close(master); return -2; } else { fprintf(stderr, "Continue (Y/n) ? "); } } continue; } if (errflag == 0 && mode == 1) { p = &line[strlen(line)-1]; while (*p) { if (*p == '\n') *p = '\0'; --p; } if (line[strlen(line)-1] == ':') { printf("\n\n%s\n", line); } else if (strlen(line) > 40) { printf("%s\n", line); } else { printf("%s", line); if (strlen(line) < 8) printf("\t\t\t"); else if (strlen(line) < 16) printf("\t\t"); else printf("\t"); ++i; if (i == 3) { fputc('\n', stdout); i = 0; } } continue; } /* default */ if (errflag == 0) fprintf(stdout, "%s", line); } if (mode == 1) fputc('\n', stdout); /* close pipes and exit. */ close(input_pipe[0]); close(inerr_pipe[0]); close(output_pipe[1]); close(master); return 0; } /* now comes the function executed by the translator process. */ static void translator(char * command, int input_pipe[], int output_pipe[], int outerr_pipe[]) { #ifdef DEBUG_P char blah[256]; #endif /* first, close unnecessary file descriptors */ close ( input_pipe[1]); /* we don't need to write to this pipe. */ close (output_pipe[0]); /* we don't need to read from this pipe. */ close (outerr_pipe[0]); /* we don't need to read from this pipe. */ fcntl (input_pipe[0], F_SETFL, O_NONBLOCK); dup2(outerr_pipe[1], STDERR_FILENO ); dup2(output_pipe[1], STDOUT_FILENO ); #ifdef OPEN_SSH_D dup2( input_pipe[0], STDIN_FILENO ); /* TRY */ #endif #ifdef DEBUG_P fputs ("password: ", stdout); fflush (stdout); fputs (fgets(blah, 255, stdin), stdout); #endif execl("/bin/sh", "sh", "-c", command, NULL); fflush(stdout); sleep(1); _exit (127); } /* spawn off the subprocess to run ssh */ int piper (char * command, int mode) { int master; int slave; /* slave pty */ int retval = 0; /* 2 arrays to contain file descriptors, for two pipes. */ int user_to_translator[2]; int translator_to_user[2]; int err_to_user[2]; pid_t pid; /* pid of child process, or 0 in parent thread. */ int rc; if (command == NULL) { fprintf(stderr, "** ERROR **: No command given !"); return EXIT_FAILURE; } if ((master = get_pty()) < 0) { perror("ptypair: could not open master pty"); exit (EXIT_FAILURE); } lstat(ttydev, &ttyfd_stat); /* first, create one pipe. */ rc = pipe(user_to_translator); if (rc == -1) { perror("main: pipe user_to_translator"); exit (EXIT_FAILURE); } /* then, create another pipe. */ rc = pipe(translator_to_user); if (rc == -1) { perror("main: pipe translator_to_user"); exit (EXIT_FAILURE); } /* then, create yet another pipe. */ rc = pipe(err_to_user); if (rc == -1) { perror("main: pipe err_to_user"); exit (EXIT_FAILURE); } /* now fork off a child process, and set their handling routines. */ pid = fork(); switch (pid) { case -1: /* fork failed. */ perror("main: fork"); memset (password, 0, 128); memset (passphrase, 0, 128); exit (EXIT_FAILURE); case 0: /* child process. */ /* page lock is not inherited after * the fork */ memset (password, 0, 128); memset (passphrase, 0, 128); /* We are in the child process */ /**************************************** close(master); if (setsid() < 0) { perror("could not set session leader"); } if ((slave = get_slave_pty(name)) < 0) { perror("ptypair: could not open slave tty"); fflush(stdout); _exit (EXIT_FAILURE); } #ifdef TIOCSCTTY if (ioctl(slave, TIOCSCTTY, NULL)) { perror("could not set new controlling tty"); } #endif dup2(slave, STDIN_FILENO); dup2(slave, 1); dup2(slave, 2); if (slave > 2) close(slave); *************************************/ /* establish a controlling teletype for the new session */ slave = get_tty(); close(master); translator (command, user_to_translator, translator_to_user, err_to_user); break; default: /* parent process. */ child_pid = pid; retval = user_handler (mode, master, translator_to_user, user_to_translator, err_to_user); break; } /* just to be sure */ (void) waitpid (-1, NULL, WNOHANG|WUNTRACED); child_pid = 0; close(master); restore_tty(ttydev); return retval; }