1 /* -*- c-basic-offset:2; tab-width:2; indent-tabs-mode:nil -*- */
2
3 #include "bl_pty.h"
4
5 #include <sys/types.h>
6 #include <sys/ioctl.h>
7 #include <sys/socket.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <string.h> /* memcpy */
13 #include <unistd.h>
14
15 #include "bl_def.h" /* HAVE_SETSID, LINE_MAX */
16 #include "bl_debug.h"
17 #include "bl_mem.h" /* realloc/free */
18 #include "bl_path.h" /* BL_LIBEXECDIR */
19
20 #ifndef MSG_NOSIGNAL
21 #define MSG_NOSIGNAL 0
22 #endif
23
24 #if 0
25 #define __DEBUG
26 #endif
27
28 typedef enum {
29 GNOME_PTY_OPEN_PTY_UTMP = 1,
30 GNOME_PTY_OPEN_PTY_UWTMP,
31 GNOME_PTY_OPEN_PTY_WTMP,
32 GNOME_PTY_OPEN_PTY_LASTLOG,
33 GNOME_PTY_OPEN_PTY_LASTLOGUTMP,
34 GNOME_PTY_OPEN_PTY_LASTLOGUWTMP,
35 GNOME_PTY_OPEN_PTY_LASTLOGWTMP,
36 GNOME_PTY_OPEN_NO_DB_UPDATE,
37 GNOME_PTY_RESET_TO_DEFAULTS,
38 GNOME_PTY_CLOSE_PTY,
39 GNOME_PTY_SYNCH
40
41 } GnomePtyOps;
42
43 typedef struct {
44 int pty;
45 void *tag;
46
47 } pty_helper_tag_t;
48
49 /* --- static variables --- */
50
51 static pid_t myself = -1;
52 static pid_t pty_helper_pid = -1;
53 static int pty_helper_tunnel = -1;
54 static pty_helper_tag_t *pty_helper_tags = NULL;
55 static u_int num_pty_helper_tags;
56 static GnomePtyOps pty_helper_open_ops = GNOME_PTY_OPEN_PTY_UTMP;
57
58 /* --- static functions --- */
59
setup_child(int fd)60 static void setup_child(int fd) {
61 char *tty;
62
63 tty = ttyname(fd);
64
65 #ifdef __DEBUG
66 bl_debug_printf(BL_DEBUG_TAG " Setting up child pty(name:%s, fd:%d)\n", tty ? tty : "(none)", fd);
67 #endif
68
69 /* Try to reopen the pty to acquire it as our controlling terminal. */
70 if (tty != NULL) {
71 int _fd;
72
73 if ((_fd = open(tty, O_RDWR)) != -1) {
74 if (fd != -1) {
75 close(fd);
76 }
77
78 fd = _fd;
79 }
80 }
81
82 if (fd == -1) {
83 exit(EXIT_FAILURE);
84 }
85
86 /* Start a new session and become process group leader. */
87 #if defined(HAVE_SETSID) && defined(HAVE_SETPGID)
88 setsid();
89 setpgid(0, 0);
90 #endif
91
92 #ifdef TIOCSCTTY
93 ioctl(fd, TIOCSCTTY, fd);
94 #endif
95
96 #if defined(HAVE_ISASTREAM) && defined(I_PUSH)
97 if (isastream(fd) == 1) {
98 ioctl(fd, I_PUSH, "ptem");
99 ioctl(fd, I_PUSH, "ldterm");
100 ioctl(fd, I_PUSH, "ttcompat");
101 }
102 #endif
103
104 if (fd != STDIN_FILENO) {
105 dup2(fd, STDIN_FILENO);
106 }
107
108 if (fd != STDOUT_FILENO) {
109 dup2(fd, STDOUT_FILENO);
110 }
111
112 if (fd != STDERR_FILENO) {
113 dup2(fd, STDERR_FILENO);
114 }
115
116 if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) {
117 close(fd);
118 }
119
120 close(pty_helper_tunnel);
121 }
122
123 #ifdef HAVE_RECVMSG
read_ptypair(int tunnel,int * master,int * slave)124 static void read_ptypair(int tunnel, int *master, int *slave) {
125 int count;
126 int ret;
127 char control[LINE_MAX];
128 char iobuf[LINE_MAX];
129 struct cmsghdr *cmsg;
130 struct msghdr msg;
131 struct iovec vec;
132
133 for (count = 0; count < 2; count++) {
134 vec.iov_base = iobuf;
135 vec.iov_len = sizeof(iobuf);
136 msg.msg_name = NULL;
137 msg.msg_namelen = 0;
138 msg.msg_iov = &vec;
139 msg.msg_iovlen = 1;
140 msg.msg_control = control;
141 msg.msg_controllen = sizeof(control);
142
143 if ((ret = recvmsg(tunnel, &msg, MSG_NOSIGNAL)) == -1) {
144 return;
145 }
146
147 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
148 if (cmsg->cmsg_type == SCM_RIGHTS) {
149 memcpy(&ret, CMSG_DATA(cmsg), sizeof(ret));
150
151 if (count == 0) {
152 /* Without this, pty master is blocked in poll. */
153 fcntl(ret, F_SETFL, O_NONBLOCK);
154 *master = ret;
155 } else /* if( i == 1) */
156 {
157 fcntl(ret, F_SETFL, O_NONBLOCK);
158 *slave = ret;
159 }
160 }
161 }
162 }
163 }
164 #elif defined(I_RECVFD)
read_ptypair(int tunnel,int * master,int * slave)165 static void read_ptypair(int tunnel, int *master, int *slave) {
166 int ret;
167
168 if (ioctl(tunnel, I_RECVFD, &ret) == -1) {
169 return;
170 }
171
172 *master = ret;
173
174 if (ioctl(tunnel, I_RECVFD, &ret) == -1) {
175 return;
176 }
177
178 *slave = ret;
179 }
180 #endif
181
182 #ifdef HAVE_SOCKETPAIR
open_pipe(int * a,int * b)183 static int open_pipe(int *a, int *b) {
184 int p[2];
185 int ret = -1;
186
187 #ifdef PF_UNIX
188 #ifdef SOCK_STREAM
189 ret = socketpair(PF_UNIX, SOCK_STREAM, 0, p);
190 #else
191 #ifdef SOCK_DGRAM
192 ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, p);
193 #endif
194 #endif
195
196 if (ret == 0) {
197 *a = p[0];
198 *b = p[1];
199
200 return 0;
201 }
202 #endif
203
204 return ret;
205 }
206 #else
open_pipe(int * a,int * b)207 static int open_pipe(int *a, int *b) {
208 int p[2];
209 int ret = -1;
210
211 ret = pipe(p);
212
213 if (ret == 0) {
214 *a = p[0];
215 *b = p[1];
216 }
217
218 return ret;
219 }
220 #endif
221
222 /* read ignoring EINTR and EAGAIN. */
n_read(int fd,void * buffer,size_t buf_size)223 static ssize_t n_read(int fd, void *buffer, size_t buf_size) {
224 size_t n;
225 char *p;
226 int ret;
227
228 n = 0;
229 p = buffer;
230
231 while (n < buf_size) {
232 ret = read(fd, p + n, buf_size - n);
233 switch (ret) {
234 case 0:
235 return n;
236
237 case -1:
238 switch (errno) {
239 case EINTR:
240 case EAGAIN:
241 #ifdef ERESTART
242 case ERESTART:
243 #endif
244 break;
245
246 default:
247 return -1;
248 }
249
250 default:
251 n += ret;
252 }
253 }
254
255 return n;
256 }
257
258 /* write ignoring EINTR and EAGAIN. */
n_write(int fd,const void * buffer,size_t buf_size)259 static ssize_t n_write(int fd, const void *buffer, size_t buf_size) {
260 size_t n;
261 const char *p;
262 int ret;
263
264 n = 0;
265 p = buffer;
266
267 while (n < buf_size) {
268 ret = write(fd, p + n, buf_size - n);
269 switch (ret) {
270 case 0:
271 return n;
272
273 case -1:
274 switch (errno) {
275 case EINTR:
276 case EAGAIN:
277 #ifdef ERESTART
278 case ERESTART:
279 #endif
280 break;
281
282 default:
283 return -1;
284 }
285
286 default:
287 n += ret;
288 }
289 }
290
291 return n;
292 }
293
stop_pty_helper(void)294 static void stop_pty_helper(void) {
295 if (pty_helper_pid != -1) {
296 free(pty_helper_tags);
297 pty_helper_tags = NULL;
298
299 num_pty_helper_tags = 0;
300
301 close(pty_helper_tunnel);
302 pty_helper_tunnel = -1;
303
304 /* child processes might trigger this function on exit(). */
305 if (myself == getpid()) {
306 kill(pty_helper_pid, SIGTERM);
307 }
308
309 pty_helper_pid = -1;
310 }
311 }
312
start_pty_helper(void)313 static int start_pty_helper(void) {
314 int tmp[2];
315 int tunnel;
316
317 if (access(BL_LIBEXECDIR("mlterm") "/gnome-pty-helper", X_OK) != 0) {
318 bl_error_printf("Couldn't run %s", BL_LIBEXECDIR("mlterm") "/gnome-pty-helper");
319
320 return 0;
321 }
322
323 /* Create a communication link with the helper. */
324 tmp[0] = open("/dev/null", O_RDONLY);
325 if (tmp[0] == -1) {
326 return 0;
327 }
328
329 tmp[1] = open("/dev/null", O_RDONLY);
330 if (tmp[1] == -1) {
331 close(tmp[0]);
332
333 return 0;
334 }
335
336 if (open_pipe(&pty_helper_tunnel, &tunnel) != 0) {
337 return 0;
338 }
339
340 close(tmp[0]);
341 close(tmp[1]);
342
343 pty_helper_pid = fork();
344 if (pty_helper_pid == -1) {
345 return 0;
346 }
347
348 if (pty_helper_pid == 0) {
349 /* Child */
350
351 int count;
352
353 /* No need to close all descriptors because gnome-pty-helper does that
354 * anyway. */
355 for (count = 0; count < 3; count++) {
356 close(count);
357 }
358
359 dup2(tunnel, STDIN_FILENO);
360 dup2(tunnel, STDOUT_FILENO);
361 close(tunnel);
362 close(pty_helper_tunnel);
363
364 execl(BL_LIBEXECDIR("mlterm") "/gnome-pty-helper", "gnome-pty-helper", NULL);
365
366 exit(EXIT_SUCCESS);
367 }
368
369 close(tunnel);
370
371 myself = getpid();
372 atexit(stop_pty_helper);
373
374 return 1;
375 }
376
377 /* --- global functions --- */
378
bl_pty_fork(int * master,int * slave)379 pid_t bl_pty_fork(int *master, int *slave) {
380 pid_t pid;
381 int ret;
382 void *tag;
383
384 if (pty_helper_pid == -1) {
385 if (!start_pty_helper()) {
386 return -1;
387 }
388 }
389
390 /* Send our request. */
391 if (n_write(pty_helper_tunnel, &pty_helper_open_ops, sizeof(pty_helper_open_ops)) !=
392 sizeof(pty_helper_open_ops)) {
393 return -1;
394 }
395
396 #ifdef __DEBUG
397 bl_debug_printf(BL_DEBUG_TAG " Sent request to helper.\n");
398 #endif
399
400 /* Read back the response. */
401 if (n_read(pty_helper_tunnel, &ret, sizeof(ret)) != sizeof(ret)) {
402 return -1;
403 }
404
405 #ifdef __DEBUG
406 bl_debug_printf(BL_DEBUG_TAG " Received response from helper.\n");
407 #endif
408
409 if (ret == 0) {
410 return -1;
411 }
412
413 #ifdef __DEBUG
414 bl_debug_printf(BL_DEBUG_TAG " Helper returns success.\n");
415 #endif
416
417 /* Read back a tag. */
418 if (n_read(pty_helper_tunnel, &tag, sizeof(tag)) != sizeof(tag)) {
419 return -1;
420 }
421
422 #ifdef __DEBUG
423 bl_debug_printf(BL_DEBUG_TAG " Tag = %p.\n", tag);
424 #endif
425
426 /* Receive the master and slave ptys. */
427 read_ptypair(pty_helper_tunnel, master, slave);
428
429 if ((*master == -1) || (*slave == -1)) {
430 close(*master);
431 close(*slave);
432 return -1;
433 }
434
435 #ifdef __DEBUG
436 bl_debug_printf(BL_DEBUG_TAG " Master pty %d / Slave pty %d.\n", *master, *slave);
437 #endif
438
439 pty_helper_tags =
440 realloc(pty_helper_tags, sizeof(pty_helper_tag_t) * (num_pty_helper_tags + 1));
441 pty_helper_tags[num_pty_helper_tags].pty = *master;
442 pty_helper_tags[num_pty_helper_tags++].tag = tag;
443
444 pid = fork();
445 if (pid == -1) {
446 /* Error */
447
448 bl_error_printf("Failed to fork.\n");
449
450 close(*master);
451 close(*slave);
452 } else if (pid == 0) {
453 /* child */
454
455 close(*master);
456
457 setup_child(*slave);
458 }
459
460 return pid;
461 }
462
bl_pty_close(int master)463 int bl_pty_close(int master) {
464 u_int count;
465
466 for (count = 0; count < num_pty_helper_tags; count++) {
467 if (pty_helper_tags[count].pty == master) {
468 void *tag;
469 GnomePtyOps ops;
470
471 tag = pty_helper_tags[count].tag;
472 ops = GNOME_PTY_CLOSE_PTY;
473
474 if (n_write(pty_helper_tunnel, &ops, sizeof(ops)) != sizeof(ops) ||
475 n_write(pty_helper_tunnel, &tag, sizeof(tag)) != sizeof(tag)) {
476 return -1;
477 }
478
479 ops = GNOME_PTY_SYNCH;
480
481 if (n_write(pty_helper_tunnel, &ops, sizeof(ops)) != sizeof(ops)) {
482 return -1;
483 }
484
485 #if 0
486 /* This can be blocked (CentOS 5, vte 0.14.0) */
487 n_read(pty_helper_tunnel, &ops, 1);
488 #endif
489
490 pty_helper_tags[count] = pty_helper_tags[--num_pty_helper_tags];
491
492 return 0;
493 }
494 }
495
496 return close(master);
497 }
498
bl_pty_helper_set_flag(int lastlog,int utmp,int wtmp)499 void bl_pty_helper_set_flag(int lastlog, int utmp, int wtmp) {
500 int idx;
501
502 GnomePtyOps ops[8] = {
503 GNOME_PTY_OPEN_NO_DB_UPDATE, /* 0 0 0 */
504 GNOME_PTY_OPEN_PTY_LASTLOG, /* 0 0 1 */
505 GNOME_PTY_OPEN_PTY_UTMP, /* 0 1 0 */
506 GNOME_PTY_OPEN_PTY_LASTLOGUTMP, /* 0 1 1 */
507 GNOME_PTY_OPEN_PTY_WTMP, /* 1 0 0 */
508 GNOME_PTY_OPEN_PTY_LASTLOGWTMP, /* 1 0 1 */
509 GNOME_PTY_OPEN_PTY_UWTMP, /* 1 1 0 */
510 GNOME_PTY_OPEN_PTY_LASTLOGUWTMP, /* 1 1 1 */
511 };
512
513 idx = 0;
514
515 if (lastlog) {
516 idx += 1;
517 }
518
519 if (utmp) {
520 idx += 2;
521 }
522
523 if (wtmp) {
524 idx += 4;
525 }
526
527 pty_helper_open_ops = ops[idx];
528 }
529