48c68eacb730ba594cf46ecaca8b68bec62c418c
[vte.git] / gnome-pty-helper / gnome-pty-helper.c
1 /*
2  * gnome-pty.c:  Helper setuid application used to open a pseudo-
3  * terminal, set the permissions, ownership and record user login
4  * information
5  *
6  * Author:
7  *    Miguel de Icaza (miguel@gnu.org)
8  *
9  * Parent application talks to us via a couple of sockets that are strategically
10  * placed on file descriptors 0 and 1 (STDIN_FILENO and STDOUT_FILENO).
11  *
12  * We use the STDIN_FILENO to read and write the protocol information and we use
13  * the STDOUT_FILENO to pass the file descriptors (we need two different file
14  * descriptors as using a socket for both data transfers and file descriptor
15  * passing crashes some BSD kernels according to Theo de Raadt)
16  *
17  * A sample protocol is used:
18  *
19  * OPEN_PTY             => 1 <tag> <master-pty-fd> <slave-pty-fd>
20  *                      => 0
21  *
22  * CLOSE_PTY  <tag>     => void
23  *
24  * <tag> is a pointer.  If tag is NULL, then the ptys were not allocated.
25  * ptys are passed using file descriptor passing on the stdin file descriptor
26  *
27  * We use as little as possible external libraries.
28  */
29 #include <config.h>
30
31 /* Use this to pull SCM_RIGHTS definition on IRIX */
32 #if defined(irix) || defined (__irix__) || defined(sgi) || defined (__sgi__)
33 #    define _XOPEN_SOURCE 1
34 extern char *strdup(const char *);
35 #endif
36
37 #include <sys/types.h>
38 #include <sys/time.h>
39 #include <sys/resource.h>
40 #include <sys/stat.h>
41 #include <limits.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <signal.h>
45 #include <sys/param.h>
46 #include <fcntl.h>
47 #include <termios.h>
48 #include <errno.h>
49 #include <termios.h>
50 #include <pwd.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <stdio.h>
54 #include <utmp.h>
55 #include <grp.h>
56 #include <glib/galloca.h>
57 #include "gnome-pty.h"
58 #include "gnome-login-support.h"
59
60 /* For PATH_MAX on BSD-like systems. */
61 #ifdef HAVE_SYS_SYSLIMITS_H
62 #include <sys/syslimits.h>
63 #endif
64
65 static struct passwd *pwent;
66 static char login_name_buffer [48];
67 static char *login_name, *display_name;
68
69 struct pty_info {
70         struct pty_info *next;
71         char   *line;
72         void   *data;
73         char   utmp, wtmp, lastlog;
74 };
75
76 typedef struct pty_info pty_info;
77
78 static pty_info *pty_list;
79
80 #ifdef HAVE_SENDMSG
81 #include <sys/socket.h>
82 #include <sys/uio.h>
83
84 #ifdef HAVE_SYS_UN_H /* Linux libc5 */
85 #include <sys/un.h>
86 #endif
87
88 #ifndef CMSG_DATA /* Linux libc5 */
89 /* Ancillary data object manipulation macros.  */
90 #if !defined __STRICT_ANSI__ && defined __GNUC__ && __GNUC__ >= 2
91 # define CMSG_DATA(cmsg) ((cmsg)->cmsg_data)
92 #else
93 # define CMSG_DATA(cmsg) ((unsigned char *) ((struct cmsghdr *) (cmsg) + 1))
94 #endif
95 #endif /* CMSG_DATA */
96
97 static struct cmsghdr *cmptr;
98 static int CONTROLLEN;
99
100 static int
101 init_msg_pass (void)
102 {
103         CONTROLLEN = (CMSG_DATA (cmptr) - (unsigned char *)cmptr) + sizeof(int);
104         cmptr = malloc (CONTROLLEN);
105
106         if (cmptr)
107                 return 0;
108
109         return -1;
110 }
111
112 static int
113 pass_fd (int client_fd, int fd)
114 {
115         struct iovec  iov[1];
116         struct msghdr msg;
117         char          buf [1];
118
119         iov [0].iov_base = buf;
120         iov [0].iov_len  = 1;
121
122         msg.msg_iov        = iov;
123         msg.msg_iovlen     = 1;
124         msg.msg_name       = NULL;
125         msg.msg_namelen    = 0;
126         msg.msg_control    = (caddr_t) cmptr;
127         msg.msg_controllen = CONTROLLEN;
128
129         cmptr->cmsg_level = SOL_SOCKET;
130         cmptr->cmsg_type  = SCM_RIGHTS;
131         cmptr->cmsg_len   = CONTROLLEN;
132         *(int *)CMSG_DATA (cmptr) = fd;
133
134         if (sendmsg (client_fd, &msg, 0) != 1)
135                 return -1;
136
137         return 0;
138 }
139
140 #elif defined(__sgi) && !defined(HAVE_SENDMSG)
141
142 /*
143  * IRIX 6.2 is like 4.3BSD; it will not have HAVE_SENDMSG set,
144  * because msghdr used msg_accrights and msg_accrightslen rather
145  * than the newer msg_control and msg_controllen fields configure
146  * checks.  The SVR4 code below doesn't work because pipe()
147  * semantics are controlled by the svr3pipe systune variable,
148  * which defaults to uni-directional pipes.  Also sending
149  * file descriptors through pipes isn't implemented.
150  */
151
152 #include <sys/socket.h>
153 #include <sys/uio.h>
154
155 static int
156 init_msg_pass ()
157 {
158   return 0;
159 }
160
161 static int
162 pass_fd (int client_fd, int fd)
163 {
164   struct iovec  iov[1];
165   struct msghdr msg;
166   char          buf [1];
167
168   iov [0].iov_base = buf;
169   iov [0].iov_len  = 1;
170
171   msg.msg_iov        = iov;
172   msg.msg_iovlen     = 1;
173   msg.msg_name       = NULL;
174   msg.msg_namelen    = 0;
175   msg.msg_accrights    = (caddr_t) &fd;
176   msg.msg_accrightslen = sizeof(fd);
177
178   if (sendmsg (client_fd, &msg, 0) != 1)
179     return -1;
180
181   return 0;
182 }
183
184 #else
185 #include <stropts.h>
186 #ifdef I_SENDFD
187 static int
188 init_msg_pass ()
189 {
190         /* nothing */
191         return 0;
192 }
193
194 int
195 pass_fd (int client_fd, int fd)
196 {
197         if (ioctl (client_fd, I_SENDFD, fd) < 0)
198                 return -1;
199         return 0;
200 }
201 #endif
202 #endif
203
204 static void
205 pty_free (pty_info *pi)
206 {
207         free (pi);
208 }
209
210 static void
211 pty_remove (pty_info *pi)
212 {
213         pty_info *l, *last;
214
215         last = (void *) 0;
216
217         for (l = pty_list; l; l = l->next) {
218                 if (l == pi) {
219                         if (last == (void *) 0)
220                                 pty_list = pi->next;
221                         else
222                                 last->next = pi->next;
223                         free (pi->line);
224                         pty_free (pi);
225                         return;
226                 }
227                 last = l;
228         }
229
230         exit (1);
231 }
232
233 static void
234 shutdown_pty (pty_info *pi)
235 {
236         if (pi->utmp || pi->wtmp || pi->lastlog)
237                 if (pi->data)
238                         write_logout_record (pi->data, pi->utmp, pi->wtmp);
239
240         pty_remove (pi);
241 }
242
243 static void
244 shutdown_helper (void)
245 {
246         pty_info *pi;
247
248         for (pi = pty_list; pi; pi = pty_list)
249                 shutdown_pty (pi);
250 }
251
252 static pty_info *
253 pty_add (int utmp, int wtmp, int lastlog, char *line)
254 {
255         pty_info *pi = malloc (sizeof (pty_info));
256
257         if (pi == NULL) {
258                 shutdown_helper ();
259                 exit (1);
260         }
261
262         memset (pi, 0, sizeof (pty_info));
263
264         if (strncmp (line, "/dev/", 5))
265                 pi->line = strdup (line);
266         else
267                 pi->line = strdup (line+5);
268
269         if (pi->line == NULL) {
270                 shutdown_helper ();
271                 exit (1);
272         }
273
274         pi->next = pty_list;
275         pi->utmp = utmp;
276         pi->wtmp = wtmp;
277         pi->lastlog = lastlog;
278
279         pty_list = pi;
280
281         return pi;
282 }
283
284 static int
285 path_max (void)
286 {
287 #ifdef _PC_PATH_MAX
288         return pathconf ("/", _PC_PATH_MAX);
289 #else
290 #  ifdef PATH_MAX
291         return PATH_MAX;
292 #  else
293         return 1024;
294 #  endif
295 #endif
296 }
297
298 static struct termios*
299 init_term_with_defaults(struct termios* term)
300 {
301         /*
302          *      openpty assumes POSIX termios so this should be portable.
303          *      Don't change this to a structure init - POSIX doesn't say
304          *      anything about field order.
305          */
306         memset(term, 0, sizeof(struct termios));
307
308         term->c_iflag = 0
309 #ifdef BRKINT
310           | BRKINT
311 #endif
312 #ifdef ICRNL
313           | ICRNL
314 #endif
315 #ifdef IMAXBEL
316           | IMAXBEL
317 #endif
318 #ifdef IXON
319           | IXON
320 #endif
321 #ifdef IXANY
322           | IXANY
323 #endif
324           ;
325         term->c_oflag = 0
326 #ifdef OPOST
327           | OPOST
328 #endif
329 #ifdef ONLCR
330           | ONLCR
331 #endif
332 #ifdef NL0
333           | NL0
334 #endif
335 #ifdef CR0
336           | CR0
337 #endif
338 #ifdef TAB0
339           | TAB0
340 #endif
341 #ifdef BS0
342           | BS0
343 #endif
344 #ifdef VT0
345           | VT0
346 #endif
347 #ifdef FF0
348           | FF0
349 #endif
350           ;
351         term->c_cflag = 0
352 #ifdef CREAD
353           | CREAD
354 #endif
355 #ifdef CS8
356           | CS8
357 #endif
358 #ifdef HUPCL
359           | HUPCL
360 #endif
361           ;
362 #ifdef EXTB
363         cfsetispeed(term, EXTB);
364         cfsetospeed(term, EXTB);
365 #else
366 #   ifdef B38400
367         cfsetispeed(term, B38400);
368         cfsetospeed(term, B38400);
369 #   else
370 #       ifdef B9600
371         cfsetispeed(term, B9600);
372         cfsetospeed(term, B9600);
373 #       endif
374 #   endif
375 #endif /* EXTB */
376
377         term->c_lflag = 0
378 #ifdef ECHO
379           | ECHO
380 #endif
381 #ifdef ICANON
382           | ICANON
383 #endif
384 #ifdef ISIG
385           | ISIG
386 #endif
387 #ifdef IEXTEN
388           | IEXTEN
389 #endif
390 #ifdef ECHOE
391           | ECHOE
392 #endif
393 #ifdef ECHOKE
394           | ECHOKE
395 #endif
396 #ifdef ECHOK
397           | ECHOK
398 #endif
399 #ifdef ECHOCTL
400           | ECHOCTL
401 #endif
402           ;
403
404 #ifdef N_TTY
405         /* should really be a check for c_line, but maybe this is good enough */
406         term->c_line = N_TTY;
407 #endif
408
409         /* These two may overlap so set them first */
410         /* That setup means, that read() will be blocked until  */
411         /* at least 1 symbol will be read.                      */
412         term->c_cc[VMIN]  = 1;
413         term->c_cc[VTIME] = 0;
414
415         /*
416          * Now set the characters. This is of course a religious matter
417          * but we use the defaults, with erase bound to the key gnome-terminal
418          * maps.
419          *
420          * These are the ones set by "stty sane".
421          */
422
423         term->c_cc[VINTR]  = 'C'-64;
424         term->c_cc[VQUIT]  = '\\'-64;
425         term->c_cc[VERASE] = 127;
426         term->c_cc[VKILL]  = 'U'-64;
427         term->c_cc[VEOF]   = 'D'-64;
428 #ifdef VSWTC
429         term->c_cc[VSWTC]  = 255;
430 #endif
431         term->c_cc[VSTART] = 'Q'-64;
432         term->c_cc[VSTOP]  = 'S'-64;
433         term->c_cc[VSUSP]  = 'Z'-64;
434         term->c_cc[VEOL]   = 255;
435
436         /*
437          *      Extended stuff.
438          */
439
440 #ifdef VREPRINT
441         term->c_cc[VREPRINT] = 'R'-64;
442 #endif
443 #ifdef VSTATUS
444         term->c_cc[VSTATUS]  = 'T'-64;
445 #endif
446 #ifdef VDISCARD
447         term->c_cc[VDISCARD] = 'O'-64;
448 #endif
449 #ifdef VWERASE
450         term->c_cc[VWERASE]  = 'W'-64;
451 #endif
452 #ifdef VLNEXT
453         term->c_cc[VLNEXT]   = 'V'-64;
454 #endif
455 #ifdef VDSUSP
456         term->c_cc[VDSUSP]   = 'Y'-64;
457 #endif
458 #ifdef VEOL2
459         term->c_cc[VEOL2]    = 255;
460 #endif
461     return term;
462 }
463
464 static int
465 open_ptys (int utmp, int wtmp, int lastlog)
466 {
467         char *term_name;
468         int status, master_pty, slave_pty;
469         pty_info *p;
470         int result;
471         uid_t savedUid;
472         gid_t savedGid;
473         struct group *group_info;
474         struct termios term;
475
476         term_name = (char *) g_alloca (path_max () + 1);
477
478         if (term_name == NULL) {
479                 exit (1);
480         }
481         /* Initialize term */
482         init_term_with_defaults(&term);
483
484         /* root privileges */
485         savedUid = geteuid();
486         savedGid = getegid();
487
488         /* drop privileges to the user level */
489 #if defined(HAVE_SETEUID)
490         seteuid (pwent->pw_uid);
491         setegid (pwent->pw_gid);
492 #elif defined(HAVE_SETREUID)
493         setreuid (savedUid, pwent->pw_uid);
494         setregid (savedGid, pwent->pw_gid);
495 #else
496 #error "No means to drop privileges! Huge security risk! Won't compile."
497 #endif
498         /* Open pty with privileges of the user */
499         status = openpty (&master_pty, &slave_pty, term_name, &term, NULL);
500
501         /* Restore saved privileges to root */
502 #ifdef HAVE_SETEUID
503         seteuid (savedUid);
504         setegid (savedGid);
505 #elif defined(HAVE_SETREUID)
506         setreuid (pwent->pw_uid, savedUid);
507         setregid (pwent->pw_gid, savedGid);
508 #else
509 #error "No means to raise privileges! Huge security risk! Won't compile."
510 #endif
511         /* openpty() failed, reject request */
512         if (status == -1) {
513                 result = 0;
514                 n_write (STDIN_FILENO, &result, sizeof (result));
515                 return 0;
516         }
517
518         /* a bit tricky, we re-do the part of the openpty()  */
519         /* that required root privileges, and, hence, failed */
520         group_info = getgrnam ("tty");
521         fchown (slave_pty, getuid (), group_info ? group_info->gr_gid : -1);
522         fchmod (slave_pty, S_IRUSR | S_IWUSR | S_IWGRP);
523         /* It's too late to call revoke at this time... */
524         /* revoke(term_name); */
525
526         /* add pty to the list of allocated by us */
527         p = pty_add (utmp, wtmp, lastlog, term_name);
528         result = 1;
529
530         if (n_write (STDIN_FILENO, &result, sizeof (result)) != sizeof (result) ||
531             n_write (STDIN_FILENO, &p, sizeof (p)) != sizeof (p) ||
532             pass_fd (STDOUT_FILENO, master_pty)  == -1 ||
533             pass_fd (STDOUT_FILENO, slave_pty)   == -1) {
534                 exit (0);
535         }
536
537         if (utmp || wtmp || lastlog) {
538                 p->data = write_login_record (login_name, display_name,
539                                               term_name, utmp, wtmp, lastlog);
540         }
541
542         close (master_pty);
543         close (slave_pty);
544
545         return 1;
546 }
547
548 static void
549 close_pty_pair (void *tag)
550 {
551         pty_info *pi;
552
553         for (pi = pty_list; pi; pi = pi->next) {
554                 if (tag == pi) {
555                         shutdown_pty (pi);
556                         break;
557                 }
558         }
559 }
560
561 #define MB (1024*1024)
562
563 struct {
564         int limit;
565         int value;
566 } sensible_limits [] = {
567         { RLIMIT_CPU,    120 },
568         { RLIMIT_FSIZE,  1 * MB },
569         { RLIMIT_DATA,   1 * MB },
570         { RLIMIT_STACK,  1 * MB },
571 #ifdef RLIMIT_AS
572         { RLIMIT_AS,     1 * MB },
573 #endif
574         { RLIMIT_NOFILE, 10 },
575 #ifdef RLIMIT_NPROC
576         { RLIMIT_NPROC,  5 },
577 #endif
578         { -1, -1 }
579 };
580
581 static void
582 sanity_checks (void)
583 {
584         int stderr_fd;
585         int i, open_max;
586         int flag;
587
588         /*
589          * Make sure stdin/stdout are open.  This is a requirement
590          * for our program to work and closes potential security holes.
591          */
592         if ((fcntl (0, F_GETFL, &flag) == -1 && errno == EBADF) ||
593             (fcntl (1, F_GETFL, &flag) == -1 && errno == EBADF)) {
594                 exit (1);
595         }
596
597         /*
598          * File descriptors 0 and 1 have been setup by the parent process
599          * to be used for the protocol exchange and for transfering
600          * file descriptors.
601          *
602          * Make stderr point to a terminal.
603          */
604         if (fcntl (2, F_GETFL, &flag) == -1 && errno == EBADF) {
605                 stderr_fd = open ("/dev/tty", O_RDWR);
606                 if (stderr_fd == -1) {
607                         stderr_fd = open ("/dev/null", O_RDWR);
608                         if (stderr_fd == -1)
609                                 exit (1);
610                 }
611
612                 if (stderr_fd != 2)
613                         while (dup2 (stderr_fd, 2) == -1 && errno == EINTR)
614                                 ;
615         }
616
617         /* Close any file descriptor we do not use */
618         open_max = sysconf (_SC_OPEN_MAX);
619         for (i = 3; i < open_max; i++) {
620                 close (i);
621         }
622
623         /* Check sensible resource limits */
624         for (i = 0; sensible_limits [i].value != -1; i++) {
625                 struct rlimit rlim;
626
627                 if (getrlimit (sensible_limits [i].limit, &rlim) != 0)
628                         continue;
629
630                 if (rlim.rlim_cur != RLIM_INFINITY &&
631                     rlim.rlim_cur < sensible_limits [i].value) {
632                         if (setrlimit (sensible_limits [i].limit, &rlim) != 0) {
633                                 fprintf (stderr, "Living environment not ok\n");
634                                 exit (1);
635                         }
636                 }
637         }
638
639         /* Make sure SIGIO/SIGINT is SIG_IGN */
640         {
641                 struct sigaction sa;
642                 sigset_t sigset;
643
644                 sa.sa_handler = SIG_IGN;
645                 sigemptyset (&sa.sa_mask);
646                 sa.sa_flags = 0;
647
648                 sigemptyset(&sigset);
649                 sigaddset(&sigset, SIGIO);
650                 sigaddset(&sigset, SIGINT);
651                 sigprocmask(SIG_UNBLOCK, &sigset, NULL);
652
653                 sigaction (SIGIO, &sa, NULL);
654                 sigaction (SIGINT, &sa, NULL);
655         }
656 }
657
658 static gboolean done;
659
660 static void
661 exit_handler (int signum)
662 {
663         done = TRUE;
664 }
665
666
667 int
668 main (int argc, char *argv [])
669 {
670         int res, n;
671         void *tag;
672         GnomePtyOps op;
673         const char *logname;
674
675         sanity_checks ();
676
677         pwent = NULL;
678
679         logname = getenv ("LOGNAME");
680         if (logname != NULL) {
681                 pwent = getpwnam (logname);
682                 if (pwent != NULL && pwent->pw_uid != getuid ()) {
683                         /* LOGNAME is lying, fall back to looking up the uid */
684                         pwent = NULL;
685                 }
686         }
687
688         if (pwent == NULL)
689                 pwent = getpwuid (getuid ());
690
691         if (pwent)
692                 login_name = pwent->pw_name;
693         else {
694                 sprintf (login_name_buffer, "#%u", (unsigned int)getuid ());
695                 login_name = login_name_buffer;
696         }
697
698         display_name = getenv ("DISPLAY");
699         if (!display_name)
700                 display_name = "localhost";
701
702         done = FALSE;
703
704         /* Make sure we clean up utmp/wtmp even under vncserver */
705         signal (SIGHUP, exit_handler);
706         signal (SIGTERM, exit_handler);
707
708         if (init_msg_pass () == -1)
709                 exit (1);
710
711         while (!done) {
712                 res = n_read (STDIN_FILENO, &op, sizeof (op));
713
714                 if (res != sizeof (op)) {
715                         done = TRUE;
716                         continue;
717                 }
718
719                 switch (op) {
720                 case GNOME_PTY_OPEN_PTY_UTMP:
721                         open_ptys (1, 0, 0);
722                         break;
723
724                 case GNOME_PTY_OPEN_PTY_UWTMP:
725                         open_ptys (1, 1, 0);
726                         break;
727
728                 case GNOME_PTY_OPEN_PTY_WTMP:
729                         open_ptys (0, 1, 0);
730                         break;
731
732                 case GNOME_PTY_OPEN_PTY_LASTLOG:
733                         open_ptys (0, 0, 1);
734                         break;
735
736                 case GNOME_PTY_OPEN_PTY_LASTLOGUTMP:
737                         open_ptys (1, 0, 1);
738                         break;
739
740                 case GNOME_PTY_OPEN_PTY_LASTLOGUWTMP:
741                         open_ptys (1, 1, 1);
742                         break;
743
744                 case GNOME_PTY_OPEN_PTY_LASTLOGWTMP:
745                         open_ptys (0, 1, 1);
746                         break;
747
748                 case GNOME_PTY_OPEN_NO_DB_UPDATE:
749                         open_ptys (0, 0, 0);
750                         break;
751
752                 case GNOME_PTY_RESET_TO_DEFAULTS:
753                         break;
754
755                 case GNOME_PTY_CLOSE_PTY:
756                         n = n_read (STDIN_FILENO, &tag, sizeof (tag));
757                         if (n != sizeof (tag)) {
758                                 shutdown_helper ();
759                                 exit (1);
760                         }
761                         close_pty_pair (tag);
762                         break;
763                 }
764
765         }
766
767         shutdown_helper ();
768         return 0;
769 }
770