]> asedeno.scripts.mit.edu Git - PuTTY_svn.git/blob - unix/pty.c
Introduce the ability to control whether the shell run in pterm is a
[PuTTY_svn.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      * Trap as many fatal signals as we can in the hope of having
240      * the best chance to clean up utmp before termination.
241      */
242     signal(SIGHUP, fatal_sig_handler);
243     signal(SIGINT, fatal_sig_handler);
244     signal(SIGQUIT, fatal_sig_handler);
245     signal(SIGILL, fatal_sig_handler);
246     signal(SIGABRT, fatal_sig_handler);
247     signal(SIGFPE, fatal_sig_handler);
248     signal(SIGPIPE, fatal_sig_handler);
249     signal(SIGALRM, fatal_sig_handler);
250     signal(SIGTERM, fatal_sig_handler);
251     signal(SIGSEGV, fatal_sig_handler);
252     signal(SIGUSR1, fatal_sig_handler);
253     signal(SIGUSR2, fatal_sig_handler);
254 #ifdef SIGBUS
255     signal(SIGBUS, fatal_sig_handler);
256 #endif
257 #ifdef SIGPOLL
258     signal(SIGPOLL, fatal_sig_handler);
259 #endif
260 #ifdef SIGPROF
261     signal(SIGPROF, fatal_sig_handler);
262 #endif
263 #ifdef SIGSYS
264     signal(SIGSYS, fatal_sig_handler);
265 #endif
266 #ifdef SIGTRAP
267     signal(SIGTRAP, fatal_sig_handler);
268 #endif
269 #ifdef SIGVTALRM
270     signal(SIGVTALRM, fatal_sig_handler);
271 #endif
272 #ifdef SIGXCPU
273     signal(SIGXCPU, fatal_sig_handler);
274 #endif
275 #ifdef SIGXFSZ
276     signal(SIGXFSZ, fatal_sig_handler);
277 #endif
278 #ifdef SIGIO
279     signal(SIGIO, fatal_sig_handler);
280 #endif
281     /* Also clean up utmp on normal exit. */
282     atexit(cleanup_utmp);
283     setup_utmp(name);
284
285     /*
286      * Fork and execute the command.
287      */
288     pid = fork();
289     if (pid < 0) {
290         perror("fork");
291         exit(1);
292     }
293
294     if (pid == 0) {
295         int i;
296         /*
297          * We are the child.
298          */
299
300         slavefd = open(name, O_RDWR);
301         if (slavefd < 0) {
302             perror("slave pty: open");
303             exit(1);
304         }
305
306 #ifdef BSD_PTYS
307         /* We need to chown/chmod the /dev/ttyXX device. */
308         {
309             struct group *gp = getgrnam("tty");
310             fchown(slavefd, getuid(), gp ? gp->gr_gid : -1);
311             fchmod(slavefd, 0600);
312         }
313 #endif
314
315         close(pty_master_fd);
316         close(0);
317         close(1);
318         close(2);
319         fcntl(slavefd, F_SETFD, 0);    /* don't close on exec */
320         dup2(slavefd, 0);
321         dup2(slavefd, 1);
322         dup2(slavefd, 2);
323         setsid();
324         ioctl(slavefd, TIOCSCTTY, 1);
325         pgrp = getpid();
326         tcsetpgrp(slavefd, pgrp);
327         setpgrp();
328         close(open(name, O_WRONLY, 0));
329         setpgrp();
330         /* In case we were setgid-utmp or setuid-root, drop privs. */
331         setgid(getgid());
332         setuid(getuid());
333         /* Close everything _else_, for tidiness. */
334         for (i = 3; i < 1024; i++)
335             close(i);
336         {
337             char term_env_var[10 + sizeof(cfg.termtype)];
338             sprintf(term_env_var, "TERM=%s", cfg.termtype);
339             putenv(term_env_var);
340         }
341         if (pty_argv)
342             execvp(pty_argv[0], pty_argv);
343         else {
344             char *shell = getenv("SHELL");
345             char *shellname;
346             if (cfg.login_shell) {
347                 char *p = strrchr(shell, '/');
348                 shellname = smalloc(2+strlen(shell));
349                 p = p ? p+1 : shell;
350                 sprintf(shellname, "-%s", p);
351             } else
352                 shellname = shell;
353             execl(getenv("SHELL"), shellname, NULL);
354         }
355
356         /*
357          * If we're here, exec has gone badly foom.
358          */
359         perror("exec");
360         exit(127);
361     } else {
362         close(slavefd);
363         pty_child_pid = pid;
364         pty_child_dead = FALSE;
365         signal(SIGCHLD, sigchld_handler);
366     }
367
368     return NULL;
369 }
370
371 /*
372  * Called to send data down the pty.
373  */
374 static int pty_send(char *buf, int len)
375 {
376     while (len > 0) {
377         int ret = write(pty_master_fd, buf, len);
378         if (ret < 0) {
379             perror("write pty master");
380             exit(1);
381         }
382         buf += ret;
383         len -= ret;
384     }
385     return 0;
386 }
387
388 /*
389  * Called to query the current socket sendability status.
390  */
391 static int pty_sendbuffer(void)
392 {
393     return 0;
394 }
395
396 /*
397  * Called to set the size of the window
398  */
399 static void pty_size(void)
400 {
401     struct winsize size;
402
403     size.ws_row = (unsigned short)rows;
404     size.ws_col = (unsigned short)cols;
405     ioctl(pty_master_fd, TIOCSWINSZ, (void *)&size);
406     return;
407 }
408
409 /*
410  * Send special codes.
411  */
412 static void pty_special(Telnet_Special code)
413 {
414     /* Do nothing! */
415     return;
416 }
417
418 static Socket pty_socket(void)
419 {
420     return NULL;                       /* shouldn't ever be needed */
421 }
422
423 static int pty_sendok(void)
424 {
425     return 1;
426 }
427
428 static void pty_unthrottle(int backlog)
429 {
430     /* do nothing */
431 }
432
433 static int pty_ldisc(int option)
434 {
435     return 0;                          /* neither editing nor echoing */
436 }
437
438 static int pty_exitcode(void)
439 {
440     /* Shouldn't ever be required */
441     return 0;
442 }
443
444 Backend pty_backend = {
445     pty_init,
446     pty_send,
447     pty_sendbuffer,
448     pty_size,
449     pty_special,
450     pty_socket,
451     pty_exitcode,
452     pty_sendok,
453     pty_ldisc,
454     pty_unthrottle,
455     1
456 };