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