1122 lines
37 KiB
C
1122 lines
37 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <locale.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <glib/gstdio.h>
|
|
#include <gio/gio.h>
|
|
|
|
#include "gdm-common.h"
|
|
|
|
#include <systemd/sd-login.h>
|
|
|
|
#define GDM_DBUS_NAME "org.gnome.DisplayManager"
|
|
#define GDM_DBUS_LOCAL_DISPLAY_FACTORY_PATH "/org/gnome/DisplayManager/LocalDisplayFactory"
|
|
#define GDM_DBUS_LOCAL_DISPLAY_FACTORY_INTERFACE "org.gnome.DisplayManager.LocalDisplayFactory"
|
|
|
|
G_DEFINE_QUARK (gdm-common-error, gdm_common_error);
|
|
|
|
gboolean
|
|
gdm_clear_close_on_exec_flag (int fd)
|
|
{
|
|
int flags;
|
|
|
|
if (fd < 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
flags = fcntl (fd, F_GETFD, 0);
|
|
|
|
if (flags < 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ((flags & FD_CLOEXEC) != 0) {
|
|
int status;
|
|
|
|
status = fcntl (fd, F_SETFD, flags & ~FD_CLOEXEC);
|
|
|
|
return status != -1;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gdm_get_pwent_for_name (const char *name,
|
|
struct passwd **pwentp)
|
|
{
|
|
struct passwd *pwent;
|
|
|
|
do {
|
|
errno = 0;
|
|
pwent = getpwnam (name);
|
|
} while (pwent == NULL && errno == EINTR);
|
|
|
|
if (pwentp != NULL) {
|
|
*pwentp = pwent;
|
|
}
|
|
|
|
return (pwent != NULL);
|
|
}
|
|
|
|
static gboolean
|
|
gdm_get_grent_for_gid (gint gid,
|
|
struct group **grentp)
|
|
{
|
|
struct group *grent;
|
|
|
|
do {
|
|
errno = 0;
|
|
grent = getgrgid (gid);
|
|
} while (grent == NULL && errno == EINTR);
|
|
|
|
if (grentp != NULL) {
|
|
*grentp = grent;
|
|
}
|
|
|
|
return (grent != NULL);
|
|
}
|
|
|
|
int
|
|
gdm_wait_on_and_disown_pid (int pid,
|
|
int timeout)
|
|
{
|
|
int status;
|
|
int ret;
|
|
int num_tries;
|
|
int flags;
|
|
gboolean already_reaped;
|
|
|
|
if (timeout > 0) {
|
|
flags = WNOHANG;
|
|
num_tries = 10 * timeout;
|
|
} else {
|
|
flags = 0;
|
|
num_tries = 0;
|
|
}
|
|
wait_again:
|
|
errno = 0;
|
|
already_reaped = FALSE;
|
|
ret = waitpid (pid, &status, flags);
|
|
if (ret < 0) {
|
|
if (errno == EINTR) {
|
|
goto wait_again;
|
|
} else if (errno == ECHILD) {
|
|
already_reaped = TRUE;
|
|
} else {
|
|
g_debug ("GdmCommon: waitpid () should not fail");
|
|
}
|
|
} else if (ret == 0) {
|
|
num_tries--;
|
|
|
|
if (num_tries > 0) {
|
|
g_usleep (G_USEC_PER_SEC / 10);
|
|
} else {
|
|
char *path;
|
|
char *command;
|
|
|
|
path = g_strdup_printf ("/proc/%ld/cmdline", (long) pid);
|
|
if (g_file_get_contents (path, &command, NULL, NULL)) {;
|
|
g_warning ("GdmCommon: process (pid:%d, command '%s') isn't dying after %d seconds, now ignoring it.",
|
|
(int) pid, command, timeout);
|
|
g_free (command);
|
|
} else {
|
|
g_warning ("GdmCommon: process (pid:%d) isn't dying after %d seconds, now ignoring it.",
|
|
(int) pid, timeout);
|
|
}
|
|
g_free (path);
|
|
|
|
return 0;
|
|
}
|
|
goto wait_again;
|
|
}
|
|
|
|
g_debug ("GdmCommon: process (pid:%d) done (%s:%d)",
|
|
(int) pid,
|
|
already_reaped? "reaped earlier" :
|
|
WIFEXITED (status) ? "status"
|
|
: WIFSIGNALED (status) ? "signal"
|
|
: "unknown",
|
|
already_reaped? 1 :
|
|
WIFEXITED (status) ? WEXITSTATUS (status)
|
|
: WIFSIGNALED (status) ? WTERMSIG (status)
|
|
: -1);
|
|
|
|
return status;
|
|
}
|
|
|
|
int
|
|
gdm_wait_on_pid (int pid)
|
|
{
|
|
return gdm_wait_on_and_disown_pid (pid, 0);
|
|
}
|
|
|
|
int
|
|
gdm_signal_pid (int pid,
|
|
int signal)
|
|
{
|
|
int status = -1;
|
|
|
|
/* perhaps block sigchld */
|
|
g_debug ("GdmCommon: sending signal %d to process %d", signal, pid);
|
|
errno = 0;
|
|
status = kill (pid, signal);
|
|
|
|
if (status < 0) {
|
|
if (errno == ESRCH) {
|
|
g_warning ("Child process %d was already dead.",
|
|
(int)pid);
|
|
} else {
|
|
g_warning ("Couldn't kill child process %d: %s",
|
|
pid,
|
|
g_strerror (errno));
|
|
}
|
|
}
|
|
|
|
/* perhaps unblock sigchld */
|
|
|
|
return status;
|
|
}
|
|
|
|
static gboolean
|
|
_fd_is_character_device (int fd)
|
|
{
|
|
struct stat file_info;
|
|
|
|
if (fstat (fd, &file_info) < 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
return S_ISCHR (file_info.st_mode);
|
|
}
|
|
|
|
static gboolean
|
|
_read_bytes (int fd,
|
|
char *bytes,
|
|
gsize number_of_bytes,
|
|
GError **error)
|
|
{
|
|
size_t bytes_left_to_read;
|
|
size_t total_bytes_read = 0;
|
|
gboolean premature_eof;
|
|
|
|
bytes_left_to_read = number_of_bytes;
|
|
premature_eof = FALSE;
|
|
do {
|
|
size_t bytes_read = 0;
|
|
|
|
errno = 0;
|
|
bytes_read = read (fd, ((guchar *) bytes) + total_bytes_read,
|
|
bytes_left_to_read);
|
|
|
|
if (bytes_read > 0) {
|
|
total_bytes_read += bytes_read;
|
|
bytes_left_to_read -= bytes_read;
|
|
} else if (bytes_read == 0) {
|
|
premature_eof = TRUE;
|
|
break;
|
|
} else if ((errno != EINTR)) {
|
|
break;
|
|
}
|
|
} while (bytes_left_to_read > 0);
|
|
|
|
if (premature_eof) {
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
G_FILE_ERROR_FAILED,
|
|
"No data available");
|
|
|
|
return FALSE;
|
|
} else if (bytes_left_to_read > 0) {
|
|
g_set_error_literal (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Pulls a requested number of bytes from /dev/urandom
|
|
*
|
|
* @param size number of bytes to pull
|
|
* @param error error if read fails
|
|
* @returns The requested number of random bytes or #NULL if fail
|
|
*/
|
|
|
|
char *
|
|
gdm_generate_random_bytes (gsize size,
|
|
GError **error)
|
|
{
|
|
int fd;
|
|
char *bytes;
|
|
GError *read_error;
|
|
|
|
/* We don't use the g_rand_* glib apis because they don't document
|
|
* how much entropy they are seeded with, and it might be less
|
|
* than the passed in size.
|
|
*/
|
|
|
|
errno = 0;
|
|
fd = open ("/dev/urandom", O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
g_set_error_literal (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
if (!_fd_is_character_device (fd)) {
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (ENODEV),
|
|
_("/dev/urandom is not a character device"));
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
bytes = g_malloc (size);
|
|
read_error = NULL;
|
|
if (!_read_bytes (fd, bytes, size, &read_error)) {
|
|
g_propagate_error (error, read_error);
|
|
g_free (bytes);
|
|
close (fd);
|
|
return NULL;
|
|
}
|
|
|
|
close (fd);
|
|
return bytes;
|
|
}
|
|
|
|
static gboolean
|
|
create_transient_display (GDBusConnection *connection,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GError *local_error = NULL;
|
|
GVariant *reply;
|
|
const char *value;
|
|
|
|
reply = g_dbus_connection_call_sync (connection,
|
|
GDM_DBUS_NAME,
|
|
GDM_DBUS_LOCAL_DISPLAY_FACTORY_PATH,
|
|
GDM_DBUS_LOCAL_DISPLAY_FACTORY_INTERFACE,
|
|
"CreateTransientDisplay",
|
|
NULL, /* parameters */
|
|
G_VARIANT_TYPE ("(o)"),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
cancellable, &local_error);
|
|
if (reply == NULL) {
|
|
g_warning ("Unable to create transient display: %s", local_error->message);
|
|
g_propagate_prefixed_error (error, local_error, _("Unable to create transient display: "));
|
|
return FALSE;
|
|
}
|
|
|
|
g_variant_get (reply, "(&o)", &value);
|
|
g_debug ("Started %s", value);
|
|
|
|
g_variant_unref (reply);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gdm_activate_session_by_id (GDBusConnection *connection,
|
|
GCancellable *cancellable,
|
|
const char *seat_id,
|
|
const char *session_id)
|
|
{
|
|
GError *local_error = NULL;
|
|
GVariant *reply;
|
|
|
|
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
|
|
g_return_val_if_fail (seat_id != NULL, FALSE);
|
|
g_return_val_if_fail (session_id != NULL, FALSE);
|
|
|
|
reply = g_dbus_connection_call_sync (connection,
|
|
"org.freedesktop.login1",
|
|
"/org/freedesktop/login1",
|
|
"org.freedesktop.login1.Manager",
|
|
"ActivateSessionOnSeat",
|
|
g_variant_new ("(ss)", session_id, seat_id),
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
cancellable, &local_error);
|
|
if (reply == NULL) {
|
|
g_warning ("Unable to activate session: %s", local_error->message);
|
|
g_error_free (local_error);
|
|
return FALSE;
|
|
}
|
|
|
|
g_variant_unref (reply);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gdm_terminate_session_by_id (GDBusConnection *connection,
|
|
GCancellable *cancellable,
|
|
const char *session_id)
|
|
{
|
|
GError *local_error = NULL;
|
|
GVariant *reply;
|
|
|
|
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
|
|
g_return_val_if_fail (session_id != NULL, FALSE);
|
|
|
|
reply = g_dbus_connection_call_sync (connection,
|
|
"org.freedesktop.login1",
|
|
"/org/freedesktop/login1",
|
|
"org.freedesktop.login1.Manager",
|
|
"TerminateSession",
|
|
g_variant_new ("(s)", session_id),
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
cancellable, &local_error);
|
|
if (reply == NULL) {
|
|
g_warning ("Unable to terminate session: %s", local_error->message);
|
|
g_error_free (local_error);
|
|
return FALSE;
|
|
}
|
|
|
|
g_variant_unref (reply);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gdm_get_login_window_session_id (const char *seat_id,
|
|
char **session_id)
|
|
{
|
|
gboolean ret;
|
|
int res, i;
|
|
char **sessions;
|
|
|
|
g_return_val_if_fail (session_id != NULL, FALSE);
|
|
|
|
res = sd_seat_get_sessions (seat_id, &sessions, NULL, NULL);
|
|
if (res < 0) {
|
|
g_debug ("Failed to determine sessions: %s", strerror (-res));
|
|
return FALSE;
|
|
}
|
|
|
|
if (sessions == NULL || sessions[0] == NULL) {
|
|
*session_id = NULL;
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; sessions[i]; i ++) {
|
|
char *service_id = NULL;
|
|
char *service_class = NULL;
|
|
char *state = NULL;
|
|
|
|
res = sd_session_get_class (sessions[i], &service_class);
|
|
if (res < 0) {
|
|
if (res == -ENXIO)
|
|
continue;
|
|
|
|
g_debug ("failed to determine class of session %s: %s", sessions[i], strerror (-res));
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp (service_class, "greeter") != 0) {
|
|
free (service_class);
|
|
continue;
|
|
}
|
|
|
|
free (service_class);
|
|
|
|
res = sd_session_get_state (sessions[i], &state);
|
|
if (res < 0) {
|
|
if (res == -ENXIO)
|
|
continue;
|
|
|
|
g_debug ("failed to determine state of session %s: %s", sessions[i], strerror (-res));
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (g_strcmp0 (state, "closing") == 0) {
|
|
free (state);
|
|
continue;
|
|
}
|
|
free (state);
|
|
|
|
res = sd_session_get_service (sessions[i], &service_id);
|
|
if (res < 0) {
|
|
if (res == -ENXIO)
|
|
continue;
|
|
|
|
g_debug ("failed to determine service of session %s: %s", sessions[i], strerror (-res));
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (strcmp (service_id, "gdm-launch-environment") == 0) {
|
|
*session_id = g_strdup (sessions[i]);
|
|
ret = TRUE;
|
|
|
|
free (service_id);
|
|
goto out;
|
|
}
|
|
|
|
free (service_id);
|
|
}
|
|
|
|
*session_id = NULL;
|
|
ret = FALSE;
|
|
|
|
out:
|
|
if (sessions) {
|
|
for (i = 0; sessions[i]; i ++) {
|
|
free (sessions[i]);
|
|
}
|
|
|
|
free (sessions);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
goto_login_session (GDBusConnection *connection,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
gboolean ret;
|
|
int res;
|
|
char *our_session;
|
|
char *session_id;
|
|
char *seat_id;
|
|
GError *local_error = NULL;
|
|
|
|
ret = FALSE;
|
|
session_id = NULL;
|
|
seat_id = NULL;
|
|
|
|
/* First look for any existing LoginWindow sessions on the seat.
|
|
If none are found, create a new one. */
|
|
|
|
/* Note that we mostly use free () here, instead of g_free ()
|
|
* since the data allocated is from libsystemd-logind, which
|
|
* does not use GLib's g_malloc (). */
|
|
|
|
if (!gdm_find_display_session (0, getuid (), &our_session, &local_error)) {
|
|
g_propagate_prefixed_error (error, local_error, _("Could not identify the current session: "));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
res = sd_session_get_seat (our_session, &seat_id);
|
|
free (our_session);
|
|
if (res < 0) {
|
|
g_debug ("failed to determine own seat: %s", strerror (-res));
|
|
g_set_error (error, GDM_COMMON_ERROR, 0, _("Could not identify the current seat."));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
res = gdm_get_login_window_session_id (seat_id, &session_id);
|
|
if (res && session_id != NULL) {
|
|
res = gdm_activate_session_by_id (connection, cancellable, seat_id, session_id);
|
|
|
|
if (res) {
|
|
ret = TRUE;
|
|
}
|
|
}
|
|
|
|
if (! ret && g_strcmp0 (seat_id, "seat0") == 0) {
|
|
res = create_transient_display (connection, cancellable, error);
|
|
if (res) {
|
|
ret = TRUE;
|
|
}
|
|
}
|
|
|
|
free (seat_id);
|
|
g_free (session_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gdm_goto_login_session (GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GDBusConnection) connection = NULL;
|
|
g_autoptr(GError) local_error = NULL;
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, &local_error);
|
|
if (connection == NULL) {
|
|
g_debug ("Failed to connect to the D-Bus daemon: %s", local_error->message);
|
|
g_propagate_error (error, g_steal_pointer (&local_error));
|
|
return FALSE;
|
|
}
|
|
|
|
return goto_login_session (connection, cancellable, error);
|
|
}
|
|
|
|
static void
|
|
listify_hash (const char *key,
|
|
const char *value,
|
|
GPtrArray *env)
|
|
{
|
|
char *str;
|
|
str = g_strdup_printf ("%s=%s", key, value);
|
|
g_debug ("Gdm: script environment: %s", str);
|
|
g_ptr_array_add (env, str);
|
|
}
|
|
|
|
GPtrArray *
|
|
gdm_get_script_environment (const char *username,
|
|
const char *display_name,
|
|
const char *display_hostname,
|
|
const char *display_x11_authority_file)
|
|
{
|
|
GPtrArray *env;
|
|
GHashTable *hash;
|
|
struct passwd *pwent;
|
|
|
|
env = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
/* create a hash table of current environment, then update keys has necessary */
|
|
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
/* modify environment here */
|
|
g_hash_table_insert (hash, g_strdup ("HOME"), g_strdup ("/"));
|
|
g_hash_table_insert (hash, g_strdup ("PWD"), g_strdup ("/"));
|
|
g_hash_table_insert (hash, g_strdup ("SHELL"), g_strdup ("/bin/sh"));
|
|
|
|
if (username != NULL) {
|
|
g_hash_table_insert (hash, g_strdup ("LOGNAME"),
|
|
g_strdup (username));
|
|
g_hash_table_insert (hash, g_strdup ("USER"),
|
|
g_strdup (username));
|
|
g_hash_table_insert (hash, g_strdup ("USERNAME"),
|
|
g_strdup (username));
|
|
|
|
gdm_get_pwent_for_name (username, &pwent);
|
|
if (pwent != NULL) {
|
|
if (pwent->pw_dir != NULL && pwent->pw_dir[0] != '\0') {
|
|
g_hash_table_insert (hash, g_strdup ("HOME"),
|
|
g_strdup (pwent->pw_dir));
|
|
g_hash_table_insert (hash, g_strdup ("PWD"),
|
|
g_strdup (pwent->pw_dir));
|
|
}
|
|
|
|
g_hash_table_insert (hash, g_strdup ("SHELL"),
|
|
g_strdup (pwent->pw_shell));
|
|
|
|
/* Also get group name and propagate down */
|
|
struct group *grent;
|
|
|
|
if (gdm_get_grent_for_gid (pwent->pw_gid, &grent)) {
|
|
g_hash_table_insert (hash, g_strdup ("GROUP"), g_strdup (grent->gr_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (display_hostname) {
|
|
g_hash_table_insert (hash, g_strdup ("REMOTE_HOST"), g_strdup (display_hostname));
|
|
}
|
|
|
|
/* Runs as root */
|
|
if (display_x11_authority_file) {
|
|
g_hash_table_insert (hash, g_strdup ("XAUTHORITY"), g_strdup (display_x11_authority_file));
|
|
}
|
|
|
|
if (display_name) {
|
|
g_hash_table_insert (hash, g_strdup ("DISPLAY"), g_strdup (display_name));
|
|
}
|
|
g_hash_table_insert (hash, g_strdup ("PATH"), g_strdup (GDM_SESSION_DEFAULT_PATH));
|
|
g_hash_table_insert (hash, g_strdup ("RUNNING_UNDER_GDM"), g_strdup ("true"));
|
|
|
|
g_hash_table_remove (hash, "MAIL");
|
|
|
|
g_hash_table_foreach (hash, (GHFunc)listify_hash, env);
|
|
g_hash_table_destroy (hash);
|
|
|
|
g_ptr_array_add (env, NULL);
|
|
|
|
return env;
|
|
}
|
|
|
|
gboolean
|
|
gdm_run_script (const char *dir,
|
|
const char *username,
|
|
const char *display_name,
|
|
const char *display_hostname,
|
|
const char *display_x11_authority_file)
|
|
{
|
|
char *script;
|
|
char **argv;
|
|
gint status;
|
|
GError *error;
|
|
GPtrArray *env;
|
|
gboolean res;
|
|
gboolean ret;
|
|
|
|
ret = FALSE;
|
|
|
|
g_assert (dir != NULL);
|
|
g_assert (username != NULL);
|
|
|
|
script = g_build_filename (dir, display_name, NULL);
|
|
g_debug ("Trying script %s", script);
|
|
if (! (g_file_test (script, G_FILE_TEST_IS_REGULAR)
|
|
&& g_file_test (script, G_FILE_TEST_IS_EXECUTABLE))) {
|
|
g_debug ("script %s not found; skipping", script);
|
|
g_free (script);
|
|
script = NULL;
|
|
}
|
|
|
|
if (script == NULL
|
|
&& display_hostname != NULL
|
|
&& display_hostname[0] != '\0') {
|
|
script = g_build_filename (dir, display_hostname, NULL);
|
|
g_debug ("Trying script %s", script);
|
|
if (! (g_file_test (script, G_FILE_TEST_IS_REGULAR)
|
|
&& g_file_test (script, G_FILE_TEST_IS_EXECUTABLE))) {
|
|
g_debug ("script %s not found; skipping", script);
|
|
g_free (script);
|
|
script = NULL;
|
|
}
|
|
}
|
|
|
|
if (script == NULL) {
|
|
script = g_build_filename (dir, "Default", NULL);
|
|
g_debug ("Trying script %s", script);
|
|
if (! (g_file_test (script, G_FILE_TEST_IS_REGULAR)
|
|
&& g_file_test (script, G_FILE_TEST_IS_EXECUTABLE))) {
|
|
g_debug ("script %s not found; skipping", script);
|
|
g_free (script);
|
|
script = NULL;
|
|
}
|
|
}
|
|
|
|
if (script == NULL) {
|
|
g_debug ("no script found");
|
|
return TRUE;
|
|
}
|
|
|
|
g_debug ("Running process: %s", script);
|
|
error = NULL;
|
|
if (! g_shell_parse_argv (script, NULL, &argv, &error)) {
|
|
g_warning ("Could not parse command: %s", error->message);
|
|
g_error_free (error);
|
|
goto out;
|
|
}
|
|
|
|
env = gdm_get_script_environment (username,
|
|
display_name,
|
|
display_hostname,
|
|
display_x11_authority_file);
|
|
|
|
res = g_spawn_sync (NULL,
|
|
argv,
|
|
(char **)env->pdata,
|
|
G_SPAWN_SEARCH_PATH,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&status,
|
|
&error);
|
|
|
|
g_ptr_array_free (env, TRUE);
|
|
g_strfreev (argv);
|
|
|
|
if (! res) {
|
|
g_warning ("Unable to run script: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
if (WIFEXITED (status)) {
|
|
g_debug ("Process exit status: %d", WEXITSTATUS (status));
|
|
ret = WEXITSTATUS (status) == 0;
|
|
}
|
|
|
|
out:
|
|
g_free (script);
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gdm_shell_var_is_valid_char (gchar c, gboolean first)
|
|
{
|
|
return (!first && g_ascii_isdigit (c)) ||
|
|
c == '_' ||
|
|
g_ascii_isalpha (c);
|
|
}
|
|
|
|
/* This expands a string somewhat similar to how a shell would do it
|
|
if it was enclosed inside double quotes. It handles variable
|
|
expansion like $FOO and ${FOO}, single-char escapes using \, and
|
|
non-escaped # at the begining of a word is taken as a comment and ignored */
|
|
char *
|
|
gdm_shell_expand (const char *str,
|
|
GdmExpandVarFunc expand_var_func,
|
|
gpointer user_data)
|
|
{
|
|
GString *s = g_string_new("");
|
|
const gchar *p, *start;
|
|
gchar c;
|
|
gboolean at_new_word;
|
|
|
|
p = str;
|
|
at_new_word = TRUE;
|
|
while (*p) {
|
|
c = *p;
|
|
if (c == '\\') {
|
|
p++;
|
|
c = *p;
|
|
if (c != '\0') {
|
|
p++;
|
|
switch (c) {
|
|
case '\\':
|
|
g_string_append_c (s, '\\');
|
|
break;
|
|
case '$':
|
|
g_string_append_c (s, '$');
|
|
break;
|
|
case '#':
|
|
g_string_append_c (s, '#');
|
|
break;
|
|
default:
|
|
g_string_append_c (s, '\\');
|
|
g_string_append_c (s, c);
|
|
break;
|
|
}
|
|
}
|
|
} else if (c == '#' && at_new_word) {
|
|
break;
|
|
} else if (c == '$') {
|
|
gboolean brackets = FALSE;
|
|
p++;
|
|
if (*p == '{') {
|
|
brackets = TRUE;
|
|
p++;
|
|
}
|
|
start = p;
|
|
while (*p != '\0' &&
|
|
gdm_shell_var_is_valid_char (*p, p == start))
|
|
p++;
|
|
if (p == start || (brackets && *p != '}')) {
|
|
/* Invalid variable, use as-is */
|
|
g_string_append_c (s, '$');
|
|
if (brackets)
|
|
g_string_append_c (s, '{');
|
|
g_string_append_len (s, start, p - start);
|
|
} else {
|
|
gchar *expanded;
|
|
gchar *var = g_strndup (start, p - start);
|
|
if (brackets && *p == '}')
|
|
p++;
|
|
|
|
expanded = expand_var_func (var, user_data);
|
|
if (expanded)
|
|
g_string_append (s, expanded);
|
|
g_free (var);
|
|
g_free (expanded);
|
|
}
|
|
} else {
|
|
p++;
|
|
g_string_append_c (s, c);
|
|
at_new_word = g_ascii_isspace (c);
|
|
}
|
|
}
|
|
return g_string_free (s, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
_systemd_session_is_graphical (const char *session_id)
|
|
{
|
|
const gchar * const graphical_session_types[] = { "wayland", "x11", "mir", NULL };
|
|
int saved_errno;
|
|
g_autofree gchar *type = NULL;
|
|
|
|
saved_errno = sd_session_get_type (session_id, &type);
|
|
if (saved_errno < 0) {
|
|
g_warning ("Couldn't get type for session '%s': %s",
|
|
session_id,
|
|
g_strerror (-saved_errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_strv_contains (graphical_session_types, type)) {
|
|
g_debug ("Session '%s' is not a graphical session (type: '%s')",
|
|
session_id,
|
|
type);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_systemd_session_is_active (const char *session_id)
|
|
{
|
|
const gchar * const active_states[] = { "active", "online", NULL };
|
|
int saved_errno;
|
|
g_autofree gchar *state = NULL;
|
|
|
|
/*
|
|
* display sessions can be 'closing' if they are logged out but some
|
|
* processes are lingering; we shouldn't consider these (this is
|
|
* checking for a race condition since we specified
|
|
* GDM_SYSTEMD_SESSION_REQUIRE_ONLINE)
|
|
*/
|
|
saved_errno = sd_session_get_state (session_id, &state);
|
|
if (saved_errno < 0) {
|
|
g_warning ("Couldn't get state for session '%s': %s",
|
|
session_id,
|
|
g_strerror (-saved_errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_strv_contains (active_states, state)) {
|
|
g_debug ("Session '%s' is not active or online", session_id);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gdm_find_display_session (GPid pid,
|
|
const uid_t uid,
|
|
char **out_session_id,
|
|
GError **error)
|
|
{
|
|
char *local_session_id = NULL;
|
|
g_auto(GStrv) sessions = NULL;
|
|
int n_sessions;
|
|
int res;
|
|
|
|
g_return_val_if_fail (out_session_id != NULL, FALSE);
|
|
|
|
/* First try to look up the session using the pid. We need this
|
|
* at least for the greeter, because it currently runs multiple
|
|
* sessions under the same user.
|
|
* See also commit 2b52d8933c8ab38e7ee83318da2363d00d8c5581
|
|
* which added an explicit dbus-run-session for all but seat0.
|
|
*/
|
|
res = sd_pid_get_session (pid, &local_session_id);
|
|
if (res >= 0) {
|
|
g_debug ("GdmCommon: Found session %s for PID %d, using", local_session_id, pid);
|
|
|
|
*out_session_id = g_strdup (local_session_id);
|
|
free (local_session_id);
|
|
|
|
return TRUE;
|
|
} else {
|
|
if (res != -ENODATA)
|
|
g_warning ("GdmCommon: Failed to retrieve session information for pid %d: %s",
|
|
pid, strerror (-res));
|
|
}
|
|
|
|
g_debug ("Finding a graphical session for user %d", uid);
|
|
|
|
n_sessions = sd_uid_get_sessions (uid,
|
|
GDM_SYSTEMD_SESSION_REQUIRE_ONLINE,
|
|
&sessions);
|
|
|
|
if (n_sessions < 0) {
|
|
g_set_error (error,
|
|
GDM_COMMON_ERROR,
|
|
0,
|
|
"Failed to get sessions for user %d",
|
|
uid);
|
|
return FALSE;
|
|
}
|
|
|
|
for (int i = 0; i < n_sessions; ++i) {
|
|
g_debug ("Considering session '%s'", sessions[i]);
|
|
|
|
if (!_systemd_session_is_graphical (sessions[i]))
|
|
continue;
|
|
|
|
if (!_systemd_session_is_active (sessions[i]))
|
|
continue;
|
|
|
|
/*
|
|
* We get the sessions from newest to oldest, so take the last
|
|
* one we find that's good
|
|
*/
|
|
local_session_id = sessions[i];
|
|
}
|
|
|
|
if (local_session_id == NULL) {
|
|
g_set_error (error,
|
|
GDM_COMMON_ERROR,
|
|
0,
|
|
"Could not find a graphical session for user %d",
|
|
uid);
|
|
return FALSE;
|
|
}
|
|
|
|
*out_session_id = g_strdup (local_session_id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
load_env_file (GFile *file,
|
|
GdmLoadEnvVarFunc load_env_func,
|
|
GdmExpandVarFunc expand_func,
|
|
gpointer user_data)
|
|
{
|
|
gchar *contents;
|
|
gchar **lines;
|
|
gchar *line, *p;
|
|
gchar *var, *var_end;
|
|
gchar *expanded;
|
|
char *filename;
|
|
int i;
|
|
|
|
filename = g_file_get_path (file);
|
|
g_debug ("Loading env vars from %s\n", filename);
|
|
g_free (filename);
|
|
|
|
if (g_file_load_contents (file, NULL, &contents, NULL, NULL, NULL)) {
|
|
lines = g_strsplit (contents, "\n", -1);
|
|
g_free (contents);
|
|
for (i = 0; lines[i] != NULL; i++) {
|
|
line = lines[i];
|
|
p = line;
|
|
while (g_ascii_isspace (*p))
|
|
p++;
|
|
if (*p == '#' || *p == '\0')
|
|
continue;
|
|
var = p;
|
|
while (gdm_shell_var_is_valid_char (*p, p == var))
|
|
p++;
|
|
var_end = p;
|
|
while (g_ascii_isspace (*p))
|
|
p++;
|
|
if (var == var_end || *p != '=') {
|
|
g_warning ("Invalid env.d line '%s'\n", line);
|
|
continue;
|
|
}
|
|
*var_end = 0;
|
|
p++; /* Skip = */
|
|
while (g_ascii_isspace (*p))
|
|
p++;
|
|
|
|
expanded = gdm_shell_expand (p, expand_func, user_data);
|
|
expanded = g_strchomp (expanded);
|
|
load_env_func (var, expanded, user_data);
|
|
g_free (expanded);
|
|
}
|
|
g_strfreev (lines);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
compare_str (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
return strcmp (*(const char **)a, *(const char **)b);
|
|
}
|
|
|
|
static void
|
|
gdm_load_env_dir (GFile *dir,
|
|
GdmLoadEnvVarFunc load_env_func,
|
|
GdmExpandVarFunc expand_func,
|
|
gpointer user_data)
|
|
{
|
|
GFileInfo *info = NULL;
|
|
GFileEnumerator *enumerator = NULL;
|
|
GPtrArray *names = NULL;
|
|
GFile *file;
|
|
const gchar *name;
|
|
int i;
|
|
|
|
enumerator = g_file_enumerate_children (dir,
|
|
G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
|
G_FILE_ATTRIBUTE_STANDARD_NAME","
|
|
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN","
|
|
G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP,
|
|
G_FILE_QUERY_INFO_NONE,
|
|
NULL, NULL);
|
|
if (!enumerator) {
|
|
goto out;
|
|
}
|
|
|
|
names = g_ptr_array_new_with_free_func (g_free);
|
|
while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
|
|
if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR &&
|
|
!g_file_info_get_is_hidden (info) &&
|
|
g_str_has_suffix (g_file_info_get_name (info), ".env"))
|
|
g_ptr_array_add (names, g_strdup (g_file_info_get_name (info)));
|
|
|
|
g_clear_object (&info);
|
|
}
|
|
|
|
g_ptr_array_sort (names, compare_str);
|
|
|
|
for (i = 0; i < names->len; i++) {
|
|
name = g_ptr_array_index (names, i);
|
|
file = g_file_get_child (dir, name);
|
|
load_env_file (file, load_env_func, expand_func, user_data);
|
|
g_object_unref (file);
|
|
}
|
|
|
|
out:
|
|
g_clear_pointer (&names, g_ptr_array_unref);
|
|
g_clear_object (&enumerator);
|
|
}
|
|
|
|
void
|
|
gdm_load_env_d (GdmLoadEnvVarFunc load_env_func,
|
|
GdmExpandVarFunc expand_func,
|
|
gpointer user_data)
|
|
{
|
|
GFile *dir;
|
|
|
|
dir = g_file_new_for_path (DATADIR "/gdm/env.d");
|
|
gdm_load_env_dir (dir, load_env_func, expand_func, user_data);
|
|
g_object_unref (dir);
|
|
|
|
dir = g_file_new_for_path (GDMCONFDIR "/env.d");
|
|
gdm_load_env_dir (dir, load_env_func, expand_func, user_data);
|
|
g_object_unref (dir);
|
|
}
|