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