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