blob: 4e0927fdaa035097e3ebe930d33969c1b7edd337 [file] [log] [blame]
Benjamin Franzkebfeda132012-01-30 14:04:04 +01001/*
2 * Copyright © 2012 Benjamin Franzke
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and
5 * its documentation for any purpose is hereby granted without fee, provided
6 * that the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation, and that the name of the copyright holders not be used in
9 * advertising or publicity pertaining to distribution of the software
10 * without specific, written prior permission. The copyright holders make
11 * no representations about the suitability of this software for any
12 * purpose. It is provided "as is" without express or implied warranty.
13 *
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
15 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
17 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
18 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
19 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
20 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 */
22
23#define _GNU_SOURCE
24
25#include "config.h"
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <assert.h>
31#include <errno.h>
32
33#include <error.h>
34#include <getopt.h>
35
36#include <sys/types.h>
37#include <sys/ioctl.h>
38#include <sys/stat.h>
39#include <sys/wait.h>
40#include <sys/socket.h>
41#include <sys/epoll.h>
42#include <sys/signalfd.h>
43#include <signal.h>
44#include <unistd.h>
45#include <fcntl.h>
46
47#include <termios.h>
48#include <linux/vt.h>
49#include <linux/major.h>
50
51#include <pwd.h>
52#include <grp.h>
53#include <security/pam_appl.h>
54
55#include <xf86drm.h>
56
57#ifdef HAVE_SYSTEMD_LOGIN
58#include <systemd/sd-login.h>
59#endif
60
61#include "weston-launch.h"
62
63struct weston_launch {
64 struct pam_conv pc;
65 pam_handle_t *ph;
66 int tty;
67 int ttynr;
68 int sock[2];
69 struct passwd *pw;
70
71 int epollfd;
72 int signalfd;
73
74 pid_t child;
75 int verbose;
76};
77
78static gid_t *
79read_groups(void)
80{
81 int n;
82 gid_t *groups;
83
84 n = getgroups(0, NULL);
85 groups = malloc(n * sizeof(gid_t));
86 if (!groups)
87 return NULL;
88
89 if (getgroups(n, groups) < 0) {
90 free(groups);
91 return NULL;
92 }
93 return groups;
94}
95
96static int
97weston_launch_allowed(struct weston_launch *wl)
98{
99 struct group *gr;
100 gid_t *groups;
101 int i;
102#ifdef HAVE_SYSTEMD_LOGIN
103 char *session, *seat;
104 int err;
105#endif
106
107 if (getuid() == 0)
108 return 1;
109
110 gr = getgrnam("weston-launch");
111 if (gr) {
112 groups = read_groups();
113 if (groups) {
114 for (i = 0; groups[i]; ++i) {
115 if (groups[i] == gr->gr_gid) {
116 free(groups);
117 return 1;
118 }
119 }
120 free(groups);
121 }
122 }
123
124#ifdef HAVE_SYSTEMD_LOGIN
125 err = sd_pid_get_session(getpid(), &session);
126 if (err == 0 && session) {
127 if (sd_session_is_active(session) &&
128 sd_session_get_seat(session, &seat) == 0) {
129 free(seat);
130 free(session);
131 return 1;
132 }
133 free(session);
134 }
135#endif
136
137 return 0;
138}
139
140static int
141pam_conversation_fn(int msg_count,
142 const struct pam_message **messages,
143 struct pam_response **responses,
144 void *user_data)
145{
146 return PAM_SUCCESS;
147}
148
149static int
150setup_pam(struct weston_launch *wl)
151{
152 int err;
153
154 wl->pc.conv = pam_conversation_fn;
155 wl->pc.appdata_ptr = wl;
156
157 err = pam_start("login", wl->pw->pw_name, &wl->pc, &wl->ph);
158 err = pam_set_item(wl->ph, PAM_TTY, ttyname(wl->tty));
159 if (err != PAM_SUCCESS) {
160 fprintf(stderr, "failed to set PAM_TTY item: %d: %s\n",
161 err, pam_strerror(wl->ph, err));
162 return -1;
163 }
164
165 err = pam_open_session(wl->ph, 0);
166 if (err != PAM_SUCCESS) {
167 fprintf(stderr, "failed to open pam session: %d: %s\n",
168 err, pam_strerror(wl->ph, err));
169 return -1;
170 }
171
172 return 0;
173}
174
175static int
176setup_launcher_socket(struct weston_launch *wl)
177{
178 struct epoll_event ev;
179
180 if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, wl->sock) < 0)
181 error(1, errno, "socketpair failed");
182
183 fcntl(wl->sock[0], F_SETFD, O_CLOEXEC);
184
185 memset(&ev, 0, sizeof ev);
186 ev.events = EPOLLIN;
187 ev.data.fd = wl->sock[0];
188 if (epoll_ctl(wl->epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
189 return -errno;
190
191 return 0;
192}
193
194static int
195setup_signals(struct weston_launch *wl)
196{
197 sigset_t mask;
198 struct sigaction sa;
199 struct epoll_event ev;
200
201 memset(&sa, 0, sizeof sa);
202 sa.sa_handler = SIG_DFL;
203 sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
204 assert(sigaction(SIGCHLD, &sa, NULL) == 0);
205
206 assert(sigemptyset(&mask) == 0);
207 sigaddset(&mask, SIGCHLD);
208 sigaddset(&mask, SIGINT);
209 sigaddset(&mask, SIGTERM);
210 assert(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
211
212 wl->signalfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
213 if (wl->signalfd < 0)
214 return -errno;
215
216 memset(&ev, 0, sizeof ev);
217 ev.events = EPOLLIN;
218 ev.data.fd = wl->signalfd;
219 if (epoll_ctl(wl->epollfd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
220 return -errno;
221
222 return 0;
223}
224
225static void
226setenv_fd(const char *env, int fd)
227{
228 char buf[32];
229
230 snprintf(buf, sizeof buf, "%d", fd);
231 setenv(env, buf, 1);
232}
233
234static int
235handle_setmaster(struct weston_launch *wl, struct msghdr *msg, ssize_t len)
236{
237 int drm_fd = -1, ret = -1;
238 struct cmsghdr *cmsg;
239 struct weston_launcher_set_master *message;
240
241 if (len != sizeof(*message)) {
242 error(0, 0, "missing value in setmaster request");
243 goto out;
244 }
245
246 message = msg->msg_iov->iov_base;
247
248 cmsg = CMSG_FIRSTHDR(msg);
249 if (!cmsg ||
250 cmsg->cmsg_level != SOL_SOCKET ||
251 cmsg->cmsg_type != SCM_RIGHTS) {
252 error(0, 0, "invalid control message");
253 goto out;
254 }
255
256 drm_fd = *(int *) CMSG_DATA(cmsg);
257 if (drm_fd == -1) {
258 error(0, 0, "missing drm fd in socket request");
259 goto out;
260 }
261
262 if (message->set_master)
263 ret = drmSetMaster(drm_fd);
264 else
265 ret = drmDropMaster(drm_fd);
266
267 close(drm_fd);
268out:
269 do {
270 len = send(wl->sock[0], &ret, sizeof ret, 0);
271 } while (len < 0 && errno == EINTR);
272 if (len < 0)
273 return -1;
274
275 return 0;
276}
277
278static int
279handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len)
280{
281 int fd = -1, ret = -1;
282 char control[CMSG_SPACE(sizeof(fd))];
283 struct cmsghdr *cmsg;
284 struct stat s;
285 struct msghdr nmsg;
286 struct iovec iov;
287 struct weston_launcher_open *message;
288
289 message = msg->msg_iov->iov_base;
290 if ((size_t)len < sizeof(*message))
291 goto err0;
292
293 /* Ensure path is null-terminated */
294 ((char *) message)[len-1] = '\0';
295
296 if (stat(message->path, &s) < 0)
297 goto err0;
298
299 fd = open(message->path, message->flags);
300 if (fd < 0)
301 goto err0;
302
303 if (major(s.st_rdev) != INPUT_MAJOR) {
304 close(fd);
305 fd = -1;
306 goto err0;
307 }
308
309err0:
310 memset(&nmsg, 0, sizeof nmsg);
311 nmsg.msg_iov = &iov;
312 nmsg.msg_iovlen = 1;
313 if (fd != -1) {
314 nmsg.msg_control = control;
315 nmsg.msg_controllen = sizeof control;
316 cmsg = CMSG_FIRSTHDR(&nmsg);
317 cmsg->cmsg_level = SOL_SOCKET;
318 cmsg->cmsg_type = SCM_RIGHTS;
319 cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
320 *(int *) CMSG_DATA(cmsg) = fd;
321 nmsg.msg_controllen = cmsg->cmsg_len;
322 ret = 0;
323 }
324 iov.iov_base = &ret;
325 iov.iov_len = sizeof ret;
326
327 if (wl->verbose)
328 fprintf(stderr, "weston-launch: opened %s: ret: %d, fd: %d\n",
329 message->path, ret, fd);
330 do {
331 len = sendmsg(wl->sock[0], &nmsg, 0);
332 } while (len < 0 && errno == EINTR);
333
334 if (len < 0)
335 return -1;
336
337 return 0;
338}
339
340static int
341handle_socket_msg(struct weston_launch *wl)
342{
343 char control[CMSG_SPACE(sizeof(int))];
344 char buf[BUFSIZ];
345 struct msghdr msg;
346 struct iovec iov;
347 int ret = -1;
348 ssize_t len;
349 struct weston_launcher_message *message;
350
351 memset(&msg, 0, sizeof(msg));
352 iov.iov_base = buf;
353 iov.iov_len = sizeof buf;
354 msg.msg_iov = &iov;
355 msg.msg_iovlen = 1;
356 msg.msg_control = control;
357 msg.msg_controllen = sizeof control;
358
359 do {
360 len = recvmsg(wl->sock[0], &msg, 0);
361 } while (len < 0 && errno == EINTR);
362
363 if (len < 1)
364 return -1;
365
366 message = (void *) buf;
367 switch (message->opcode) {
368 case WESTON_LAUNCHER_OPEN:
369 ret = handle_open(wl, &msg, len);
370 break;
371 case WESTON_LAUNCHER_DRM_SET_MASTER:
372 ret = handle_setmaster(wl, &msg, len);
373 break;
374 }
375
376 return ret;
377}
378
379static void
380quit(struct weston_launch *wl, int status)
381{
382 int err;
383
384 close(wl->epollfd);
385 close(wl->signalfd);
386 close(wl->sock[0]);
387
388 err = pam_close_session(wl->ph, 0);
389 if (err)
390 fprintf(stderr, "pam_close_session failed: %d: %s\n",
391 err, pam_strerror(wl->ph, err));
392 pam_end(wl->ph, err);
393
394 exit(status);
395}
396
397static int
398handle_signal(struct weston_launch *wl)
399{
400 struct signalfd_siginfo sig;
401 int pid, status;
402
403 if (read(wl->signalfd, &sig, sizeof sig) != sizeof sig) {
404 error(0, errno, "reading signalfd failed");
405 return -1;
406 }
407
408 switch (sig.ssi_signo) {
409 case SIGCHLD:
410 pid = waitpid(-1, &status, 0);
411 if (pid == wl->child) {
412 wl->child = 0;
413 quit(wl, WIFEXITED(status) ? WEXITSTATUS(status) : 0);
414 }
415 break;
416 case SIGTERM:
417 if (wl->child)
418 kill(wl->child, SIGTERM);
419 quit(wl, 0);
420 break;
421 case SIGINT:
422 if (wl->child)
423 kill(wl->child, SIGTERM);
424 break;
425 default:
426 return -1;
427 }
428
429 return 0;
430}
431
432static int
433setup_tty(struct weston_launch *wl, const char *tty)
434{
435 struct stat buf;
436 char *t;
437
438 if (tty) {
439 t = ttyname(STDIN_FILENO);
440 if (t && strcmp(t, tty) == 0)
441 wl->tty = STDIN_FILENO;
442 else
443 wl->tty = open(tty, O_RDWR | O_NOCTTY);
444 } else {
445 int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC);
446 char filename[16];
447
448 if (tty0 < 0)
449 error(1, errno, "count not open tty0");
450
451 if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1)
452 error(1, errno, "failed to find non-opened console");
453
454 snprintf(filename, sizeof filename, "/dev/tty%d", wl->ttynr);
455 wl->tty = open(filename, O_RDWR | O_NOCTTY);
456 close(tty0);
457 }
458
459 if (wl->tty < 0)
460 error(1, errno, "failed to open tty");
461
462 if (tty) {
463 if (fstat(wl->tty, &buf) < 0)
464 error(1, errno, "stat %s failed", tty);
465
466 if (major(buf.st_rdev) != TTY_MAJOR)
467 error(1, 0, "invalid tty device: %s", tty);
468
469 wl->ttynr = minor(buf.st_rdev);
470 }
471
472 return 0;
473}
474
475static void
476help(const char *name)
477{
478 fprintf(stderr, "Usage: %s [args...] [-- [weston args..]]\n", name);
479 fprintf(stderr, " -u, --user Start session as specified username\n");
480 fprintf(stderr, " -t, --tty Start session on alternative tty device\n");
481 fprintf(stderr, " -v, --verbose Be verbose\n");
482 fprintf(stderr, " -s, --sleep Sleep specified amount of time before exec\n");
483 fprintf(stderr, " -h, --help Display this help message\n");
484}
485
486int
487main(int argc, char *argv[])
488{
489 struct weston_launch wl;
490 char **env;
491 int i, c;
492 char **child_argv;
493 char *tty = NULL, *new_user = NULL;
494 int sleep_fork = 0;
495 struct option opts[] = {
496 { "user", required_argument, NULL, 'u' },
497 { "tty", required_argument, NULL, 't' },
498 { "verbose", no_argument, NULL, 'v' },
499 { "sleep", optional_argument, NULL, 's' },
500 { "help", no_argument, NULL, 'h' },
501 { 0, 0, NULL, 0 }
502 };
503
504 memset(&wl, 0, sizeof wl);
505
506 while ((c = getopt_long(argc, argv, "u:t:s::vh", opts, &i)) != -1) {
507 switch (c) {
508 case 'u':
509 new_user = optarg;
510 if (getuid() != 0)
511 error(1, 0, "Permission denied. -u allowed for root only");
512 break;
513 case 't':
514 tty = optarg;
515 break;
516 case 'v':
517 wl.verbose = 1;
518 break;
519 case 's':
520 if (optarg)
521 sleep_fork = atoi(optarg);
522 else
523 sleep_fork = 10;
524 break;
525 case 'h':
526 help("weston");
527 exit(1);
528 }
529 }
530
531 child_argv = &argv[optind-1];
532 child_argv[0] = BINDIR "/weston";
533
534 if (new_user)
535 wl.pw = getpwnam(new_user);
536 else
537 wl.pw = getpwuid(getuid());
538 if (wl.pw == NULL)
539 error(1, errno, "failed to get username");
540
541 if (!weston_launch_allowed(&wl))
542 error(1, 0, "Permission denied. You should..\n"
543#ifdef HAVE_SYSTEMD_LOGIN
544 " - run from an active and local (systemd) session.\n"
545#else
546 " - enable systemd session support for weston-launch.\n"
547#endif
548 " - add yourself to the 'weston-launch' group.");
549
550 if (setup_tty(&wl, tty) < 0)
551 return 1;
552
553 if (setup_pam(&wl) < 0)
554 return 1;
555
556 wl.epollfd = epoll_create1(EPOLL_CLOEXEC);
557 if (wl.epollfd < 0)
558 error(1, errno, "epoll create failed");
559
560 if (setup_launcher_socket(&wl) < 0)
561 return 1;
562
563 if (setup_signals(&wl) < 0)
564 return 1;
565
566 switch ((wl.child = fork())) {
567 case -1:
568 error(1, errno, "fork failed");
569 break;
570 case 0:
571 if (wl.verbose)
572 printf("weston-launch: spawned weston with pid: %d\n", getpid());
573 if (wl.tty != STDIN_FILENO) {
574 if (setsid() < 0)
575 error(1, errno, "setsid failed");
576 if (ioctl(wl.tty, TIOCSCTTY, 0) < 0)
577 error(1, errno, "TIOCSCTTY failed - tty is in use");
578 }
579
580 if (setgid(wl.pw->pw_gid) < 0 ||
581 setuid(wl.pw->pw_uid) < 0)
582 error(1, errno, "dropping privilidges failed");
583
584 if (sleep_fork) {
585 if (wl.verbose)
586 printf("weston-launch: waiting %d seconds\n", sleep_fork);
587 sleep(sleep_fork);
588 }
589
590 if (new_user) {
591 setenv("USER", wl.pw->pw_name, 1);
592 setenv("LOGNAME", wl.pw->pw_name, 1);
593 setenv("HOME", wl.pw->pw_dir, 1);
594 setenv("SHELL", wl.pw->pw_shell, 1);
595 }
596 env = pam_getenvlist(wl.ph);
597 if (env) {
598 for (i = 0; env[i]; ++i) {
599 if (putenv(env[i]) < 0)
600 error(0, 0, "putenv %s failed", env[i]);
601 }
602 free(env);
603 }
604
605 if (wl.tty != STDIN_FILENO)
606 setenv_fd("WESTON_TTY_FD", wl.tty);
607
608 setenv_fd("WESTON_LAUNCHER_SOCK", wl.sock[1]);
609
610 unsetenv("DISPLAY");
611
612 execv(child_argv[0], child_argv);
613 error(1, errno, "exec failed");
614 break;
615 default:
616 close(wl.sock[1]);
617 if (wl.tty != STDIN_FILENO)
618 close(wl.tty);
619
620 while (1) {
621 struct epoll_event ev;
622 int n;
623
624 n = epoll_wait(wl.epollfd, &ev, 1, -1);
625 if (n < 0)
626 error(0, errno, "epoll_wait failed");
627 if (n != 1)
628 continue;
629
630 if (ev.data.fd == wl.sock[0])
631 handle_socket_msg(&wl);
632 else if (ev.data.fd == wl.signalfd)
633 handle_signal(&wl);
634 }
635 break;
636 }
637
638 return 0;
639}