diff options
author | Pierre Ossman <ossman@cendio.se> | 2018-07-16 15:58:06 +0200 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2020-03-12 12:03:32 +0100 |
commit | 1af1cfdf8709dd1a5574efa19fb4f0e68a98021e (patch) | |
tree | d04dd9e3a1a5e24d5a6955088f1f8bedcd616b45 /unix/vncserver | |
parent | 79960594551aa32a99506ff87fb0fed5f7bee5eb (diff) | |
download | tigervnc-1af1cfdf8709dd1a5574efa19fb4f0e68a98021e.tar.gz tigervnc-1af1cfdf8709dd1a5574efa19fb4f0e68a98021e.zip |
Start sessions via PAM
This sets up a more correct session as there are key tasks that
need to be performed by PAM. E.g. systemd will allocate cgroups
and start base services.
In order to easily handle this as a system service the mapping of
displays is now done via a configuration file.
Diffstat (limited to 'unix/vncserver')
-rw-r--r-- | unix/vncserver/CMakeLists.txt | 14 | ||||
-rw-r--r-- | unix/vncserver/tigervnc.pam | 11 | ||||
-rwxr-xr-x | unix/vncserver/vncserver.in | 40 | ||||
-rw-r--r-- | unix/vncserver/vncserver.man | 86 | ||||
-rw-r--r-- | unix/vncserver/vncserver.users | 8 | ||||
-rw-r--r-- | unix/vncserver/vncserver@.service.in | 23 | ||||
-rw-r--r-- | unix/vncserver/vncsession-start.in | 43 | ||||
-rw-r--r-- | unix/vncserver/vncsession.c | 592 | ||||
-rw-r--r-- | unix/vncserver/vncsession.man | 75 |
9 files changed, 755 insertions, 137 deletions
diff --git a/unix/vncserver/CMakeLists.txt b/unix/vncserver/CMakeLists.txt index 9f085e0d..127c7a3e 100644 --- a/unix/vncserver/CMakeLists.txt +++ b/unix/vncserver/CMakeLists.txt @@ -1,10 +1,20 @@ +add_executable(vncsession vncsession.c) +target_link_libraries(vncsession ${PAM_LIBS}) + configure_file(vncserver@.service.in vncserver@.service @ONLY) +configure_file(vncsession-start.in vncsession-start @ONLY) configure_file(vncserver.in vncserver @ONLY) -install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vncserver DESTINATION ${BIN_DIR}) -install(FILES vncserver.man DESTINATION ${MAN_DIR}/man1 RENAME vncserver.1) +install(TARGETS vncsession DESTINATION ${SBIN_DIR}) +install(FILES tigervnc.pam DESTINATION ${SYSCONF_DIR}/pam.d RENAME tigervnc) +install(FILES vncsession.man DESTINATION ${MAN_DIR}/man8 RENAME vncsession.8) +install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vncserver DESTINATION ${LIBEXEC_DIR}) +install(FILES vncserver.man DESTINATION ${MAN_DIR}/man8 RENAME vncserver.8) install(FILES vncserver-config-defaults vncserver-config-mandatory DESTINATION ${SYSCONF_DIR}/tigervnc) +install(FILES vncserver.users DESTINATION ${SYSCONF_DIR}/tigervnc) + if(INSTALL_SYSTEMD_UNITS) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/vncserver@.service DESTINATION ${UNIT_DIR}) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vncsession-start DESTINATION ${LIBEXEC_DIR}) endif() diff --git a/unix/vncserver/tigervnc.pam b/unix/vncserver/tigervnc.pam new file mode 100644 index 00000000..0f4cb3a7 --- /dev/null +++ b/unix/vncserver/tigervnc.pam @@ -0,0 +1,11 @@ +#%PAM-1.0 +# pam_selinux.so close should be the first session rule +-session required pam_selinux.so close +session required pam_loginuid.so +-session required pam_selinux.so open +session required pam_namespace.so +session optional pam_keyinit.so force revoke +session required pam_limits.so +-session optional pam_systemd.so +session required pam_unix.so +-session optional pam_reauthorize.so prepare diff --git a/unix/vncserver/vncserver.in b/unix/vncserver/vncserver.in index 0b1e9a9e..68a39af0 100755 --- a/unix/vncserver/vncserver.in +++ b/unix/vncserver/vncserver.in @@ -88,8 +88,6 @@ if ((@ARGV == 1) && ($ARGV[0] =~ /^:(\d+)$/)) { if (!&CheckDisplayNumber($displayNumber)) { die "A VNC server is already running as :$displayNumber\n"; } -} elsif (@ARGV == 0) { - $displayNumber = &GetDisplayNumber(); } else { &Usage(); } @@ -127,8 +125,8 @@ LoadConfig($vncUserConfig); LoadConfig($vncSystemConfigMandatoryFile, 1); # -# Check whether VNC authentication is enabled, and if so, prompt the user to -# create a VNC password if they don't already have one. +# Check whether VNC authentication is enabled, and if so, check that +# a VNC password has been created. # $securityTypeArgSpecified = 0; @@ -154,17 +152,10 @@ if ($config{'password'} || if ((!$securityTypeArgSpecified || $vncAuthEnabled) && !$passwordArgSpecified) { ($z,$z,$mode) = stat("$vncUserDir/passwd"); if (!(-e "$vncUserDir/passwd") || ($mode & 077)) { - warn "\nYou will require a password to access your desktops.\n\n"; - system($exedir."vncpasswd -q $vncUserDir/passwd"); - if (($? >> 8) != 0) { - exit 1; - } + die "VNC authentication enabled, but no password file created.\n"; } } -$desktopLog = "$vncUserDir/$host:$displayNumber.log"; -unlink($desktopLog); - # # Find a desktop session to run # @@ -264,11 +255,6 @@ warn "\nNew '$desktopName' desktop is $host:$displayNumber\n\n"; warn "Starting desktop session $sessionname\n"; -warn "Log file is $desktopLog\n\n"; - -open STDOUT, "> $desktopLog" -open STDERR, ">&STDOUT" - exec(@cmd); die "Failed to start session.\n"; @@ -315,24 +301,6 @@ sub LoadConfig { # -# GetDisplayNumber gets the lowest available display number. A display number -# n is taken if something is listening on the VNC server port (5900+n) or the -# X server port (6000+n). -# - -sub GetDisplayNumber -{ - foreach $n (1..99) { - if (&CheckDisplayNumber($n)) { - return $n+0; # Bruce Mah's workaround for bug in perl 5.005_02 - } - } - - die "$prog: no free display number on $host.\n"; -} - - -# # Load a session desktop file # sub LoadXSession { @@ -439,7 +407,7 @@ sub CheckDisplayNumber sub Usage { - die("\nusage: $prog [:<number>]\n\n"); + die("\nusage: $prog <display>\n\n"); } diff --git a/unix/vncserver/vncserver.man b/unix/vncserver/vncserver.man index 8398ed04..f1017be9 100644 --- a/unix/vncserver/vncserver.man +++ b/unix/vncserver/vncserver.man @@ -1,85 +1 @@ -.TH vncserver 1 "" "TigerVNC" "Virtual Network Computing" -.SH NAME -vncserver \- start a VNC server -.SH SYNOPSIS -.B vncserver -.RI [: display# ] -.SH DESCRIPTION -.B vncserver -is used to start a VNC (Virtual Network Computing) desktop. -.B vncserver -is a Perl script which simplifies the process of starting an Xvnc server. It -runs Xvnc with appropriate options and starts a window manager on the VNC -desktop. - -.B vncserver -can be run with no options at all. In this case it will choose the first -available display number (usually :1), start Xvnc with that display number, -and start the default window manager in the Xvnc session. You can also -specify the display number, in which case vncserver will attempt to start -Xvnc with that display number and exit if the display number is not -available. For example: - -.RS -vncserver :13 -.RE - -vncserver will exit once the started session exits. - -.SH FILES -Several VNC-related files are found in the directory $HOME/.vnc: -.TP -/etc/tigervnc/vncserver-config-defaults -The optional system-wide equivalent of $HOME/.vnc/config. If this file exists -and defines options to be passed to Xvnc, they will be used as defaults for -users. The user's $HOME/.vnc/config overrides settings configured in this file. -The overall configuration file load order is: this file, $HOME/.vnc/config, -and then /etc/tigervnc/vncserver-config-mandatory. None are required to exist. -.TP -/etc/tigervnc/vncserver-config-mandatory -The optional system-wide equivalent of $HOME/.vnc/config. If this file exists -and defines options to be passed to Xvnc, they will override any of the same -options defined in a user's $HOME/.vnc/config. This file offers a mechanism -to establish some basic form of system-wide policy. WARNING! There is -nothing stopping users from constructing their own vncserver-like script -that calls Xvnc directly to bypass any options defined in -/etc/tigervnc/vncserver-config-mandatory. The overall configuration file load -order is: /etc/tigervnc/vncserver-config-defaults, $HOME/.vnc/config, and then -this file. None are required to exist. -.TP -$HOME/.vnc/config -An optional server config file wherein options to be passed to Xvnc are listed -to avoid hard-coding them to the physical invocation. List options in this file -one per line. For those requiring an argument, simply separate the option from -the argument with an equal sign, for example: "geometry=2000x1200" or -"securitytypes=vncauth,tlsvnc". Options without an argument are simply listed -as a single word, for example: "localhost" or "alwaysshared". - -The special option -.B session -can be used to control which session type will be started. This should match -one of the files in \fI/usr/share/xsessions\fP. E.g. if there is a file called -"gnome.desktop", then "session=gnome" would be set to use that session type. -.TP -$HOME/.vnc/passwd -The VNC password file. -.TP -$HOME/.vnc/\fIhost\fP:\fIdisplay#\fP.log -The log file for Xvnc and the session. - -.SH SEE ALSO -.BR vncviewer (1), -.BR vncpasswd (1), -.BR vncconfig (1), -.BR Xvnc (1) -.br -https://www.tigervnc.org - -.SH AUTHOR -Tristan Richardson, RealVNC Ltd., D. R. Commander and others. - -VNC was originally developed by the RealVNC team while at Olivetti -Research Ltd / AT&T Laboratories Cambridge. TightVNC additions were -implemented by Constantin Kaplinsky. Many other people have since -participated in development, testing and support. This manual is part -of the TigerVNC software suite. +.so man8/vncsession.8 diff --git a/unix/vncserver/vncserver.users b/unix/vncserver/vncserver.users new file mode 100644 index 00000000..0a63784c --- /dev/null +++ b/unix/vncserver/vncserver.users @@ -0,0 +1,8 @@ +# TigerVNC User assignment +# +# This file assigns users to specific VNC display numbers. +# The syntax is <display>=<username>. E.g.: +# +# :2=andrew +# :3=lisa + diff --git a/unix/vncserver/vncserver@.service.in b/unix/vncserver/vncserver@.service.in index d4ca9bf7..36ad02d0 100644 --- a/unix/vncserver/vncserver@.service.in +++ b/unix/vncserver/vncserver@.service.in @@ -1,13 +1,11 @@ # The vncserver service unit file # # Quick HowTo: -# 1. Copy this file to /etc/systemd/system/vncserver@.service -# 2. Switches for vncserver should be entered in ~/.vnc/config rather than -# hard-coded into this unit file. See the vncserver(1) manpage. -# 3. Replace <USER> with the desired user -# ("runuser -l <USER> -c /usr/bin/vncserver %i") -# 4. Run `systemctl daemon-reload` -# 5. Run `systemctl enable vncserver@:<display>.service` +# 1. Add a user mapping to /etc/tigervnc/vncserver.users. +# 2. Adjust the global or user configuration. See the +# vncsession(8) manpage for details. (OPTIONAL) +# 3. Run `systemctl enable vncserver@:<display>.service` +# 4. Run `systemctl start vncserver@:<display>.service` # # DO NOT RUN THIS SERVICE if your local area network is # untrusted! For a secure way of using VNC, you should @@ -24,9 +22,9 @@ # You can then point a VNC client on hostA at vncdisplay N of localhost and with # the help of ssh, you end up seeing what hostB makes available on port 590M # -# Use "-nolisten tcp" to prevent X connections to your VNC server via TCP. +# Use "nolisten=tcp" to prevent X connections to your VNC server via TCP. # -# Use "-localhost" to prevent remote VNC clients connecting except when +# Use "localhost" to prevent remote VNC clients connecting except when # doing so through a secure tunnel. See the "-via" option in the # `man vncviewer' manual page. @@ -37,11 +35,8 @@ After=syslog.target network.target [Service] Type=forking -# Clean any existing files in /tmp/.X11-unix environment -ExecStartPre=/bin/sh -c '@BIN_DIR@/vncserver -kill %i > /dev/null 2>&1 || :' -ExecStart=/usr/sbin/runuser -l <USER> -c "@BIN_DIR@/vncserver %i" -PIDFile=/home/<USER>/.vnc/%H%i.pid -ExecStop=/bin/sh -c '@BIN_DIR@/vncserver -kill %i > /dev/null 2>&1 || :' +ExecStart=/usr/libexec/vncsession-start %i +PIDFile=/var/run/vncsession-%i.pid [Install] WantedBy=multi-user.target diff --git a/unix/vncserver/vncsession-start.in b/unix/vncserver/vncsession-start.in new file mode 100644 index 00000000..b4a6e007 --- /dev/null +++ b/unix/vncserver/vncsession-start.in @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Copyright 2019 Pierre Ossman for Cendio AB +# +# This is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +# USA. +# + +USERSFILE="@SYSCONF_DIR@/tigervnc/vncserver.users" + +if [ $# -ne 1 ]; then + echo "Syntax:" >&2 + echo " $0 <display>" >&2 + exit 1 +fi + +if [ ! -f "${USERSFILE}" ]; then + echo "Users file ${USERSFILE} missing" >&2 + exit 1 +fi + +DISPLAY="$1" + +USER=`grep "^${DISPLAY}=" "${USERSFILE}" 2>/dev/null | head -1 | cut -d = -f 2-` + +if [ -z "${USER}" ]; then + echo "No user configured for display ${DISPLAY}" >&2 + exit 1 +fi + +exec "@SBIN_DIR@/vncsession" "${USER}" "${DISPLAY}" diff --git a/unix/vncserver/vncsession.c b/unix/vncserver/vncsession.c new file mode 100644 index 00000000..d2d328e8 --- /dev/null +++ b/unix/vncserver/vncsession.c @@ -0,0 +1,592 @@ +/* + * Copyright 2018 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <config.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <syslog.h> +#include <security/pam_appl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> + +extern char **environ; + +// PAM service name +const char *SERVICE_NAME = "tigervnc"; + +// Main script PID +volatile static pid_t script = -1; + +// Daemon completion pipe +int daemon_pipe_fd = -1; + +static int +begin_daemon(void) +{ + int devnull, fds[2]; + pid_t pid; + + /* Pipe to report startup success */ + if (pipe(fds) < 0) { + perror("pipe"); + return -1; + } + + /* First fork */ + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid != 0) { + ssize_t len; + char buf[1]; + + close(fds[1]); + + /* Wait for child to finish startup */ + len = read(fds[0], buf, 1); + if (len != 1) { + fprintf(stderr, "Failure daemonizing\n"); + _exit(EX_OSERR); + } + + _exit(0); + } + + close(fds[0]); + daemon_pipe_fd = fds[1]; + + /* Detach from terminal */ + if (setsid() < 0) { + perror("setsid"); + return -1; + } + + /* Another fork required to fully detach */ + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + + if (pid == 0) + _exit(0); + + /* Send all stdio to /dev/null */ + devnull = open("/dev/null", O_RDWR); + if (devnull < 0) { + fprintf(stderr, "Failed to open /dev/null: %s\n", strerror(errno)); + return -1; + } + if ((dup2(devnull, 0) < 0) || + (dup2(devnull, 1) < 0) || + (dup2(devnull, 2) < 0)) { + perror("dup2"); + return -1; + } + if (devnull > 2) + close(devnull); + + /* Full control of access bits */ + umask(0); + + /* A safe working directory */ + if (chdir("/") < 0) { + perror("chdir"); + return -1; + } + + return 0; +} + +static void +finish_daemon(void) +{ + write(daemon_pipe_fd, "+", 1); + close(daemon_pipe_fd); + daemon_pipe_fd = -1; +} + +static void +sighandler(int sig) +{ + if (script > 0) { + kill(script, SIGTERM); + } +} + +static void +setup_signals(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sighandler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + sigaction(SIGHUP, &act, NULL); + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + sigaction(SIGPIPE, &act, NULL); +} + +static int +conv(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) +{ + /* Opening a session should not require a conversation */ + return PAM_CONV_ERR; +} + +static pam_handle_t * +run_pam(int *pamret, const char *username, const char *display) +{ + pam_handle_t *pamh; + + /* Say hello to PAM */ + struct pam_conv pconv; + pconv.conv = conv; + pconv.appdata_ptr = NULL; + *pamret = pam_start(SERVICE_NAME, username, &pconv, &pamh); + if (*pamret != PAM_SUCCESS) { + /* pam_strerror requires a pamh argument, but if pam_start + fails, pamh is invalid. In practice, at least the Linux + implementation of pam_strerror does not use the pamh + argument, but let's take care - avoid pam_strerror here. */ + syslog(LOG_CRIT, "pam_start failed: %d", *pamret); + return NULL; + } + + /* ConsoleKit and systemd (and possibly others) uses this to + determine if the session is local or not. It needs to be set to + something that can't be interpreted as localhost. We don't know + what the client's address is though, and that might change on + reconnects. We also don't want to set it to some text string as + that results in a DNS lookup with e.g. libaudit. Let's use a + fake IPv4 address from the documentation range. */ + /* FIXME: This might throw an error on a IPv6-only host */ + *pamret = pam_set_item(pamh, PAM_RHOST, "203.0.113.20"); + if (*pamret != PAM_SUCCESS) { + syslog(LOG_CRIT, "pam_set_item(PAM_RHOST) failed: %d (%s)", + *pamret, pam_strerror(pamh, *pamret)); + return pamh; + } + +#ifdef PAM_XDISPLAY + /* Let PAM modules use this to tag the session as a graphical one */ + *pamret = pam_set_item(pamh, PAM_XDISPLAY, display); + /* Note: PAM_XDISPLAY is only supported by modern versions of PAM */ + if (*pamret != PAM_BAD_ITEM && *pamret != PAM_SUCCESS) { + syslog(LOG_CRIT, "pam_set_item(PAM_XDISPLAY) failed: %d (%s)", + *pamret, pam_strerror(pamh, *pamret)); + return pamh; + } +#endif + + /* Open session */ + *pamret = pam_open_session(pamh, PAM_SILENT); + if (*pamret != PAM_SUCCESS) { + syslog(LOG_CRIT, "pam_open_session failed: %d (%s)", + *pamret, pam_strerror(pamh, *pamret)); + return pamh; + } + + return pamh; +} + +static int +stop_pam(pam_handle_t * pamh, int pamret) +{ + /* Close session */ + if (pamret == PAM_SUCCESS) { + pamret = pam_close_session(pamh, PAM_SILENT); + if (pamret != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_close_session failed: %d (%s)", + pamret, pam_strerror(pamh, pamret)); + } + } + + /* If PAM was OK and we are running on a SELinux system, new + processes images will be executed in the root context. */ + + /* Say goodbye */ + pamret = pam_end(pamh, pamret); + if (pamret != PAM_SUCCESS) { + /* avoid pam_strerror - we have no pamh. */ + syslog(LOG_ERR, "pam_end failed: %d", pamret); + return EX_OSERR; + } + return pamret; +} + +static char ** +prepare_environ(pam_handle_t * pamh) +{ + char **pam_env, **child_env, **entry; + int orig_count, pam_count; + + /* This function merges the normal environment with PAM's changes */ + + pam_env = pam_getenvlist(pamh); + if (pam_env == NULL) + return NULL; + + /* + * Worst case scenario is that PAM only adds variables, so allocate + * based on that assumption. + */ + orig_count = 0; + for (entry = environ; *entry != NULL; entry++) + orig_count++; + pam_count = 0; + for (entry = pam_env; *entry != NULL; entry++) + pam_count++; + + child_env = calloc(orig_count + pam_count + 1, sizeof(char *)); + if (child_env == NULL) + return NULL; + + memcpy(child_env, environ, sizeof(char *) * orig_count); + for (entry = child_env; *entry != NULL; entry++) { + *entry = strdup(*entry); + if (*entry == NULL) // FIXME: cleanup + return NULL; + } + + for (entry = pam_env; *entry != NULL; entry++) { + size_t varlen; + char **orig_entry; + + varlen = strcspn(*entry, "=") + 1; + + /* Check for overwrite */ + for (orig_entry = child_env; *orig_entry != NULL; orig_entry++) { + if (strncmp(*entry, *orig_entry, varlen) != 0) + continue; + + free(*orig_entry); + *orig_entry = *entry; + break; + } + + /* New variable? */ + if (*orig_entry == NULL) { + /* + * orig_entry will be pointing at the terminating entry, + * so we can just tack it on here. The new NULL was already + * prepared by calloc(). + */ + *orig_entry = *entry; + } + } + + return child_env; +} + +static void +switch_user(const char *username, uid_t uid, gid_t gid) +{ + // We must change group stuff first, because only root can do that. + if (setgid(gid) < 0) { + perror(": setgid"); + _exit(EX_OSERR); + } + + // Supplementary groups. + if (initgroups(username, gid) < 0) { + perror("initgroups"); + _exit(EX_OSERR); + } + + // Set euid, ruid and suid + if (setuid(uid) < 0) { + perror("setuid"); + _exit(EX_OSERR); + } +} + +static void +redir_stdio(const char *homedir, const char *display) +{ + int fd; + char hostname[HOST_NAME_MAX+1]; + char logfile[PATH_MAX]; + + fd = open("/dev/null", O_RDONLY); + if (fd == -1) { + perror("open"); + _exit(EX_OSERR); + } + if (dup2(fd, 0) == -1) { + perror("dup2"); + _exit(EX_OSERR); + } + close(fd); + + snprintf(logfile, sizeof(logfile), "%s/.vnc", homedir); + if (mkdir(logfile, 0755) == -1) { + if (errno != EEXIST) { + perror("mkdir"); + _exit(EX_OSERR); + } + } + + if (gethostname(hostname, sizeof(hostname)) == -1) { + perror("gethostname"); + _exit(EX_OSERR); + } + + snprintf(logfile, sizeof(logfile), "%s/.vnc/%s%s.log", + homedir, hostname, display); + fd = open(logfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd == -1) { + perror("open"); + _exit(EX_OSERR); + } + if ((dup2(fd, 1) == -1) || (dup2(fd, 2) == -1)) { + perror("dup2"); + _exit(EX_OSERR); + } + close(fd); +} + +static void +close_fds(void) +{ + DIR *dir; + struct dirent *entry; + + dir = opendir("/proc/self/fd"); + if (dir == NULL) { + perror("opendir"); + _exit(EX_OSERR); + } + + while ((entry = readdir(dir)) != NULL) { + int fd; + fd = atoi(entry->d_name); + if (fd < 3) + continue; + close(fd); + } + + closedir(dir); +} + +static pid_t +run_script(const char *username, const char *display, char **envp) +{ + struct passwd *pwent; + pid_t pid; + const char *child_argv[3]; + + pwent = getpwnam(username); + if (pwent == NULL) { + syslog(LOG_CRIT, "getpwnam: %s", strerror(errno)); + return -1; + } + + pid = fork(); + if (pid < 0) { + syslog(LOG_CRIT, "fork: %s", strerror(errno)); + return pid; + } + + /* two processes now */ + + if (pid > 0) + return pid; + + /* child */ + + switch_user(pwent->pw_name, pwent->pw_uid, pwent->pw_gid); + + close_fds(); + + redir_stdio(pwent->pw_dir, display); + + // execvpe() is not POSIX and is missing from older glibc + // First clear out everything + while ((environ != NULL) && (*environ != NULL)) { + char *var, *eq; + var = strdup(*environ); + eq = strchr(var, '='); + if (eq) + *eq = '\0'; + unsetenv(var); + free(var); + } + + // Then copy over the desired environment + for (; *envp != NULL; envp++) + putenv(*envp); + + // Set up some basic environment for the script + setenv("HOME", pwent->pw_dir, 1); + setenv("SHELL", pwent->pw_shell, 1); + setenv("LOGNAME", pwent->pw_name, 1); + setenv("USER", pwent->pw_name, 1); + setenv("USERNAME", pwent->pw_name, 1); + + child_argv[0] = LIBEXEC_DIR "/vncserver"; + child_argv[1] = display; + child_argv[2] = NULL; + + execvp(child_argv[0], (char*const*)child_argv); + + // execvp failed + perror("execvp"); + + _exit(EX_OSERR); +} + +int +main(int argc, char **argv) +{ + char pid_file[PATH_MAX]; + FILE *f; + + const char *username, *display; + + if ((argc != 3) || (argv[2][0] != ':')) { + fprintf(stderr, "Syntax:\n"); + fprintf(stderr, " %s <username> <display>\n", argv[0]); + return EX_USAGE; + } + + username = argv[1]; + display = argv[2]; + + if (geteuid() != 0) { + fprintf(stderr, "This program needs to be run as root!\n"); + return EX_USAGE; + } + + if (getpwnam(username) == NULL) { + if (errno == 0) + fprintf(stderr, "User \"%s\" does not exist\n", username); + else + fprintf(stderr, "Cannot look up user \"%s\": %s\n", + username, strerror(errno)); + return EX_OSERR; + } + + if (begin_daemon() == -1) + return EX_OSERR; + + openlog("vncsession", LOG_PID, LOG_AUTH); + + /* Indicate that this is a graphical user session. We need to do + this here before PAM as pam_systemd.so looks at these. */ + if ((putenv("XDG_SESSION_CLASS=user") < 0) || + (putenv("XDG_SESSION_TYPE=x11") < 0)) { + syslog(LOG_CRIT, "putenv: %s", strerror(errno)); + return EX_OSERR; + } + + /* Init PAM */ + int pamret; + pam_handle_t *pamh = run_pam(&pamret, username, display); + if (!pamh) { + return EX_OSERR; + } + if (pamret != PAM_SUCCESS) { + stop_pam(pamh, pamret); + return EX_OSERR; + } + + char **child_env; + child_env = prepare_environ(pamh); + if (child_env == NULL) { + syslog(LOG_CRIT, "Failure creating child process environment"); + stop_pam(pamh, pamret); + return EX_OSERR; + } + + setup_signals(); + + script = run_script(username, display, child_env); + if (script == -1) { + syslog(LOG_CRIT, "Failure starting vncserver script"); + stop_pam(pamh, pamret); + return EX_OSERR; + } + + snprintf(pid_file, sizeof(pid_file), + "/var/run/vncsession-%s.pid", display); + f = fopen(pid_file, "w"); + if (f == NULL) { + syslog(LOG_ERR, "Failure creating pid file \"%s\": %s", + pid_file, strerror(errno)); + } else { + fprintf(f, "%ld\n", (long)getpid()); + fclose(f); + } + + finish_daemon(); + + while (1) { + int status; + pid_t gotpid = waitpid(script, &status, 0); + if (gotpid < 0) { + if (errno != EINTR) { + syslog(LOG_CRIT, "waitpid: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + continue; + } + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + syslog(LOG_WARNING, + "vncsession: vncserver exited with status=%d", + WEXITSTATUS(status)); + } + break; + } + else if (WIFSIGNALED(status)) { + syslog(LOG_WARNING, + "vncsession: vncserver was terminated by signal %d", + WTERMSIG(status)); + break; + } + } + + unlink(pid_file); + + stop_pam(pamh, pamret); + + return 0; +} diff --git a/unix/vncserver/vncsession.man b/unix/vncserver/vncsession.man new file mode 100644 index 00000000..21382093 --- /dev/null +++ b/unix/vncserver/vncsession.man @@ -0,0 +1,75 @@ +.TH vncsession 8 "" "TigerVNC" "Virtual Network Computing" +.SH NAME +vncsession \- start a VNC server +.SH SYNOPSIS +.B vncsession +.RI < username > +.RI <: display# > +.SH DESCRIPTION +.B vncsession +is used to start a VNC (Virtual Network Computing) desktop. +.B vncsession +performs all the necessary steps to create a new user session, run Xvnc with +appropriate options and starts a window manager on the VNC desktop. + +.B vncsession +is rarely called directly and is normally started by the system service +manager. + +.SH FILES +Several VNC-related files are found in the directory $HOME/.vnc: +.TP +/etc/tigervnc/vncserver-config-defaults +The optional system-wide equivalent of $HOME/.vnc/config. If this file exists +and defines options to be passed to Xvnc, they will be used as defaults for +users. The user's $HOME/.vnc/config overrides settings configured in this file. +The overall configuration file load order is: this file, $HOME/.vnc/config, +and then /etc/tigervnc/vncserver-config-mandatory. None are required to exist. +.TP +/etc/tigervnc/vncserver-config-mandatory +The optional system-wide equivalent of $HOME/.vnc/config. If this file exists +and defines options to be passed to Xvnc, they will override any of the same +options defined in a user's $HOME/.vnc/config. This file offers a mechanism +to establish some basic form of system-wide policy. WARNING! There is +nothing stopping users from constructing their own vncsession-like script +that calls Xvnc directly to bypass any options defined in +/etc/tigervnc/vncserver-config-mandatory. The overall configuration file load +order is: /etc/tigervnc/vncserver-config-defaults, $HOME/.vnc/config, and then +this file. None are required to exist. +.TP +$HOME/.vnc/config +An optional server config file wherein options to be passed to Xvnc are listed +to avoid hard-coding them to the physical invocation. List options in this file +one per line. For those requiring an argument, simply separate the option from +the argument with an equal sign, for example: "geometry=2000x1200" or +"securitytypes=vncauth,tlsvnc". Options without an argument are simply listed +as a single word, for example: "localhost" or "alwaysshared". + +The special option +.B session +can be used to control which session type will be started. This should match +one of the files in \fI/usr/share/xsessions\fP. E.g. if there is a file called +"gnome.desktop", then "session=gnome" would be set to use that session type. +.TP +$HOME/.vnc/passwd +The VNC password file. +.TP +$HOME/.vnc/\fIhost\fP:\fIdisplay#\fP.log +The log file for Xvnc and the session. + +.SH SEE ALSO +.BR vncviewer (1), +.BR vncpasswd (1), +.BR vncconfig (1), +.BR Xvnc (1) +.br +https://www.tigervnc.org + +.SH AUTHOR +Tristan Richardson, RealVNC Ltd., D. R. Commander and others. + +VNC was originally developed by the RealVNC team while at Olivetti +Research Ltd / AT&T Laboratories Cambridge. TightVNC additions were +implemented by Constantin Kaplinsky. Many other people have since +participated in development, testing and support. This manual is part +of the TigerVNC software suite. |