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