+++ /dev/null
-/*\r
- * Pseudo-tty backend for pterm.\r
- */\r
-\r
-#define _GNU_SOURCE\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <unistd.h>\r
-#include <signal.h>\r
-#include <assert.h>\r
-#include <fcntl.h>\r
-#include <termios.h>\r
-#include <grp.h>\r
-#include <utmp.h>\r
-#include <pwd.h>\r
-#include <time.h>\r
-#include <sys/types.h>\r
-#include <sys/stat.h>\r
-#include <sys/wait.h>\r
-#include <sys/ioctl.h>\r
-#include <errno.h>\r
-\r
-#include "putty.h"\r
-#include "tree234.h"\r
-\r
-#ifndef OMIT_UTMP\r
-#include <utmpx.h>\r
-#endif\r
-\r
-#ifndef FALSE\r
-#define FALSE 0\r
-#endif\r
-#ifndef TRUE\r
-#define TRUE 1\r
-#endif\r
-\r
-/* updwtmpx() needs the name of the wtmp file. Try to find it. */\r
-#ifndef WTMPX_FILE\r
-#ifdef _PATH_WTMPX\r
-#define WTMPX_FILE _PATH_WTMPX\r
-#else\r
-#define WTMPX_FILE "/var/log/wtmpx"\r
-#endif\r
-#endif\r
-\r
-#ifndef LASTLOG_FILE\r
-#ifdef _PATH_LASTLOG\r
-#define LASTLOG_FILE _PATH_LASTLOG\r
-#else\r
-#define LASTLOG_FILE "/var/log/lastlog"\r
-#endif\r
-#endif\r
-\r
-/*\r
- * Set up a default for vaguely sane systems. The idea is that if\r
- * OMIT_UTMP is not defined, then at least one of the symbols which\r
- * enable particular forms of utmp processing should be, if only so\r
- * that a link error can warn you that you should have defined\r
- * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is\r
- * the only such symbol.\r
- */\r
-#ifndef OMIT_UTMP\r
-#if !defined HAVE_PUTUTLINE\r
-#define HAVE_PUTUTLINE\r
-#endif\r
-#endif\r
-\r
-typedef struct pty_tag *Pty;\r
-\r
-/*\r
- * The pty_signal_pipe, along with the SIGCHLD handler, must be\r
- * process-global rather than session-specific.\r
- */\r
-static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */\r
-\r
-struct pty_tag {\r
- Config cfg;\r
- int master_fd, slave_fd;\r
- void *frontend;\r
- char name[FILENAME_MAX];\r
- pid_t child_pid;\r
- int term_width, term_height;\r
- int child_dead, finished;\r
- int exit_code;\r
- bufchain output_data;\r
-};\r
-\r
-/*\r
- * We store our pty backends in a tree sorted by master fd, so that\r
- * when we get an uxsel notification we know which backend instance\r
- * is the owner of the pty that caused it.\r
- */\r
-static int pty_compare_by_fd(void *av, void *bv)\r
-{\r
- Pty a = (Pty)av;\r
- Pty b = (Pty)bv;\r
-\r
- if (a->master_fd < b->master_fd)\r
- return -1;\r
- else if (a->master_fd > b->master_fd)\r
- return +1;\r
- return 0;\r
-}\r
-\r
-static int pty_find_by_fd(void *av, void *bv)\r
-{\r
- int a = *(int *)av;\r
- Pty b = (Pty)bv;\r
-\r
- if (a < b->master_fd)\r
- return -1;\r
- else if (a > b->master_fd)\r
- return +1;\r
- return 0;\r
-}\r
-\r
-static tree234 *ptys_by_fd = NULL;\r
-\r
-/*\r
- * We also have a tree sorted by child pid, so that when we wait()\r
- * in response to the signal we know which backend instance is the\r
- * owner of the process that caused the signal.\r
- */\r
-static int pty_compare_by_pid(void *av, void *bv)\r
-{\r
- Pty a = (Pty)av;\r
- Pty b = (Pty)bv;\r
-\r
- if (a->child_pid < b->child_pid)\r
- return -1;\r
- else if (a->child_pid > b->child_pid)\r
- return +1;\r
- return 0;\r
-}\r
-\r
-static int pty_find_by_pid(void *av, void *bv)\r
-{\r
- pid_t a = *(pid_t *)av;\r
- Pty b = (Pty)bv;\r
-\r
- if (a < b->child_pid)\r
- return -1;\r
- else if (a > b->child_pid)\r
- return +1;\r
- return 0;\r
-}\r
-\r
-static tree234 *ptys_by_pid = NULL;\r
-\r
-/*\r
- * If we are using pty_pre_init(), it will need to have already\r
- * allocated a pty structure, which we must then return from\r
- * pty_init() rather than allocating a new one. Here we store that\r
- * structure between allocation and use.\r
- * \r
- * Note that although most of this module is entirely capable of\r
- * handling multiple ptys in a single process, pty_pre_init() is\r
- * fundamentally _dependent_ on there being at most one pty per\r
- * process, so the normal static-data constraints don't apply.\r
- * \r
- * Likewise, since utmp is only used via pty_pre_init, it too must\r
- * be single-instance, so we can declare utmp-related variables\r
- * here.\r
- */\r
-static Pty single_pty = NULL;\r
-\r
-#ifndef OMIT_UTMP\r
-static pid_t pty_utmp_helper_pid;\r
-static int pty_utmp_helper_pipe;\r
-static int pty_stamped_utmp;\r
-static struct utmpx utmp_entry;\r
-#endif\r
-\r
-/*\r
- * pty_argv is a grievous hack to allow a proper argv to be passed\r
- * through from the Unix command line. Again, it doesn't really\r
- * make sense outside a one-pty-per-process setup.\r
- */\r
-char **pty_argv;\r
-\r
-static void pty_close(Pty pty);\r
-static void pty_try_write(Pty pty);\r
-\r
-#ifndef OMIT_UTMP\r
-static void setup_utmp(char *ttyname, char *location)\r
-{\r
-#ifdef HAVE_LASTLOG\r
- struct lastlog lastlog_entry;\r
- FILE *lastlog;\r
-#endif\r
- struct passwd *pw;\r
- struct timeval tv;\r
-\r
- pw = getpwuid(getuid());\r
- memset(&utmp_entry, 0, sizeof(utmp_entry));\r
- utmp_entry.ut_type = USER_PROCESS;\r
- utmp_entry.ut_pid = getpid();\r
- strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));\r
- strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));\r
- strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));\r
- strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));\r
- /*\r
- * Apparently there are some architectures where (struct\r
- * utmpx).ut_tv is not essentially struct timeval (e.g. Linux\r
- * amd64). Hence the temporary.\r
- */\r
- gettimeofday(&tv, NULL);\r
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;\r
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;\r
-\r
- setutxent();\r
- pututxline(&utmp_entry);\r
- endutxent();\r
-\r
- updwtmpx(WTMPX_FILE, &utmp_entry);\r
-\r
-#ifdef HAVE_LASTLOG\r
- memset(&lastlog_entry, 0, sizeof(lastlog_entry));\r
- strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));\r
- strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));\r
- time(&lastlog_entry.ll_time);\r
- if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {\r
- fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);\r
- fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);\r
- fclose(lastlog);\r
- }\r
-#endif\r
-\r
- pty_stamped_utmp = 1;\r
-\r
-}\r
-\r
-static void cleanup_utmp(void)\r
-{\r
- struct timeval tv;\r
-\r
- if (!pty_stamped_utmp)\r
- return;\r
-\r
- utmp_entry.ut_type = DEAD_PROCESS;\r
- memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));\r
- gettimeofday(&tv, NULL);\r
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;\r
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;\r
-\r
- updwtmpx(WTMPX_FILE, &utmp_entry);\r
-\r
- memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));\r
- utmp_entry.ut_tv.tv_sec = 0;\r
- utmp_entry.ut_tv.tv_usec = 0;\r
-\r
- setutxent();\r
- pututxline(&utmp_entry);\r
- endutxent();\r
-\r
- pty_stamped_utmp = 0; /* ensure we never double-cleanup */\r
-}\r
-#endif\r
-\r
-static void sigchld_handler(int signum)\r
-{\r
- if (write(pty_signal_pipe[1], "x", 1) <= 0)\r
- /* not much we can do about it */;\r
-}\r
-\r
-#ifndef OMIT_UTMP\r
-static void fatal_sig_handler(int signum)\r
-{\r
- putty_signal(signum, SIG_DFL);\r
- cleanup_utmp();\r
- setuid(getuid());\r
- raise(signum);\r
-}\r
-#endif\r
-\r
-static int pty_open_slave(Pty pty)\r
-{\r
- if (pty->slave_fd < 0) {\r
- pty->slave_fd = open(pty->name, O_RDWR);\r
- cloexec(pty->slave_fd);\r
- }\r
-\r
- return pty->slave_fd;\r
-}\r
-\r
-static void pty_open_master(Pty pty)\r
-{\r
-#ifdef BSD_PTYS\r
- const char chars1[] = "pqrstuvwxyz";\r
- const char chars2[] = "0123456789abcdef";\r
- const char *p1, *p2;\r
- char master_name[20];\r
- struct group *gp;\r
-\r
- for (p1 = chars1; *p1; p1++)\r
- for (p2 = chars2; *p2; p2++) {\r
- sprintf(master_name, "/dev/pty%c%c", *p1, *p2);\r
- pty->master_fd = open(master_name, O_RDWR);\r
- if (pty->master_fd >= 0) {\r
- if (geteuid() == 0 ||\r
- access(master_name, R_OK | W_OK) == 0) {\r
- /*\r
- * We must also check at this point that we are\r
- * able to open the slave side of the pty. We\r
- * wouldn't want to allocate the wrong master,\r
- * get all the way down to forking, and _then_\r
- * find we're unable to open the slave.\r
- */\r
- strcpy(pty->name, master_name);\r
- pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */\r
-\r
- cloexec(pty->master_fd);\r
-\r
- if (pty_open_slave(pty) >= 0 &&\r
- access(pty->name, R_OK | W_OK) == 0)\r
- goto got_one;\r
- if (pty->slave_fd > 0)\r
- close(pty->slave_fd);\r
- pty->slave_fd = -1;\r
- }\r
- close(pty->master_fd);\r
- }\r
- }\r
-\r
- /* If we get here, we couldn't get a tty at all. */\r
- fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");\r
- exit(1);\r
-\r
- got_one:\r
-\r
- /* We need to chown/chmod the /dev/ttyXX device. */\r
- gp = getgrnam("tty");\r
- chown(pty->name, getuid(), gp ? gp->gr_gid : -1);\r
- chmod(pty->name, 0600);\r
-#else\r
- pty->master_fd = open("/dev/ptmx", O_RDWR);\r
-\r
- if (pty->master_fd < 0) {\r
- perror("/dev/ptmx: open");\r
- exit(1);\r
- }\r
-\r
- if (grantpt(pty->master_fd) < 0) {\r
- perror("grantpt");\r
- exit(1);\r
- }\r
- \r
- if (unlockpt(pty->master_fd) < 0) {\r
- perror("unlockpt");\r
- exit(1);\r
- }\r
-\r
- cloexec(pty->master_fd);\r
-\r
- pty->name[FILENAME_MAX-1] = '\0';\r
- strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);\r
-#endif\r
-\r
- {\r
- /*\r
- * Set the pty master into non-blocking mode.\r
- */\r
- int fl;\r
- fl = fcntl(pty->master_fd, F_GETFL);\r
- if (fl != -1 && !(fl & O_NONBLOCK))\r
- fcntl(pty->master_fd, F_SETFL, fl | O_NONBLOCK);\r
- }\r
-\r
- if (!ptys_by_fd)\r
- ptys_by_fd = newtree234(pty_compare_by_fd);\r
- add234(ptys_by_fd, pty);\r
-}\r
-\r
-/*\r
- * Pre-initialisation. This is here to get around the fact that GTK\r
- * doesn't like being run in setuid/setgid programs (probably\r
- * sensibly). So before we initialise GTK - and therefore before we\r
- * even process the command line - we check to see if we're running\r
- * set[ug]id. If so, we open our pty master _now_, chown it as\r
- * necessary, and drop privileges. We can always close it again\r
- * later. If we're potentially going to be doing utmp as well, we\r
- * also fork off a utmp helper process and communicate with it by\r
- * means of a pipe; the utmp helper will keep privileges in order\r
- * to clean up utmp when we exit (i.e. when its end of our pipe\r
- * closes).\r
- */\r
-void pty_pre_init(void)\r
-{\r
- Pty pty;\r
-\r
-#ifndef OMIT_UTMP\r
- pid_t pid;\r
- int pipefd[2];\r
-#endif\r
-\r
- pty = single_pty = snew(struct pty_tag);\r
- bufchain_init(&pty->output_data);\r
-\r
- /* set the child signal handler straight away; it needs to be set\r
- * before we ever fork. */\r
- putty_signal(SIGCHLD, sigchld_handler);\r
- pty->master_fd = pty->slave_fd = -1;\r
-#ifndef OMIT_UTMP\r
- pty_stamped_utmp = FALSE;\r
-#endif\r
-\r
- if (geteuid() != getuid() || getegid() != getgid()) {\r
- pty_open_master(pty);\r
- }\r
-\r
-#ifndef OMIT_UTMP\r
- /*\r
- * Fork off the utmp helper.\r
- */\r
- if (pipe(pipefd) < 0) {\r
- perror("pterm: pipe");\r
- exit(1);\r
- }\r
- cloexec(pipefd[0]);\r
- cloexec(pipefd[1]);\r
- pid = fork();\r
- if (pid < 0) {\r
- perror("pterm: fork");\r
- exit(1);\r
- } else if (pid == 0) {\r
- char display[128], buffer[128];\r
- int dlen, ret;\r
-\r
- close(pipefd[1]);\r
- /*\r
- * Now sit here until we receive a display name from the\r
- * other end of the pipe, and then stamp utmp. Unstamp utmp\r
- * again, and exit, when the pipe closes.\r
- */\r
-\r
- dlen = 0;\r
- while (1) {\r
- \r
- ret = read(pipefd[0], buffer, lenof(buffer));\r
- if (ret <= 0) {\r
- cleanup_utmp();\r
- _exit(0);\r
- } else if (!pty_stamped_utmp) {\r
- if (dlen < lenof(display))\r
- memcpy(display+dlen, buffer,\r
- min(ret, lenof(display)-dlen));\r
- if (buffer[ret-1] == '\0') {\r
- /*\r
- * Now we have a display name. NUL-terminate\r
- * it, and stamp utmp.\r
- */\r
- display[lenof(display)-1] = '\0';\r
- /*\r
- * Trap as many fatal signals as we can in the\r
- * hope of having the best possible chance to\r
- * clean up utmp before termination. We are\r
- * unfortunately unprotected against SIGKILL,\r
- * but that's life.\r
- */\r
- putty_signal(SIGHUP, fatal_sig_handler);\r
- putty_signal(SIGINT, fatal_sig_handler);\r
- putty_signal(SIGQUIT, fatal_sig_handler);\r
- putty_signal(SIGILL, fatal_sig_handler);\r
- putty_signal(SIGABRT, fatal_sig_handler);\r
- putty_signal(SIGFPE, fatal_sig_handler);\r
- putty_signal(SIGPIPE, fatal_sig_handler);\r
- putty_signal(SIGALRM, fatal_sig_handler);\r
- putty_signal(SIGTERM, fatal_sig_handler);\r
- putty_signal(SIGSEGV, fatal_sig_handler);\r
- putty_signal(SIGUSR1, fatal_sig_handler);\r
- putty_signal(SIGUSR2, fatal_sig_handler);\r
-#ifdef SIGBUS\r
- putty_signal(SIGBUS, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGPOLL\r
- putty_signal(SIGPOLL, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGPROF\r
- putty_signal(SIGPROF, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGSYS\r
- putty_signal(SIGSYS, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGTRAP\r
- putty_signal(SIGTRAP, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGVTALRM\r
- putty_signal(SIGVTALRM, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGXCPU\r
- putty_signal(SIGXCPU, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGXFSZ\r
- putty_signal(SIGXFSZ, fatal_sig_handler);\r
-#endif\r
-#ifdef SIGIO\r
- putty_signal(SIGIO, fatal_sig_handler);\r
-#endif\r
- setup_utmp(pty->name, display);\r
- }\r
- }\r
- }\r
- } else {\r
- close(pipefd[0]);\r
- pty_utmp_helper_pid = pid;\r
- pty_utmp_helper_pipe = pipefd[1];\r
- }\r
-#endif\r
-\r
- /* Drop privs. */\r
- {\r
-#ifndef HAVE_NO_SETRESUID\r
- int gid = getgid(), uid = getuid();\r
- int setresgid(gid_t, gid_t, gid_t);\r
- int setresuid(uid_t, uid_t, uid_t);\r
- setresgid(gid, gid, gid);\r
- setresuid(uid, uid, uid);\r
-#else\r
- setgid(getgid());\r
- setuid(getuid());\r
-#endif\r
- }\r
-}\r
-\r
-int pty_real_select_result(Pty pty, int event, int status)\r
-{\r
- char buf[4096];\r
- int ret;\r
- int finished = FALSE;\r
-\r
- if (event < 0) {\r
- /*\r
- * We've been called because our child process did\r
- * something. `status' tells us what.\r
- */\r
- if ((WIFEXITED(status) || WIFSIGNALED(status))) {\r
- /*\r
- * The primary child process died. We could keep\r
- * the terminal open for remaining subprocesses to\r
- * output to, but conventional wisdom seems to feel\r
- * that that's the Wrong Thing for an xterm-alike,\r
- * so we bail out now (though we don't necessarily\r
- * _close_ the window, depending on the state of\r
- * Close On Exit). This would be easy enough to\r
- * change or make configurable if necessary.\r
- */\r
- pty->exit_code = status;\r
- pty->child_dead = TRUE;\r
- del234(ptys_by_pid, pty);\r
- finished = TRUE;\r
- }\r
- } else {\r
- if (event == 1) {\r
-\r
- ret = read(pty->master_fd, buf, sizeof(buf));\r
-\r
- /*\r
- * Clean termination condition is that either ret == 0, or ret\r
- * < 0 and errno == EIO. Not sure why the latter, but it seems\r
- * to happen. Boo.\r
- */\r
- if (ret == 0 || (ret < 0 && errno == EIO)) {\r
- /*\r
- * We assume a clean exit if the pty has closed but the\r
- * actual child process hasn't. The only way I can\r
- * imagine this happening is if it detaches itself from\r
- * the pty and goes daemonic - in which case the\r
- * expected usage model would precisely _not_ be for\r
- * the pterm window to hang around!\r
- */\r
- finished = TRUE;\r
- if (!pty->child_dead)\r
- pty->exit_code = 0;\r
- } else if (ret < 0) {\r
- perror("read pty master");\r
- exit(1);\r
- } else if (ret > 0) {\r
- from_backend(pty->frontend, 0, buf, ret);\r
- }\r
- } else if (event == 2) {\r
- /*\r
- * Attempt to send data down the pty.\r
- */\r
- pty_try_write(pty);\r
- }\r
- }\r
-\r
- if (finished && !pty->finished) {\r
- uxsel_del(pty->master_fd);\r
- pty_close(pty);\r
- pty->master_fd = -1;\r
-\r
- pty->finished = TRUE;\r
-\r
- /*\r
- * This is a slight layering-violation sort of hack: only\r
- * if we're not closing on exit (COE is set to Never, or to\r
- * Only On Clean and it wasn't a clean exit) do we output a\r
- * `terminated' message.\r
- */\r
- if (pty->cfg.close_on_exit == FORCE_OFF ||\r
- (pty->cfg.close_on_exit == AUTO && pty->exit_code != 0)) {\r
- char message[512];\r
- if (WIFEXITED(pty->exit_code))\r
- sprintf(message, "\r\n[pterm: process terminated with exit"\r
- " code %d]\r\n", WEXITSTATUS(pty->exit_code));\r
- else if (WIFSIGNALED(pty->exit_code))\r
-#ifdef HAVE_NO_STRSIGNAL\r
- sprintf(message, "\r\n[pterm: process terminated on signal"\r
- " %d]\r\n", WTERMSIG(pty->exit_code));\r
-#else\r
- sprintf(message, "\r\n[pterm: process terminated on signal"\r
- " %d (%.400s)]\r\n", WTERMSIG(pty->exit_code),\r
- strsignal(WTERMSIG(pty->exit_code)));\r
-#endif\r
- from_backend(pty->frontend, 0, message, strlen(message));\r
- }\r
-\r
- notify_remote_exit(pty->frontend);\r
- }\r
-\r
- return !finished;\r
-}\r
-\r
-int pty_select_result(int fd, int event)\r
-{\r
- int ret = TRUE;\r
- Pty pty;\r
-\r
- if (fd == pty_signal_pipe[0]) {\r
- pid_t pid;\r
- int status;\r
- char c[1];\r
-\r
- if (read(pty_signal_pipe[0], c, 1) <= 0)\r
- /* ignore error */;\r
- /* ignore its value; it'll be `x' */\r
-\r
- do {\r
- pid = waitpid(-1, &status, WNOHANG);\r
-\r
- pty = find234(ptys_by_pid, &pid, pty_find_by_pid);\r
-\r
- if (pty)\r
- ret = ret && pty_real_select_result(pty, -1, status);\r
- } while (pid > 0);\r
- } else {\r
- pty = find234(ptys_by_fd, &fd, pty_find_by_fd);\r
-\r
- if (pty)\r
- ret = ret && pty_real_select_result(pty, event, 0);\r
- }\r
-\r
- return ret;\r
-}\r
-\r
-static void pty_uxsel_setup(Pty pty)\r
-{\r
- int rwx;\r
-\r
- rwx = 1; /* always want to read from pty */\r
- if (bufchain_size(&pty->output_data))\r
- rwx |= 2; /* might also want to write to it */\r
- uxsel_set(pty->master_fd, rwx, pty_select_result);\r
-\r
- /*\r
- * In principle this only needs calling once for all pty\r
- * backend instances, but it's simplest just to call it every\r
- * time; uxsel won't mind.\r
- */\r
- uxsel_set(pty_signal_pipe[0], 1, pty_select_result);\r
-}\r
-\r
-/*\r
- * Called to set up the pty.\r
- * \r
- * Returns an error message, or NULL on success.\r
- *\r
- * Also places the canonical host name into `realhost'. It must be\r
- * freed by the caller.\r
- */\r
-static const char *pty_init(void *frontend, void **backend_handle, Config *cfg,\r
- char *host, int port, char **realhost, int nodelay,\r
- int keepalive)\r
-{\r
- int slavefd;\r
- pid_t pid, pgrp;\r
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */\r
- long windowid;\r
-#endif\r
- Pty pty;\r
-\r
- if (single_pty) {\r
- pty = single_pty;\r
- } else {\r
- pty = snew(struct pty_tag);\r
- pty->master_fd = pty->slave_fd = -1;\r
-#ifndef OMIT_UTMP\r
- pty_stamped_utmp = FALSE;\r
-#endif\r
- }\r
-\r
- pty->frontend = frontend;\r
- *backend_handle = NULL; /* we can't sensibly use this, sadly */\r
-\r
- pty->cfg = *cfg; /* structure copy */\r
- pty->term_width = cfg->width;\r
- pty->term_height = cfg->height;\r
-\r
- if (pty->master_fd < 0)\r
- pty_open_master(pty);\r
-\r
- /*\r
- * Set the backspace character to be whichever of ^H and ^? is\r
- * specified by bksp_is_delete.\r
- */\r
- {\r
- struct termios attrs;\r
- tcgetattr(pty->master_fd, &attrs);\r
- attrs.c_cc[VERASE] = cfg->bksp_is_delete ? '\177' : '\010';\r
- tcsetattr(pty->master_fd, TCSANOW, &attrs);\r
- }\r
-\r
-#ifndef OMIT_UTMP\r
- /*\r
- * Stamp utmp (that is, tell the utmp helper process to do so),\r
- * or not.\r
- */\r
- if (!cfg->stamp_utmp) {\r
- close(pty_utmp_helper_pipe); /* just let the child process die */\r
- pty_utmp_helper_pipe = -1;\r
- } else {\r
- char *location = get_x_display(pty->frontend);\r
- int len = strlen(location)+1, pos = 0; /* +1 to include NUL */\r
- while (pos < len) {\r
- int ret = write(pty_utmp_helper_pipe, location+pos, len - pos);\r
- if (ret < 0) {\r
- perror("pterm: writing to utmp helper process");\r
- close(pty_utmp_helper_pipe); /* arrgh, just give up */\r
- pty_utmp_helper_pipe = -1;\r
- break;\r
- }\r
- pos += ret;\r
- }\r
- }\r
-#endif\r
-\r
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */\r
- windowid = get_windowid(pty->frontend);\r
-#endif\r
-\r
- /*\r
- * Fork and execute the command.\r
- */\r
- pid = fork();\r
- if (pid < 0) {\r
- perror("fork");\r
- exit(1);\r
- }\r
-\r
- if (pid == 0) {\r
- /*\r
- * We are the child.\r
- */\r
-\r
- slavefd = pty_open_slave(pty);\r
- if (slavefd < 0) {\r
- perror("slave pty: open");\r
- _exit(1);\r
- }\r
-\r
- close(pty->master_fd);\r
- fcntl(slavefd, F_SETFD, 0); /* don't close on exec */\r
- dup2(slavefd, 0);\r
- dup2(slavefd, 1);\r
- dup2(slavefd, 2);\r
- close(slavefd);\r
- setsid();\r
-#ifdef TIOCSCTTY\r
- ioctl(0, TIOCSCTTY, 1);\r
-#endif\r
- pgrp = getpid();\r
- tcsetpgrp(0, pgrp);\r
- setpgid(pgrp, pgrp);\r
- close(open(pty->name, O_WRONLY, 0));\r
- setpgid(pgrp, pgrp);\r
- {\r
- char *term_env_var = dupprintf("TERM=%s", cfg->termtype);\r
- putenv(term_env_var);\r
- /* We mustn't free term_env_var, as putenv links it into the\r
- * environment in place.\r
- */\r
- }\r
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */\r
- {\r
- char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid);\r
- putenv(windowid_env_var);\r
- /* We mustn't free windowid_env_var, as putenv links it into the\r
- * environment in place.\r
- */\r
- }\r
-#endif\r
- {\r
- char *e = cfg->environmt;\r
- char *var, *varend, *val, *varval;\r
- while (*e) {\r
- var = e;\r
- while (*e && *e != '\t') e++;\r
- varend = e;\r
- if (*e == '\t') e++;\r
- val = e;\r
- while (*e) e++;\r
- e++;\r
-\r
- varval = dupprintf("%.*s=%s", varend-var, var, val);\r
- putenv(varval);\r
- /*\r
- * We must not free varval, since putenv links it\r
- * into the environment _in place_. Weird, but\r
- * there we go. Memory usage will be rationalised\r
- * as soon as we exec anyway.\r
- */\r
- }\r
- }\r
-\r
- /*\r
- * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by\r
- * our parent, particularly by things like sh -c 'pterm &' and\r
- * some window or session managers. SIGCHLD, meanwhile, was\r
- * blocked during pt_main() startup. Reverse all this for our\r
- * child process.\r
- */\r
- putty_signal(SIGINT, SIG_DFL);\r
- putty_signal(SIGQUIT, SIG_DFL);\r
- putty_signal(SIGPIPE, SIG_DFL);\r
- block_signal(SIGCHLD, 0);\r
- if (pty_argv)\r
- execvp(pty_argv[0], pty_argv);\r
- else {\r
- char *shell = getenv("SHELL");\r
- char *shellname;\r
- if (cfg->login_shell) {\r
- char *p = strrchr(shell, '/');\r
- shellname = snewn(2+strlen(shell), char);\r
- p = p ? p+1 : shell;\r
- sprintf(shellname, "-%s", p);\r
- } else\r
- shellname = shell;\r
- execl(getenv("SHELL"), shellname, (void *)NULL);\r
- }\r
-\r
- /*\r
- * If we're here, exec has gone badly foom.\r
- */\r
- perror("exec");\r
- _exit(127);\r
- } else {\r
- pty->child_pid = pid;\r
- pty->child_dead = FALSE;\r
- pty->finished = FALSE;\r
- if (pty->slave_fd > 0)\r
- close(pty->slave_fd);\r
- if (!ptys_by_pid)\r
- ptys_by_pid = newtree234(pty_compare_by_pid);\r
- add234(ptys_by_pid, pty);\r
- }\r
-\r
- if (pty_signal_pipe[0] < 0) {\r
- if (pipe(pty_signal_pipe) < 0) {\r
- perror("pipe");\r
- exit(1);\r
- }\r
- cloexec(pty_signal_pipe[0]);\r
- cloexec(pty_signal_pipe[1]);\r
- }\r
- pty_uxsel_setup(pty);\r
-\r
- *backend_handle = pty;\r
-\r
- *realhost = dupprintf("\0");\r
-\r
- return NULL;\r
-}\r
-\r
-static void pty_reconfig(void *handle, Config *cfg)\r
-{\r
- Pty pty = (Pty)handle;\r
- /*\r
- * We don't have much need to reconfigure this backend, but\r
- * unfortunately we do need to pick up the setting of Close On\r
- * Exit so we know whether to give a `terminated' message.\r
- */\r
- pty->cfg = *cfg; /* structure copy */\r
-}\r
-\r
-/*\r
- * Stub routine (never called in pterm).\r
- */\r
-static void pty_free(void *handle)\r
-{\r
- Pty pty = (Pty)handle;\r
-\r
- /* Either of these may fail `not found'. That's fine with us. */\r
- del234(ptys_by_pid, pty);\r
- del234(ptys_by_fd, pty);\r
-\r
- sfree(pty);\r
-}\r
-\r
-static void pty_try_write(Pty pty)\r
-{\r
- void *data;\r
- int len, ret;\r
-\r
- assert(pty->master_fd >= 0);\r
-\r
- while (bufchain_size(&pty->output_data) > 0) {\r
- bufchain_prefix(&pty->output_data, &data, &len);\r
- ret = write(pty->master_fd, data, len);\r
-\r
- if (ret < 0 && (errno == EWOULDBLOCK)) {\r
- /*\r
- * We've sent all we can for the moment.\r
- */\r
- break;\r
- }\r
- if (ret < 0) {\r
- perror("write pty master");\r
- exit(1);\r
- }\r
- bufchain_consume(&pty->output_data, ret);\r
- }\r
-\r
- pty_uxsel_setup(pty);\r
-}\r
-\r
-/*\r
- * Called to send data down the pty.\r
- */\r
-static int pty_send(void *handle, char *buf, int len)\r
-{\r
- Pty pty = (Pty)handle;\r
-\r
- if (pty->master_fd < 0)\r
- return 0; /* ignore all writes if fd closed */\r
-\r
- bufchain_add(&pty->output_data, buf, len);\r
- pty_try_write(pty);\r
-\r
- return bufchain_size(&pty->output_data);\r
-}\r
-\r
-static void pty_close(Pty pty)\r
-{\r
- if (pty->master_fd >= 0) {\r
- close(pty->master_fd);\r
- pty->master_fd = -1;\r
- }\r
-#ifndef OMIT_UTMP\r
- if (pty_utmp_helper_pipe >= 0) {\r
- close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */\r
- pty_utmp_helper_pipe = -1;\r
- }\r
-#endif\r
-}\r
-\r
-/*\r
- * Called to query the current socket sendability status.\r
- */\r
-static int pty_sendbuffer(void *handle)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- return 0;\r
-}\r
-\r
-/*\r
- * Called to set the size of the window\r
- */\r
-static void pty_size(void *handle, int width, int height)\r
-{\r
- Pty pty = (Pty)handle;\r
- struct winsize size;\r
-\r
- pty->term_width = width;\r
- pty->term_height = height;\r
-\r
- size.ws_row = (unsigned short)pty->term_height;\r
- size.ws_col = (unsigned short)pty->term_width;\r
- size.ws_xpixel = (unsigned short) pty->term_width *\r
- font_dimension(pty->frontend, 0);\r
- size.ws_ypixel = (unsigned short) pty->term_height *\r
- font_dimension(pty->frontend, 1);\r
- ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size);\r
- return;\r
-}\r
-\r
-/*\r
- * Send special codes.\r
- */\r
-static void pty_special(void *handle, Telnet_Special code)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- /* Do nothing! */\r
- return;\r
-}\r
-\r
-/*\r
- * Return a list of the special codes that make sense in this\r
- * protocol.\r
- */\r
-static const struct telnet_special *pty_get_specials(void *handle)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- /*\r
- * Hmm. When I get round to having this actually usable, it\r
- * might be quite nice to have the ability to deliver a few\r
- * well chosen signals to the child process - SIGINT, SIGTERM,\r
- * SIGKILL at least.\r
- */\r
- return NULL;\r
-}\r
-\r
-static int pty_connected(void *handle)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- return TRUE;\r
-}\r
-\r
-static int pty_sendok(void *handle)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- return 1;\r
-}\r
-\r
-static void pty_unthrottle(void *handle, int backlog)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- /* do nothing */\r
-}\r
-\r
-static int pty_ldisc(void *handle, int option)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- return 0; /* neither editing nor echoing */\r
-}\r
-\r
-static void pty_provide_ldisc(void *handle, void *ldisc)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- /* This is a stub. */\r
-}\r
-\r
-static void pty_provide_logctx(void *handle, void *logctx)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- /* This is a stub. */\r
-}\r
-\r
-static int pty_exitcode(void *handle)\r
-{\r
- Pty pty = (Pty)handle;\r
- if (!pty->finished)\r
- return -1; /* not dead yet */\r
- else\r
- return pty->exit_code;\r
-}\r
-\r
-static int pty_cfg_info(void *handle)\r
-{\r
- /* Pty pty = (Pty)handle; */\r
- return 0;\r
-}\r
-\r
-Backend pty_backend = {\r
- pty_init,\r
- pty_free,\r
- pty_reconfig,\r
- pty_send,\r
- pty_sendbuffer,\r
- pty_size,\r
- pty_special,\r
- pty_get_specials,\r
- pty_connected,\r
- pty_exitcode,\r
- pty_sendok,\r
- pty_ldisc,\r
- pty_provide_ldisc,\r
- pty_provide_logctx,\r
- pty_unthrottle,\r
- pty_cfg_info,\r
- "pty",\r
- -1,\r
- 0\r
-};\r