]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/pty.c
b621d8be0d0ae9787306c097255fd24410094fb8
[PuTTY.git] / unix / pty.c
1 /*
2  * Pseudo-tty backend for pterm.
3  * 
4  * Unlike the other backends, data for this one is not neatly
5  * encapsulated into a data structure, because it wouldn't make
6  * sense to do so - the utmp stuff has to be done before a backend
7  * is initialised, and starting a second pterm from the same
8  * process would therefore be infeasible because privileges would
9  * already have been dropped. Hence, I haven't bothered to keep the
10  * data dynamically allocated: instead, the backend handle is just
11  * a null pointer and ignored everywhere.
12  */
13
14 #define _XOPEN_SOURCE
15 #define _XOPEN_SOURCE_EXTENDED
16 #define _GNU_SOURCE
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <signal.h>
23 #include <fcntl.h>
24 #include <termios.h>
25 #include <grp.h>
26 #include <utmp.h>
27 #include <pwd.h>
28 #include <time.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/wait.h>
32 #include <sys/ioctl.h>
33 #include <errno.h>
34
35 #include "putty.h"
36
37 #ifndef FALSE
38 #define FALSE 0
39 #endif
40 #ifndef TRUE
41 #define TRUE 1
42 #endif
43
44 #ifndef UTMP_FILE
45 #define UTMP_FILE "/var/run/utmp"
46 #endif
47 #ifndef WTMP_FILE
48 #define WTMP_FILE "/var/log/wtmp"
49 #endif
50 #ifndef LASTLOG_FILE
51 #ifdef _PATH_LASTLOG
52 #define LASTLOG_FILE _PATH_LASTLOG
53 #else
54 #define LASTLOG_FILE "/var/log/lastlog"
55 #endif
56 #endif
57
58 /*
59  * Set up a default for vaguely sane systems. The idea is that if
60  * OMIT_UTMP is not defined, then at least one of the symbols which
61  * enable particular forms of utmp processing should be, if only so
62  * that a link error can warn you that you should have defined
63  * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is
64  * the only such symbol.
65  */
66 #ifndef OMIT_UTMP
67 #if !defined HAVE_PUTUTLINE
68 #define HAVE_PUTUTLINE
69 #endif
70 #endif
71
72 static Config pty_cfg;
73 static int pty_master_fd;
74 static void *pty_frontend;
75 static char pty_name[FILENAME_MAX];
76 static int pty_signal_pipe[2];
77 static int pty_child_pid;
78 static int pty_term_width, pty_term_height;
79 static int pty_child_dead, pty_finished;
80 static int pty_exit_code;
81 char **pty_argv;
82 int use_pty_argv = TRUE;
83
84 static void pty_close(void);
85
86 #ifndef OMIT_UTMP
87 static int pty_utmp_helper_pid, pty_utmp_helper_pipe;
88 static int pty_stamped_utmp = 0;
89 static struct utmp utmp_entry;
90
91 static void setup_utmp(char *ttyname, char *location)
92 {
93 #ifdef HAVE_LASTLOG
94     struct lastlog lastlog_entry;
95     FILE *lastlog;
96 #endif
97     struct passwd *pw;
98     FILE *wtmp;
99     time_t uttime;
100
101     pw = getpwuid(getuid());
102     memset(&utmp_entry, 0, sizeof(utmp_entry));
103     utmp_entry.ut_type = USER_PROCESS;
104     utmp_entry.ut_pid = getpid();
105     strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
106     strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
107     strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
108     strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
109     /* Apparently there are some architectures where (struct utmp).ut_time
110      * is not essentially time_t (e.g. Linux amd64). Hence the temporary. */
111     time(&uttime);
112     utmp_entry.ut_time = uttime; /* may truncate */
113
114 #if defined HAVE_PUTUTLINE
115     utmpname(UTMP_FILE);
116     setutent();
117     pututline(&utmp_entry);
118     endutent();
119 #endif
120
121     if ((wtmp = fopen(WTMP_FILE, "a")) != NULL) {
122         fwrite(&utmp_entry, 1, sizeof(utmp_entry), wtmp);
123         fclose(wtmp);
124     }
125
126 #ifdef HAVE_LASTLOG
127     memset(&lastlog_entry, 0, sizeof(lastlog_entry));
128     strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
129     strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
130     time(&lastlog_entry.ll_time);
131     if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
132         fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
133         fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
134         fclose(lastlog);
135     }
136 #endif
137
138     pty_stamped_utmp = 1;
139
140 }
141
142 static void cleanup_utmp(void)
143 {
144     FILE *wtmp;
145     time_t uttime;
146
147     if (!pty_stamped_utmp)
148         return;
149
150     utmp_entry.ut_type = DEAD_PROCESS;
151     memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
152     time(&uttime);
153     utmp_entry.ut_time = uttime;
154
155     if ((wtmp = fopen(WTMP_FILE, "a")) != NULL) {
156         fwrite(&utmp_entry, 1, sizeof(utmp_entry), wtmp);
157         fclose(wtmp);
158     }
159
160     memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
161     utmp_entry.ut_time = 0;
162
163 #if defined HAVE_PUTUTLINE
164     utmpname(UTMP_FILE);
165     setutent();
166     pututline(&utmp_entry);
167     endutent();
168 #endif
169
170     pty_stamped_utmp = 0;              /* ensure we never double-cleanup */
171 }
172 #endif
173
174 static void sigchld_handler(int signum)
175 {
176     write(pty_signal_pipe[1], "x", 1);
177 }
178
179 #ifndef OMIT_UTMP
180 static void fatal_sig_handler(int signum)
181 {
182     putty_signal(signum, SIG_DFL);
183     cleanup_utmp();
184     setuid(getuid());
185     raise(signum);
186 }
187 #endif
188
189 static void pty_open_master(void)
190 {
191 #ifdef BSD_PTYS
192     const char chars1[] = "pqrstuvwxyz";
193     const char chars2[] = "0123456789abcdef";
194     const char *p1, *p2;
195     char master_name[20];
196     struct group *gp;
197
198     for (p1 = chars1; *p1; p1++)
199         for (p2 = chars2; *p2; p2++) {
200             sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
201             pty_master_fd = open(master_name, O_RDWR);
202             if (pty_master_fd >= 0) {
203                 if (geteuid() == 0 ||
204                     access(master_name, R_OK | W_OK) == 0)
205                     goto got_one;
206                 close(pty_master_fd);
207             }
208         }
209
210     /* If we get here, we couldn't get a tty at all. */
211     fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
212     exit(1);
213
214     got_one:
215     strcpy(pty_name, master_name);
216     pty_name[5] = 't';                 /* /dev/ptyXX -> /dev/ttyXX */
217
218     /* We need to chown/chmod the /dev/ttyXX device. */
219     gp = getgrnam("tty");
220     chown(pty_name, getuid(), gp ? gp->gr_gid : -1);
221     chmod(pty_name, 0600);
222 #else
223     pty_master_fd = open("/dev/ptmx", O_RDWR);
224
225     if (pty_master_fd < 0) {
226         perror("/dev/ptmx: open");
227         exit(1);
228     }
229
230     if (grantpt(pty_master_fd) < 0) {
231         perror("grantpt");
232         exit(1);
233     }
234     
235     if (unlockpt(pty_master_fd) < 0) {
236         perror("unlockpt");
237         exit(1);
238     }
239
240     pty_name[FILENAME_MAX-1] = '\0';
241     strncpy(pty_name, ptsname(pty_master_fd), FILENAME_MAX-1);
242 #endif
243 }
244
245 /*
246  * Pre-initialisation. This is here to get around the fact that GTK
247  * doesn't like being run in setuid/setgid programs (probably
248  * sensibly). So before we initialise GTK - and therefore before we
249  * even process the command line - we check to see if we're running
250  * set[ug]id. If so, we open our pty master _now_, chown it as
251  * necessary, and drop privileges. We can always close it again
252  * later. If we're potentially going to be doing utmp as well, we
253  * also fork off a utmp helper process and communicate with it by
254  * means of a pipe; the utmp helper will keep privileges in order
255  * to clean up utmp when we exit (i.e. when its end of our pipe
256  * closes).
257  */
258 void pty_pre_init(void)
259 {
260 #ifndef OMIT_UTMP
261     pid_t pid;
262     int pipefd[2];
263 #endif
264
265     /* set the child signal handler straight away; it needs to be set
266      * before we ever fork. */
267     putty_signal(SIGCHLD, sigchld_handler);
268     pty_master_fd = -1;
269
270     if (geteuid() != getuid() || getegid() != getgid()) {
271         pty_open_master();
272     }
273
274 #ifndef OMIT_UTMP
275     /*
276      * Fork off the utmp helper.
277      */
278     if (pipe(pipefd) < 0) {
279         perror("pterm: pipe");
280         exit(1);
281     }
282     pid = fork();
283     if (pid < 0) {
284         perror("pterm: fork");
285         exit(1);
286     } else if (pid == 0) {
287         char display[128], buffer[128];
288         int dlen, ret;
289
290         close(pipefd[1]);
291         /*
292          * Now sit here until we receive a display name from the
293          * other end of the pipe, and then stamp utmp. Unstamp utmp
294          * again, and exit, when the pipe closes.
295          */
296
297         dlen = 0;
298         while (1) {
299             
300             ret = read(pipefd[0], buffer, lenof(buffer));
301             if (ret <= 0) {
302                 cleanup_utmp();
303                 _exit(0);
304             } else if (!pty_stamped_utmp) {
305                 if (dlen < lenof(display))
306                     memcpy(display+dlen, buffer,
307                            min(ret, lenof(display)-dlen));
308                 if (buffer[ret-1] == '\0') {
309                     /*
310                      * Now we have a display name. NUL-terminate
311                      * it, and stamp utmp.
312                      */
313                     display[lenof(display)-1] = '\0';
314                     /*
315                      * Trap as many fatal signals as we can in the
316                      * hope of having the best possible chance to
317                      * clean up utmp before termination. We are
318                      * unfortunately unprotected against SIGKILL,
319                      * but that's life.
320                      */
321                     putty_signal(SIGHUP, fatal_sig_handler);
322                     putty_signal(SIGINT, fatal_sig_handler);
323                     putty_signal(SIGQUIT, fatal_sig_handler);
324                     putty_signal(SIGILL, fatal_sig_handler);
325                     putty_signal(SIGABRT, fatal_sig_handler);
326                     putty_signal(SIGFPE, fatal_sig_handler);
327                     putty_signal(SIGPIPE, fatal_sig_handler);
328                     putty_signal(SIGALRM, fatal_sig_handler);
329                     putty_signal(SIGTERM, fatal_sig_handler);
330                     putty_signal(SIGSEGV, fatal_sig_handler);
331                     putty_signal(SIGUSR1, fatal_sig_handler);
332                     putty_signal(SIGUSR2, fatal_sig_handler);
333 #ifdef SIGBUS
334                     putty_signal(SIGBUS, fatal_sig_handler);
335 #endif
336 #ifdef SIGPOLL
337                     putty_signal(SIGPOLL, fatal_sig_handler);
338 #endif
339 #ifdef SIGPROF
340                     putty_signal(SIGPROF, fatal_sig_handler);
341 #endif
342 #ifdef SIGSYS
343                     putty_signal(SIGSYS, fatal_sig_handler);
344 #endif
345 #ifdef SIGTRAP
346                     putty_signal(SIGTRAP, fatal_sig_handler);
347 #endif
348 #ifdef SIGVTALRM
349                     putty_signal(SIGVTALRM, fatal_sig_handler);
350 #endif
351 #ifdef SIGXCPU
352                     putty_signal(SIGXCPU, fatal_sig_handler);
353 #endif
354 #ifdef SIGXFSZ
355                     putty_signal(SIGXFSZ, fatal_sig_handler);
356 #endif
357 #ifdef SIGIO
358                     putty_signal(SIGIO, fatal_sig_handler);
359 #endif
360                     setup_utmp(pty_name, display);
361                 }
362             }
363         }
364     } else {
365         close(pipefd[0]);
366         pty_utmp_helper_pid = pid;
367         pty_utmp_helper_pipe = pipefd[1];
368     }
369 #endif
370
371     /* Drop privs. */
372     {
373 #ifndef HAVE_NO_SETRESUID
374         int gid = getgid(), uid = getuid();
375         int setresgid(gid_t, gid_t, gid_t);
376         int setresuid(uid_t, uid_t, uid_t);
377         setresgid(gid, gid, gid);
378         setresuid(uid, uid, uid);
379 #else
380         setgid(getgid());
381         setuid(getuid());
382 #endif
383     }
384 }
385
386 int pty_select_result(int fd, int event)
387 {
388     char buf[4096];
389     int ret;
390     int finished = FALSE;
391
392     if (fd == pty_master_fd && event == 1) {
393
394         ret = read(pty_master_fd, buf, sizeof(buf));
395
396         /*
397          * Clean termination condition is that either ret == 0, or ret
398          * < 0 and errno == EIO. Not sure why the latter, but it seems
399          * to happen. Boo.
400          */
401         if (ret == 0 || (ret < 0 && errno == EIO)) {
402             /*
403              * We assume a clean exit if the pty has closed but the
404              * actual child process hasn't. The only way I can
405              * imagine this happening is if it detaches itself from
406              * the pty and goes daemonic - in which case the
407              * expected usage model would precisely _not_ be for
408              * the pterm window to hang around!
409              */
410             finished = TRUE;
411             if (!pty_child_dead)
412                 pty_exit_code = 0;
413         } else if (ret < 0) {
414             perror("read pty master");
415             exit(1);
416         } else if (ret > 0) {
417             from_backend(pty_frontend, 0, buf, ret);
418         }
419     } else if (fd == pty_signal_pipe[0]) {
420         pid_t pid;
421         int status;
422         char c[1];
423
424         read(pty_signal_pipe[0], c, 1); /* ignore its value; it'll be `x' */
425
426         do {
427             pid = waitpid(-1, &status, WNOHANG);
428             if (pid == pty_child_pid &&
429                 (WIFEXITED(status) || WIFSIGNALED(status))) {
430                 /*
431                  * The primary child process died. We could keep
432                  * the terminal open for remaining subprocesses to
433                  * output to, but conventional wisdom seems to feel
434                  * that that's the Wrong Thing for an xterm-alike,
435                  * so we bail out now (though we don't necessarily
436                  * _close_ the window, depending on the state of
437                  * Close On Exit). This would be easy enough to
438                  * change or make configurable if necessary.
439                  */
440                 pty_exit_code = status;
441                 pty_child_dead = TRUE;
442                 finished = TRUE;
443             }
444         } while(pid > 0);
445     }
446
447     if (finished && !pty_finished) {
448         uxsel_del(pty_master_fd);
449         pty_close();
450         pty_master_fd = -1;
451
452         pty_finished = TRUE;
453
454         /*
455          * This is a slight layering-violation sort of hack: only
456          * if we're not closing on exit (COE is set to Never, or to
457          * Only On Clean and it wasn't a clean exit) do we output a
458          * `terminated' message.
459          */
460         if (pty_cfg.close_on_exit == FORCE_OFF ||
461             (pty_cfg.close_on_exit == AUTO && pty_exit_code != 0)) {
462             char message[512];
463             if (WIFEXITED(pty_exit_code))
464                 sprintf(message, "\r\n[pterm: process terminated with exit"
465                         " code %d]\r\n", WEXITSTATUS(pty_exit_code));
466             else if (WIFSIGNALED(pty_exit_code))
467 #ifdef HAVE_NO_STRSIGNAL
468                 sprintf(message, "\r\n[pterm: process terminated on signal"
469                         " %d]\r\n", WTERMSIG(pty_exit_code));
470 #else
471                 sprintf(message, "\r\n[pterm: process terminated on signal"
472                         " %d (%.400s)]\r\n", WTERMSIG(pty_exit_code),
473                         strsignal(WTERMSIG(pty_exit_code)));
474 #endif
475             from_backend(pty_frontend, 0, message, strlen(message));
476         }
477     }
478     return !finished;
479 }
480
481 static void pty_uxsel_setup(void)
482 {
483     uxsel_set(pty_master_fd, 1, pty_select_result);
484     uxsel_set(pty_signal_pipe[0], 1, pty_select_result);
485 }
486
487 /*
488  * Called to set up the pty.
489  * 
490  * Returns an error message, or NULL on success.
491  *
492  * Also places the canonical host name into `realhost'. It must be
493  * freed by the caller.
494  */
495 static const char *pty_init(void *frontend, void **backend_handle, Config *cfg,
496                             char *host, int port, char **realhost, int nodelay,
497                             int keepalive)
498 {
499     int slavefd;
500     pid_t pid, pgrp;
501     long windowid;
502
503     pty_frontend = frontend;
504     *backend_handle = NULL;            /* we can't sensibly use this, sadly */
505
506     pty_cfg = *cfg;                    /* structure copy */
507     pty_term_width = cfg->width;
508     pty_term_height = cfg->height;
509
510     if (pty_master_fd < 0)
511         pty_open_master();
512
513     /*
514      * Set the backspace character to be whichever of ^H and ^? is
515      * specified by bksp_is_delete.
516      */
517     {
518         struct termios attrs;
519         tcgetattr(pty_master_fd, &attrs);
520         attrs.c_cc[VERASE] = cfg->bksp_is_delete ? '\177' : '\010';
521         tcsetattr(pty_master_fd, TCSANOW, &attrs);
522     }
523
524 #ifndef OMIT_UTMP
525     /*
526      * Stamp utmp (that is, tell the utmp helper process to do so),
527      * or not.
528      */
529     if (!cfg->stamp_utmp) {
530         close(pty_utmp_helper_pipe);   /* just let the child process die */
531         pty_utmp_helper_pipe = -1;
532     } else {
533         char *location = get_x_display(pty_frontend);
534         int len = strlen(location)+1, pos = 0;   /* +1 to include NUL */
535         while (pos < len) {
536             int ret = write(pty_utmp_helper_pipe, location+pos, len - pos);
537             if (ret < 0) {
538                 perror("pterm: writing to utmp helper process");
539                 close(pty_utmp_helper_pipe);   /* arrgh, just give up */
540                 pty_utmp_helper_pipe = -1;
541                 break;
542             }
543             pos += ret;
544         }
545     }
546 #endif
547
548     windowid = get_windowid(pty_frontend);
549
550     /*
551      * Fork and execute the command.
552      */
553     pid = fork();
554     if (pid < 0) {
555         perror("fork");
556         exit(1);
557     }
558
559     if (pid == 0) {
560         int i;
561         /*
562          * We are the child.
563          */
564
565         slavefd = open(pty_name, O_RDWR);
566         if (slavefd < 0) {
567             perror("slave pty: open");
568             _exit(1);
569         }
570
571         close(pty_master_fd);
572         fcntl(slavefd, F_SETFD, 0);    /* don't close on exec */
573         dup2(slavefd, 0);
574         dup2(slavefd, 1);
575         dup2(slavefd, 2);
576         setsid();
577         ioctl(slavefd, TIOCSCTTY, 1);
578         pgrp = getpid();
579         tcsetpgrp(slavefd, pgrp);
580         setpgid(pgrp, pgrp);
581         close(open(pty_name, O_WRONLY, 0));
582         setpgid(pgrp, pgrp);
583         /* Close everything _else_, for tidiness. */
584         for (i = 3; i < 1024; i++)
585             close(i);
586         {
587             char term_env_var[10 + sizeof(cfg->termtype)];
588             sprintf(term_env_var, "TERM=%s", cfg->termtype);
589             putenv(term_env_var);
590         }
591         {
592             char windowid_env_var[40];
593             sprintf(windowid_env_var, "WINDOWID=%ld", windowid);
594             putenv(windowid_env_var);
595         }
596         {
597             char *e = cfg->environmt;
598             char *var, *varend, *val, *varval;
599             while (*e) {
600                 var = e;
601                 while (*e && *e != '\t') e++;
602                 varend = e;
603                 if (*e == '\t') e++;
604                 val = e;
605                 while (*e) e++;
606                 e++;
607
608                 varval = dupprintf("%.*s=%s", varend-var, var, val);
609                 putenv(varval);
610                 /*
611                  * We must not free varval, since putenv links it
612                  * into the environment _in place_. Weird, but
613                  * there we go. Memory usage will be rationalised
614                  * as soon as we exec anyway.
615                  */
616             }
617         }
618
619         /*
620          * SIGINT and SIGQUIT may have been set to ignored by our
621          * parent, particularly by things like sh -c 'pterm &' and
622          * some window managers. Reverse this for our child process.
623          */
624         putty_signal(SIGINT, SIG_DFL);
625         putty_signal(SIGQUIT, SIG_DFL);
626         if (pty_argv)
627             execvp(pty_argv[0], pty_argv);
628         else {
629             char *shell = getenv("SHELL");
630             char *shellname;
631             if (cfg->login_shell) {
632                 char *p = strrchr(shell, '/');
633                 shellname = snewn(2+strlen(shell), char);
634                 p = p ? p+1 : shell;
635                 sprintf(shellname, "-%s", p);
636             } else
637                 shellname = shell;
638             execl(getenv("SHELL"), shellname, NULL);
639         }
640
641         /*
642          * If we're here, exec has gone badly foom.
643          */
644         perror("exec");
645         _exit(127);
646     } else {
647         pty_child_pid = pid;
648         pty_child_dead = FALSE;
649         pty_finished = FALSE;
650     }      
651
652     if (pipe(pty_signal_pipe) < 0) {
653         perror("pipe");
654         exit(1);
655     }
656     pty_uxsel_setup();
657
658     return NULL;
659 }
660
661 static void pty_reconfig(void *handle, Config *cfg)
662 {
663     /*
664      * We don't have much need to reconfigure this backend, but
665      * unfortunately we do need to pick up the setting of Close On
666      * Exit so we know whether to give a `terminated' message.
667      */
668     pty_cfg = *cfg;                    /* structure copy */
669 }
670
671 /*
672  * Stub routine (never called in pterm).
673  */
674 static void pty_free(void *handle)
675 {
676 }
677
678 /*
679  * Called to send data down the pty.
680  */
681 static int pty_send(void *handle, char *buf, int len)
682 {
683     if (pty_master_fd < 0)
684         return 0;                      /* ignore all writes if fd closed */
685
686     while (len > 0) {
687         int ret = write(pty_master_fd, buf, len);
688         if (ret < 0) {
689             perror("write pty master");
690             exit(1);
691         }
692         buf += ret;
693         len -= ret;
694     }
695     return 0;
696 }
697
698 static void pty_close(void)
699 {
700     if (pty_master_fd >= 0) {
701         close(pty_master_fd);
702         pty_master_fd = -1;
703     }
704 #ifndef OMIT_UTMP
705     if (pty_utmp_helper_pipe >= 0) {
706         close(pty_utmp_helper_pipe);   /* this causes utmp to be cleaned up */
707         pty_utmp_helper_pipe = -1;
708     }
709 #endif
710 }
711
712 /*
713  * Called to query the current socket sendability status.
714  */
715 static int pty_sendbuffer(void *handle)
716 {
717     return 0;
718 }
719
720 /*
721  * Called to set the size of the window
722  */
723 static void pty_size(void *handle, int width, int height)
724 {
725     struct winsize size;
726
727     pty_term_width = width;
728     pty_term_height = height;
729
730     size.ws_row = (unsigned short)pty_term_height;
731     size.ws_col = (unsigned short)pty_term_width;
732     size.ws_xpixel = (unsigned short) pty_term_width *
733         font_dimension(pty_frontend, 0);
734     size.ws_ypixel = (unsigned short) pty_term_height *
735         font_dimension(pty_frontend, 1);
736     ioctl(pty_master_fd, TIOCSWINSZ, (void *)&size);
737     return;
738 }
739
740 /*
741  * Send special codes.
742  */
743 static void pty_special(void *handle, Telnet_Special code)
744 {
745     /* Do nothing! */
746     return;
747 }
748
749 /*
750  * Return a list of the special codes that make sense in this
751  * protocol.
752  */
753 static const struct telnet_special *pty_get_specials(void *handle)
754 {
755     /*
756      * Hmm. When I get round to having this actually usable, it
757      * might be quite nice to have the ability to deliver a few
758      * well chosen signals to the child process - SIGINT, SIGTERM,
759      * SIGKILL at least.
760      */
761     return NULL;
762 }
763
764 static Socket pty_socket(void *handle)
765 {
766     return NULL;                       /* shouldn't ever be needed */
767 }
768
769 static int pty_sendok(void *handle)
770 {
771     return 1;
772 }
773
774 static void pty_unthrottle(void *handle, int backlog)
775 {
776     /* do nothing */
777 }
778
779 static int pty_ldisc(void *handle, int option)
780 {
781     return 0;                          /* neither editing nor echoing */
782 }
783
784 static void pty_provide_ldisc(void *handle, void *ldisc)
785 {
786     /* This is a stub. */
787 }
788
789 static void pty_provide_logctx(void *handle, void *logctx)
790 {
791     /* This is a stub. */
792 }
793
794 static int pty_exitcode(void *handle)
795 {
796     if (!pty_finished)
797         return -1;                     /* not dead yet */
798     else
799         return pty_exit_code;
800 }
801
802 Backend pty_backend = {
803     pty_init,
804     pty_free,
805     pty_reconfig,
806     pty_send,
807     pty_sendbuffer,
808     pty_size,
809     pty_special,
810     pty_get_specials,
811     pty_socket,
812     pty_exitcode,
813     pty_sendok,
814     pty_ldisc,
815     pty_provide_ldisc,
816     pty_provide_logctx,
817     pty_unthrottle,
818     1
819 };