/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * gsm-util.c * Copyright (C) 2008 Lucas Rocha. * * This program 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 program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "gsm-util.h" static gchar *_saved_session_dir = NULL; static gchar **child_environment; /* These are variables that will not be passed on to subprocesses * (either directly, via systemd or DBus). * Some of these are blacklisted as they might end up in the wrong session * (e.g. XDG_VTNR), others because they simply must never be passed on * (NOTIFY_SOCKET). */ static const char * const variable_blacklist[] = { "NOTIFY_SOCKET", "XDG_SEAT", "XDG_SESSION_ID", "XDG_VTNR", NULL }; /* The following is copied from GDMs spawn_session function. * * Environment variables listed here will be copied into the user's service * environments if they are set in gnome-session's environment. If they are * not set in gnome-session's environment, they will be removed from the * service environments. This is to protect against environment variables * leaking from previous sessions (e.g. when switching from classic to * default GNOME $GNOME_SHELL_SESSION_MODE will become unset). */ static const char * const variable_unsetlist[] = { "DISPLAY", "XAUTHORITY", "WAYLAND_DISPLAY", "WAYLAND_SOCKET", "GNOME_SHELL_SESSION_MODE", "GNOME_SETUP_DISPLAY", /* None of the LC_* variables should survive a logout/login */ "LC_CTYPE", "LC_NUMERIC", "LC_TIME", "LC_COLLATE", "LC_MONETARY", "LC_MESSAGES", "LC_PAPER", "LC_NAME", "LC_ADDRESS", "LC_TELEPHONE", "LC_MEASUREMENT", "LC_IDENTIFICATION", "LC_ALL", NULL }; char * gsm_util_find_desktop_file_for_app_name (const char *name, gboolean look_in_saved_session, gboolean autostart_first) { char *app_path; char **app_dirs; GKeyFile *key_file; char *desktop_file; int i; app_path = NULL; app_dirs = gsm_util_get_desktop_dirs (look_in_saved_session, autostart_first); key_file = g_key_file_new (); desktop_file = g_strdup_printf ("%s.desktop", name); g_debug ("GsmUtil: Looking for file '%s'", desktop_file); for (i = 0; app_dirs[i] != NULL; i++) { g_debug ("GsmUtil: Looking in '%s'", app_dirs[i]); } g_key_file_load_from_dirs (key_file, desktop_file, (const char **) app_dirs, &app_path, G_KEY_FILE_NONE, NULL); if (app_path != NULL) { g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); } /* look for gnome vendor prefix */ if (app_path == NULL) { g_free (desktop_file); desktop_file = g_strdup_printf ("gnome-%s.desktop", name); g_key_file_load_from_dirs (key_file, desktop_file, (const char **) app_dirs, &app_path, G_KEY_FILE_NONE, NULL); if (app_path != NULL) { g_debug ("GsmUtil: found in XDG dirs: '%s'", app_path); } } g_free (desktop_file); g_key_file_free (key_file); g_strfreev (app_dirs); return app_path; } static gboolean ensure_dir_exists (const char *dir) { if (g_mkdir_with_parents (dir, 0700) == 0) return TRUE; g_warning ("GsmSessionSave: Failed to create directory %s: %s", dir, strerror (errno)); return FALSE; } gchar * gsm_util_get_empty_tmp_session_dir (void) { char *tmp; gboolean exists; tmp = g_build_filename (g_get_user_config_dir (), "gnome-session", "saved-session.new", NULL); exists = ensure_dir_exists (tmp); if (G_UNLIKELY (!exists)) { g_warning ("GsmSessionSave: could not create directory for saved session: %s", tmp); g_free (tmp); return NULL; } else { /* make sure it's empty */ GDir *dir; const char *filename; dir = g_dir_open (tmp, 0, NULL); if (dir) { while ((filename = g_dir_read_name (dir))) { char *path = g_build_filename (tmp, filename, NULL); g_unlink (path); } g_dir_close (dir); } } return tmp; } const gchar * gsm_util_get_saved_session_dir (void) { if (_saved_session_dir == NULL) { gboolean exists; _saved_session_dir = g_build_filename (g_get_user_config_dir (), "gnome-session", "saved-session", NULL); exists = ensure_dir_exists (_saved_session_dir); if (G_UNLIKELY (!exists)) { static gboolean printed_warning = FALSE; if (!printed_warning) { g_warning ("GsmSessionSave: could not create directory for saved session: %s", _saved_session_dir); printed_warning = TRUE; } _saved_session_dir = NULL; return NULL; } } return _saved_session_dir; } static char ** autostart_dirs; void gsm_util_set_autostart_dirs (char ** dirs) { autostart_dirs = g_strdupv (dirs); } static char ** gsm_util_get_standard_autostart_dirs (void) { GPtrArray *dirs; const char * const *system_config_dirs; const char * const *system_data_dirs; int i; dirs = g_ptr_array_new (); g_ptr_array_add (dirs, g_build_filename (g_get_user_config_dir (), "autostart", NULL)); system_data_dirs = g_get_system_data_dirs (); for (i = 0; system_data_dirs[i]; i++) { g_ptr_array_add (dirs, g_build_filename (system_data_dirs[i], "gnome", "autostart", NULL)); } system_config_dirs = g_get_system_config_dirs (); for (i = 0; system_config_dirs[i]; i++) { g_ptr_array_add (dirs, g_build_filename (system_config_dirs[i], "autostart", NULL)); } g_ptr_array_add (dirs, NULL); return (char **) g_ptr_array_free (dirs, FALSE); } char ** gsm_util_get_autostart_dirs () { if (autostart_dirs) { return g_strdupv ((char **)autostart_dirs); } return gsm_util_get_standard_autostart_dirs (); } char ** gsm_util_get_app_dirs () { GPtrArray *dirs; const char * const *system_data_dirs; int i; dirs = g_ptr_array_new (); g_ptr_array_add (dirs, g_build_filename (g_get_user_data_dir (), "applications", NULL)); system_data_dirs = g_get_system_data_dirs (); for (i = 0; system_data_dirs[i]; i++) { g_ptr_array_add (dirs, g_build_filename (system_data_dirs[i], "applications", NULL)); } g_ptr_array_add (dirs, NULL); return (char **) g_ptr_array_free (dirs, FALSE); } char ** gsm_util_get_desktop_dirs (gboolean include_saved_session, gboolean autostart_first) { char **apps; char **autostart; char **standard_autostart; char **result; int size; int i; apps = gsm_util_get_app_dirs (); autostart = gsm_util_get_autostart_dirs (); /* Still, check the standard autostart dirs for things like fulfilling session reqs, * if using a non-standard autostart dir for autostarting */ if (autostart_dirs != NULL) standard_autostart = gsm_util_get_standard_autostart_dirs (); else standard_autostart = NULL; size = 0; for (i = 0; apps[i] != NULL; i++) { size++; } for (i = 0; autostart[i] != NULL; i++) { size++; } if (autostart_dirs != NULL) for (i = 0; standard_autostart[i] != NULL; i++) { size++; } if (include_saved_session) size += 1; result = g_new (char *, size + 1); /* including last NULL */ size = 0; if (autostart_first) { if (include_saved_session) result[size++] = g_strdup (gsm_util_get_saved_session_dir ()); for (i = 0; autostart[i] != NULL; i++, size++) { result[size] = autostart[i]; } if (standard_autostart != NULL) { for (i = 0; standard_autostart[i] != NULL; i++, size++) { result[size] = standard_autostart[i]; } } for (i = 0; apps[i] != NULL; i++, size++) { result[size] = apps[i]; } } else { for (i = 0; apps[i] != NULL; i++, size++) { result[size] = apps[i]; } if (standard_autostart != NULL) { for (i = 0; standard_autostart[i] != NULL; i++, size++) { result[size] = standard_autostart[i]; } } for (i = 0; autostart[i] != NULL; i++, size++) { result[size] = autostart[i]; } if (include_saved_session) result[size++] = g_strdup (gsm_util_get_saved_session_dir ()); } g_free (apps); g_free (autostart); g_free (standard_autostart); result[size] = NULL; return result; } gboolean gsm_util_text_is_blank (const char *str) { if (str == NULL) { return TRUE; } while (*str) { if (!isspace(*str)) { return FALSE; } str++; } return TRUE; } /** * gsm_util_init_error: * @fatal: whether or not the error is fatal to the login session * @format: printf-style error message format * @...: error message args * * Displays the error message to the user. If @fatal is %TRUE, gsm * will exit after displaying the message. * * This should be called for major errors that occur before the * session is up and running. (Notably, it positions the dialog box * itself, since no window manager will be running yet.) **/ void gsm_util_init_error (gboolean fatal, const char *format, ...) { char *msg; va_list args; gchar *argv[13]; va_start (args, format); msg = g_strdup_vprintf (format, args); va_end (args); argv[0] = "zenity"; argv[1] = "--error"; argv[2] = "--class"; argv[3] = "mutter-dialog"; argv[4] = "--title"; argv[5] = "\"\""; argv[6] = "--text"; argv[7] = msg; argv[8] = "--icon-name"; argv[9] = "face-sad-symbolic"; argv[10] = "--ok-label"; argv[11] = _("_Log out"); argv[12] = NULL; g_spawn_sync (NULL, argv, child_environment, 0, NULL, NULL, NULL, NULL, NULL, NULL); g_free (msg); if (fatal) { exit (1); } } /** * gsm_util_generate_startup_id: * * Generates a new SM client ID. * * Return value: an SM client ID. **/ char * gsm_util_generate_startup_id (void) { static int sequence = -1; static guint rand1 = 0; static guint rand2 = 0; static pid_t pid = 0; struct timeval tv; /* The XSMP spec defines the ID as: * * Version: "1" * Address type and address: * "1" + an IPv4 address as 8 hex digits * "2" + a DECNET address as 12 hex digits * "6" + an IPv6 address as 32 hex digits * Time stamp: milliseconds since UNIX epoch as 13 decimal digits * Process-ID type and process-ID: * "1" + POSIX PID as 10 decimal digits * Sequence number as 4 decimal digits * * XSMP client IDs are supposed to be globally unique: if * SmsGenerateClientID() is unable to determine a network * address for the machine, it gives up and returns %NULL. * GNOME and KDE have traditionally used a fourth address * format in this case: * "0" + 16 random hex digits * * We don't even bother trying SmsGenerateClientID(), since the * user's IP address is probably "192.168.1.*" anyway, so a random * number is actually more likely to be globally unique. */ if (!rand1) { rand1 = g_random_int (); rand2 = g_random_int (); pid = getpid (); } sequence = (sequence + 1) % 10000; gettimeofday (&tv, NULL); return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d", rand1, rand2, (unsigned long) tv.tv_sec, (unsigned) tv.tv_usec, (unsigned long) pid, sequence); } static gboolean gsm_util_update_activation_environment (const char *variable, const char *value, GError **error) { GDBusConnection *connection; gboolean environment_updated; GVariantBuilder builder; GVariant *reply; GError *bus_error = NULL; environment_updated = FALSE; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) { return FALSE; } g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); g_variant_builder_add (&builder, "{ss}", variable, value); reply = g_dbus_connection_call_sync (connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "UpdateActivationEnvironment", g_variant_new ("(@a{ss})", g_variant_builder_end (&builder)), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); } else { environment_updated = TRUE; g_variant_unref (reply); } g_clear_object (&connection); return environment_updated; } gboolean gsm_util_export_activation_environment (GError **error) { GDBusConnection *connection; gboolean environment_updated = FALSE; char **entry_names; int i = 0; GVariantBuilder builder; GRegex *name_regex, *value_regex; GVariant *reply; GError *bus_error = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) { return FALSE; } name_regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*$", G_REGEX_OPTIMIZE, 0, error); if (name_regex == NULL) { return FALSE; } value_regex = g_regex_new ("^(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error); if (value_regex == NULL) { return FALSE; } if (child_environment == NULL) { child_environment = g_listenv (); } g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}")); for (entry_names = g_listenv (); entry_names[i] != NULL; i++) { const char *entry_name = entry_names[i]; const char *entry_value = g_getenv (entry_name); if (g_strv_contains (variable_blacklist, entry_name)) continue; if (!g_utf8_validate (entry_name, -1, NULL) || !g_regex_match (name_regex, entry_name, 0, NULL) || !g_utf8_validate (entry_value, -1, NULL) || !g_regex_match (value_regex, entry_value, 0, NULL)) { g_message ("Environment variable is unsafe to export to dbus: %s", entry_name); continue; } child_environment = g_environ_setenv (child_environment, entry_name, entry_value, TRUE); g_variant_builder_add (&builder, "{ss}", entry_name, entry_value); } g_regex_unref (name_regex); g_regex_unref (value_regex); g_strfreev (entry_names); reply = g_dbus_connection_call_sync (connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "UpdateActivationEnvironment", g_variant_new ("(@a{ss})", g_variant_builder_end (&builder)), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); } else { environment_updated = TRUE; g_variant_unref (reply); } g_clear_object (&connection); return environment_updated; } #ifdef HAVE_SYSTEMD gboolean gsm_util_export_user_environment (GError **error) { GDBusConnection *connection; gboolean environment_updated = FALSE; char **entries; int i = 0; GVariantBuilder builder; GRegex *regex; GVariant *reply; GError *bus_error = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) { return FALSE; } regex = g_regex_new ("^[a-zA-Z_][a-zA-Z0-9_]*=(?:[ \t\n]|[^[:cntrl:]])*$", G_REGEX_OPTIMIZE, 0, error); if (regex == NULL) { return FALSE; } entries = g_get_environ (); for (i = 0; variable_blacklist[i] != NULL; i++) entries = g_environ_unsetenv (entries, variable_blacklist[i]); g_variant_builder_init (&builder, G_VARIANT_TYPE ("(asas)")); g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); for (i = 0; variable_unsetlist[i] != NULL; i++) g_variant_builder_add (&builder, "s", variable_unsetlist[i]); for (i = 0; variable_blacklist[i] != NULL; i++) g_variant_builder_add (&builder, "s", variable_blacklist[i]); g_variant_builder_close (&builder); g_variant_builder_open (&builder, G_VARIANT_TYPE ("as")); for (i = 0; entries[i] != NULL; i++) { const char *entry = entries[i]; if (!g_utf8_validate (entry, -1, NULL) || !g_regex_match (regex, entry, 0, NULL)) { g_message ("Environment entry is unsafe to upload into user environment: %s", entry); continue; } g_variant_builder_add (&builder, "s", entry); } g_variant_builder_close (&builder); g_regex_unref (regex); g_strfreev (entries); reply = g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment", g_variant_builder_end (&builder), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); } else { environment_updated = TRUE; g_variant_unref (reply); } g_clear_object (&connection); return environment_updated; } static gboolean gsm_util_update_user_environment (const char *variable, const char *value, GError **error) { GDBusConnection *connection; gboolean environment_updated; char *entry; GVariantBuilder builder; GVariant *reply; GError *bus_error = NULL; environment_updated = FALSE; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) { return FALSE; } g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); entry = g_strdup_printf ("%s=%s", variable, value); g_variant_builder_add (&builder, "s", entry); g_free (entry); reply = g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "SetEnvironment", g_variant_new ("(@as)", g_variant_builder_end (&builder)), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); } else { environment_updated = TRUE; g_variant_unref (reply); } g_clear_object (&connection); return environment_updated; } gboolean gsm_util_start_systemd_unit (const char *unit, const char *mode, GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) reply = NULL; GError *bus_error = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) return FALSE; reply = g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit", g_variant_new ("(ss)", unit, mode), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); return FALSE; } return TRUE; } gboolean gsm_util_systemd_reset_failed (GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) reply = NULL; GError *bus_error = NULL; connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); if (connection == NULL) return FALSE; reply = g_dbus_connection_call_sync (connection, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "ResetFailed", NULL, NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &bus_error); if (bus_error != NULL) { g_propagate_error (error, bus_error); return FALSE; } return TRUE; } #endif void gsm_util_setenv (const char *variable, const char *value) { GError *error = NULL; if (child_environment == NULL) child_environment = g_listenv (); if (!value) child_environment = g_environ_unsetenv (child_environment, variable); else child_environment = g_environ_setenv (child_environment, variable, value, TRUE); /* If this fails it isn't fatal, it means some things like session * management and keyring won't work in activated clients. */ if (!gsm_util_update_activation_environment (variable, value, &error)) { g_warning ("Could not make bus activated clients aware of %s=%s environment variable: %s", variable, value, error->message); g_clear_error (&error); } #ifdef HAVE_SYSTEMD /* If this fails, the system user session won't get the updated environment */ if (!gsm_util_update_user_environment (variable, value, &error)) { g_debug ("Could not make systemd aware of %s=%s environment variable: %s", variable, value, error->message); g_clear_error (&error); } #endif } const char * const * gsm_util_listenv (void) { return (const char * const *) child_environment; } const char * const * gsm_util_get_variable_blacklist (void) { return variable_blacklist; }