Support for BSD-style pty devices. Tested under Linux; might need
[sgt/putty] / 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 <sys/types.h>
14 #include <sys/wait.h>
15 #include <sys/ioctl.h>
16
17 #include "putty.h"
18
19 #ifndef FALSE
20 #define FALSE 0
21 #endif
22 #ifndef TRUE
23 #define TRUE 1
24 #endif
25
26 int pty_master_fd;
27 static int pty_child_pid;
28 static sig_atomic_t pty_child_dead;
29 char **pty_argv;
30
31 int pty_child_is_dead(void)
32 {
33 return pty_child_dead;
34 }
35
36 static void pty_size(void);
37
38 static void sigchld_handler(int signum)
39 {
40 pid_t pid;
41 int status;
42 pid = waitpid(-1, &status, WNOHANG);
43 if (pid == pty_child_pid && (WIFEXITED(status) || WIFSIGNALED(status)))
44 pty_child_dead = TRUE;
45 }
46
47 /*
48 * Called to set up the pty.
49 *
50 * Returns an error message, or NULL on success.
51 *
52 * Also places the canonical host name into `realhost'. It must be
53 * freed by the caller.
54 */
55 static char *pty_init(char *host, int port, char **realhost, int nodelay)
56 {
57 int slavefd;
58 char name[FILENAME_MAX];
59 pid_t pid, pgrp;
60
61 #ifdef BSD_PTYS
62 {
63 const char chars1[] = "pqrstuvwxyz";
64 const char chars2[] = "0123456789abcdef";
65 const char *p1, *p2;
66 char master_name[20];
67
68 for (p1 = chars1; *p1; p1++)
69 for (p2 = chars2; *p2; p2++) {
70 sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
71 pty_master_fd = open(master_name, O_RDWR);
72 if (pty_master_fd >= 0) {
73 if (geteuid() == 0 ||
74 access(master_name, R_OK | W_OK) == 0)
75 goto got_one;
76 close(pty_master_fd);
77 }
78 }
79
80 /* If we get here, we couldn't get a tty at all. */
81 fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
82 exit(1);
83
84 got_one:
85 strcpy(name, master_name);
86 name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */
87 }
88 #else
89 pty_master_fd = open("/dev/ptmx", O_RDWR);
90
91 if (pty_master_fd < 0) {
92 perror("/dev/ptmx: open");
93 exit(1);
94 }
95
96 if (grantpt(pty_master_fd) < 0) {
97 perror("grantpt");
98 exit(1);
99 }
100
101 if (unlockpt(pty_master_fd) < 0) {
102 perror("unlockpt");
103 exit(1);
104 }
105
106 name[FILENAME_MAX-1] = '\0';
107 strncpy(name, ptsname(pty_master_fd), FILENAME_MAX-1);
108 #endif
109
110 /*
111 * Fork and execute the command.
112 */
113 pid = fork();
114 if (pid < 0) {
115 perror("fork");
116 exit(1);
117 }
118
119 if (pid == 0) {
120 int i;
121 /*
122 * We are the child.
123 */
124
125 slavefd = open(name, O_RDWR);
126 if (slavefd < 0) {
127 perror("slave pty: open");
128 exit(1);
129 }
130
131 #ifdef BSD_PTYS
132 /* We need to chown/chmod the /dev/ttyXX device. */
133 {
134 struct group *gp = getgrnam("tty");
135 fchown(slavefd, getuid(), gp ? gp->gr_gid : -1);
136 fchmod(slavefd, 0600);
137 }
138 #endif
139
140 close(pty_master_fd);
141 close(0);
142 close(1);
143 close(2);
144 fcntl(slavefd, F_SETFD, 0); /* don't close on exec */
145 dup2(slavefd, 0);
146 dup2(slavefd, 1);
147 dup2(slavefd, 2);
148 setsid();
149 ioctl(slavefd, TIOCSCTTY, 1);
150 pgrp = getpid();
151 tcsetpgrp(slavefd, pgrp);
152 setpgrp();
153 close(open(name, O_WRONLY, 0));
154 setpgrp();
155 /* In case we were setgid-utmp or setuid-root, drop privs. */
156 setgid(getgid());
157 setuid(getuid());
158 /* Close everything _else_, for tidiness. */
159 for (i = 3; i < 1024; i++)
160 close(i);
161 {
162 char term_env_var[10 + sizeof(cfg.termtype)];
163 sprintf(term_env_var, "TERM=%s", cfg.termtype);
164 putenv(term_env_var);
165 }
166 if (pty_argv)
167 execvp(pty_argv[0], pty_argv);
168 else
169 execl(getenv("SHELL"), getenv("SHELL"), NULL);
170 /*
171 * If we're here, exec has gone badly foom.
172 */
173 perror("exec");
174 exit(127);
175 } else {
176 close(slavefd);
177 pty_child_pid = pid;
178 pty_child_dead = FALSE;
179 signal(SIGCHLD, sigchld_handler);
180 }
181
182 return NULL;
183 }
184
185 /*
186 * Called to send data down the pty.
187 */
188 static int pty_send(char *buf, int len)
189 {
190 while (len > 0) {
191 int ret = write(pty_master_fd, buf, len);
192 if (ret < 0) {
193 perror("write pty master");
194 exit(1);
195 }
196 buf += ret;
197 len -= ret;
198 }
199 return 0;
200 }
201
202 /*
203 * Called to query the current socket sendability status.
204 */
205 static int pty_sendbuffer(void)
206 {
207 return 0;
208 }
209
210 /*
211 * Called to set the size of the window
212 */
213 static void pty_size(void)
214 {
215 struct winsize size;
216
217 size.ws_row = (unsigned short)rows;
218 size.ws_col = (unsigned short)cols;
219 ioctl(pty_master_fd, TIOCSWINSZ, (void *)&size);
220 return;
221 }
222
223 /*
224 * Send special codes.
225 */
226 static void pty_special(Telnet_Special code)
227 {
228 /* Do nothing! */
229 return;
230 }
231
232 static Socket pty_socket(void)
233 {
234 return NULL; /* shouldn't ever be needed */
235 }
236
237 static int pty_sendok(void)
238 {
239 return 1;
240 }
241
242 static void pty_unthrottle(int backlog)
243 {
244 /* do nothing */
245 }
246
247 static int pty_ldisc(int option)
248 {
249 return 0; /* neither editing nor echoing */
250 }
251
252 static int pty_exitcode(void)
253 {
254 /* Shouldn't ever be required */
255 return 0;
256 }
257
258 Backend pty_backend = {
259 pty_init,
260 pty_send,
261 pty_sendbuffer,
262 pty_size,
263 pty_special,
264 pty_socket,
265 pty_exitcode,
266 pty_sendok,
267 pty_ldisc,
268 pty_unthrottle,
269 1
270 };