]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/pty.c
2a7abb68cf3c523f7f100d29f5bafc7fae1a8506
[PuTTY.git] / unix / pty.c
1 #define _XOPEN_SOURCE
2 #define _XOPEN_SOURCE_EXTENDED
3 #include <features.h>
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <signal.h>
10 #include <fcntl.h>
11 #include <termios.h>
12 #include <grp.h>
13 #include <utmp.h>
14 #include <pwd.h>
15 #include <time.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <sys/ioctl.h>
19
20 #include "putty.h"
21
22 #ifndef FALSE
23 #define FALSE 0
24 #endif
25 #ifndef TRUE
26 #define TRUE 1
27 #endif
28
29 #ifndef UTMP_FILE
30 #define UTMP_FILE "/var/run/utmp"
31 #endif
32 #ifndef WTMP_FILE
33 #define WTMP_FILE "/var/log/wtmp"
34 #endif
35 #ifndef LASTLOG_FILE
36 #ifdef _PATH_LASTLOG
37 #define LASTLOG_FILE _PATH_LASTLOG
38 #else
39 #define LASTLOG_FILE "/var/log/lastlog"
40 #endif
41 #endif
42
43 /*
44  * Set up a default for vaguely sane systems. The idea is that if
45  * OMIT_UTMP is not defined, then at least one of the symbols which
46  * enable particular forms of utmp processing should be, if only so
47  * that a link error can warn you that you should have defined
48  * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is
49  * the only such symbol.
50  */
51 #ifndef OMIT_UTMP
52 #if !defined HAVE_PUTUTLINE
53 #define HAVE_PUTUTLINE
54 #endif
55 #endif
56
57 int pty_master_fd;
58 static int pty_stamped_utmp = 0;
59 static int pty_child_pid;
60 static sig_atomic_t pty_child_dead;
61 #ifndef OMIT_UTMP
62 static struct utmp utmp_entry;
63 #endif
64 char **pty_argv;
65
66 int pty_child_is_dead(void)
67 {
68     return pty_child_dead;
69 }
70
71 static void pty_size(void);
72
73 static void setup_utmp(char *ttyname)
74 {
75 #ifndef OMIT_UTMP
76 #ifdef HAVE_LASTLOG
77     struct lastlog lastlog_entry;
78     FILE *lastlog;
79 #endif
80     struct passwd *pw;
81     char *location;
82     FILE *wtmp;
83
84     if (!cfg.stamp_utmp)
85         return;
86
87     pw = getpwuid(getuid());
88     location = get_x_display();
89     memset(&utmp_entry, 0, sizeof(utmp_entry));
90     utmp_entry.ut_type = USER_PROCESS;
91     utmp_entry.ut_pid = getpid();
92     strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
93     strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
94     strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
95     strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
96     time(&utmp_entry.ut_time);
97
98 #if defined HAVE_PUTUTLINE
99     utmpname(UTMP_FILE);
100     setutent();
101     pututline(&utmp_entry);
102     endutent();
103 #endif
104
105     if ((wtmp = fopen(WTMP_FILE, "a")) != NULL) {
106         fwrite(&utmp_entry, 1, sizeof(utmp_entry), wtmp);
107         fclose(wtmp);
108     }
109
110 #ifdef HAVE_LASTLOG
111     memset(&lastlog_entry, 0, sizeof(lastlog_entry));
112     strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
113     strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
114     time(&lastlog_entry.ll_time);
115     if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
116         fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
117         fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
118         fclose(lastlog);
119     }
120 #endif
121
122     pty_stamped_utmp = 1;
123
124 #endif
125 }
126
127 static void cleanup_utmp(void)
128 {
129 #ifndef OMIT_UTMP
130     FILE *wtmp;
131
132     if (!cfg.stamp_utmp || !pty_stamped_utmp)
133         return;
134
135     utmp_entry.ut_type = DEAD_PROCESS;
136     memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
137     time(&utmp_entry.ut_time);
138
139     if ((wtmp = fopen(WTMP_FILE, "a")) != NULL) {
140         fwrite(&utmp_entry, 1, sizeof(utmp_entry), wtmp);
141         fclose(wtmp);
142     }
143
144     memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
145     utmp_entry.ut_time = 0;
146
147 #if defined HAVE_PUTUTLINE
148     utmpname(UTMP_FILE);
149     setutent();
150     pututline(&utmp_entry);
151     endutent();
152 #endif
153
154     pty_stamped_utmp = 0;              /* ensure we never double-cleanup */
155 #endif
156 }
157
158 static void sigchld_handler(int signum)
159 {
160     pid_t pid;
161     int status;
162     pid = waitpid(-1, &status, WNOHANG);
163     if (pid == pty_child_pid && (WIFEXITED(status) || WIFSIGNALED(status)))
164         pty_child_dead = TRUE;  
165 }
166
167 static void fatal_sig_handler(int signum)
168 {
169     signal(signum, SIG_DFL);
170     cleanup_utmp();
171     setuid(getuid());
172     raise(signum);
173 }
174
175 /*
176  * Called to set up the pty.
177  * 
178  * Returns an error message, or NULL on success.
179  *
180  * Also places the canonical host name into `realhost'. It must be
181  * freed by the caller.
182  */
183 static char *pty_init(char *host, int port, char **realhost, int nodelay)
184 {
185     int slavefd;
186     char name[FILENAME_MAX];
187     pid_t pid, pgrp;
188
189 #ifdef BSD_PTYS
190     {
191         const char chars1[] = "pqrstuvwxyz";
192         const char chars2[] = "0123456789abcdef";
193         const char *p1, *p2;
194         char master_name[20];
195
196         for (p1 = chars1; *p1; p1++)
197             for (p2 = chars2; *p2; p2++) {
198                 sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
199                 pty_master_fd = open(master_name, O_RDWR);
200                 if (pty_master_fd >= 0) {
201                     if (geteuid() == 0 ||
202                         access(master_name, R_OK | W_OK) == 0)
203                         goto got_one;
204                     close(pty_master_fd);
205                 }
206             }
207
208         /* If we get here, we couldn't get a tty at all. */
209         fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
210         exit(1);
211
212         got_one:
213         strcpy(name, master_name);
214         name[5] = 't';                 /* /dev/ptyXX -> /dev/ttyXX */
215     }
216 #else
217     pty_master_fd = open("/dev/ptmx", O_RDWR);
218
219     if (pty_master_fd < 0) {
220         perror("/dev/ptmx: open");
221         exit(1);
222     }
223
224     if (grantpt(pty_master_fd) < 0) {
225         perror("grantpt");
226         exit(1);
227     }
228     
229     if (unlockpt(pty_master_fd) < 0) {
230         perror("unlockpt");
231         exit(1);
232     }
233
234     name[FILENAME_MAX-1] = '\0';
235     strncpy(name, ptsname(pty_master_fd), FILENAME_MAX-1);
236 #endif
237
238     /*
239      * Set the backspace character to be whichever of ^H and ^? is
240      * specified by bksp_is_delete.
241      */
242     {
243         struct termios attrs;
244         tcgetattr(pty_master_fd, &attrs);
245         attrs.c_cc[VERASE] = cfg.bksp_is_delete ? '\177' : '\010';
246         tcsetattr(pty_master_fd, TCSANOW, &attrs);
247     }
248
249     /*
250      * Trap as many fatal signals as we can in the hope of having
251      * the best chance to clean up utmp before termination.
252      */
253     signal(SIGHUP, fatal_sig_handler);
254     signal(SIGINT, fatal_sig_handler);
255     signal(SIGQUIT, fatal_sig_handler);
256     signal(SIGILL, fatal_sig_handler);
257     signal(SIGABRT, fatal_sig_handler);
258     signal(SIGFPE, fatal_sig_handler);
259     signal(SIGPIPE, fatal_sig_handler);
260     signal(SIGALRM, fatal_sig_handler);
261     signal(SIGTERM, fatal_sig_handler);
262     signal(SIGSEGV, fatal_sig_handler);
263     signal(SIGUSR1, fatal_sig_handler);
264     signal(SIGUSR2, fatal_sig_handler);
265 #ifdef SIGBUS
266     signal(SIGBUS, fatal_sig_handler);
267 #endif
268 #ifdef SIGPOLL
269     signal(SIGPOLL, fatal_sig_handler);
270 #endif
271 #ifdef SIGPROF
272     signal(SIGPROF, fatal_sig_handler);
273 #endif
274 #ifdef SIGSYS
275     signal(SIGSYS, fatal_sig_handler);
276 #endif
277 #ifdef SIGTRAP
278     signal(SIGTRAP, fatal_sig_handler);
279 #endif
280 #ifdef SIGVTALRM
281     signal(SIGVTALRM, fatal_sig_handler);
282 #endif
283 #ifdef SIGXCPU
284     signal(SIGXCPU, fatal_sig_handler);
285 #endif
286 #ifdef SIGXFSZ
287     signal(SIGXFSZ, fatal_sig_handler);
288 #endif
289 #ifdef SIGIO
290     signal(SIGIO, fatal_sig_handler);
291 #endif
292     /* Also clean up utmp on normal exit. */
293     atexit(cleanup_utmp);
294     setup_utmp(name);
295
296     /*
297      * Fork and execute the command.
298      */
299     pid = fork();
300     if (pid < 0) {
301         perror("fork");
302         exit(1);
303     }
304
305     if (pid == 0) {
306         int i;
307         /*
308          * We are the child.
309          */
310
311         slavefd = open(name, O_RDWR);
312         if (slavefd < 0) {
313             perror("slave pty: open");
314             exit(1);
315         }
316
317 #ifdef BSD_PTYS
318         /* We need to chown/chmod the /dev/ttyXX device. */
319         {
320             struct group *gp = getgrnam("tty");
321             fchown(slavefd, getuid(), gp ? gp->gr_gid : -1);
322             fchmod(slavefd, 0600);
323         }
324 #endif
325
326         close(pty_master_fd);
327         close(0);
328         close(1);
329         close(2);
330         fcntl(slavefd, F_SETFD, 0);    /* don't close on exec */
331         dup2(slavefd, 0);
332         dup2(slavefd, 1);
333         dup2(slavefd, 2);
334         setsid();
335         ioctl(slavefd, TIOCSCTTY, 1);
336         pgrp = getpid();
337         tcsetpgrp(slavefd, pgrp);
338         setpgrp();
339         close(open(name, O_WRONLY, 0));
340         setpgrp();
341         /* In case we were setgid-utmp or setuid-root, drop privs. */
342         setgid(getgid());
343         setuid(getuid());
344         /* Close everything _else_, for tidiness. */
345         for (i = 3; i < 1024; i++)
346             close(i);
347         {
348             char term_env_var[10 + sizeof(cfg.termtype)];
349             sprintf(term_env_var, "TERM=%s", cfg.termtype);
350             putenv(term_env_var);
351         }
352         if (pty_argv)
353             execvp(pty_argv[0], pty_argv);
354         else {
355             char *shell = getenv("SHELL");
356             char *shellname;
357             if (cfg.login_shell) {
358                 char *p = strrchr(shell, '/');
359                 shellname = smalloc(2+strlen(shell));
360                 p = p ? p+1 : shell;
361                 sprintf(shellname, "-%s", p);
362             } else
363                 shellname = shell;
364             execl(getenv("SHELL"), shellname, NULL);
365         }
366
367         /*
368          * If we're here, exec has gone badly foom.
369          */
370         perror("exec");
371         exit(127);
372     } else {
373         close(slavefd);
374         pty_child_pid = pid;
375         pty_child_dead = FALSE;
376         signal(SIGCHLD, sigchld_handler);
377     }
378
379     return NULL;
380 }
381
382 /*
383  * Called to send data down the pty.
384  */
385 static int pty_send(char *buf, int len)
386 {
387     while (len > 0) {
388         int ret = write(pty_master_fd, buf, len);
389         if (ret < 0) {
390             perror("write pty master");
391             exit(1);
392         }
393         buf += ret;
394         len -= ret;
395     }
396     return 0;
397 }
398
399 /*
400  * Called to query the current socket sendability status.
401  */
402 static int pty_sendbuffer(void)
403 {
404     return 0;
405 }
406
407 /*
408  * Called to set the size of the window
409  */
410 static void pty_size(void)
411 {
412     struct winsize size;
413
414     size.ws_row = (unsigned short)rows;
415     size.ws_col = (unsigned short)cols;
416     size.ws_xpixel = (unsigned short) cols * font_dimension(0);
417     size.ws_ypixel = (unsigned short) rows * font_dimension(1);
418     ioctl(pty_master_fd, TIOCSWINSZ, (void *)&size);
419     return;
420 }
421
422 /*
423  * Send special codes.
424  */
425 static void pty_special(Telnet_Special code)
426 {
427     /* Do nothing! */
428     return;
429 }
430
431 static Socket pty_socket(void)
432 {
433     return NULL;                       /* shouldn't ever be needed */
434 }
435
436 static int pty_sendok(void)
437 {
438     return 1;
439 }
440
441 static void pty_unthrottle(int backlog)
442 {
443     /* do nothing */
444 }
445
446 static int pty_ldisc(int option)
447 {
448     return 0;                          /* neither editing nor echoing */
449 }
450
451 static int pty_exitcode(void)
452 {
453     /* Shouldn't ever be required */
454     return 0;
455 }
456
457 Backend pty_backend = {
458     pty_init,
459     pty_send,
460     pty_sendbuffer,
461     pty_size,
462     pty_special,
463     pty_socket,
464     pty_exitcode,
465     pty_sendok,
466     pty_ldisc,
467     pty_unthrottle,
468     1
469 };