/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DBUS_API_SUBJECT_TO_CHANGE #include #include #include "gdm-manager.h" #include "gdm-log.h" #include "gdm-common.h" #include "gdm-signal-handler.h" #include "gdm-settings.h" #include "gdm-settings-direct.h" #include "gdm-settings-keys.h" #define GDM_DBUS_NAME "org.gnome.DisplayManager" static void bus_proxy_destroyed_cb (DBusGProxy *bus_proxy, GdmManager **managerp); extern char **environ; static GdmManager *manager = NULL; static GdmSettings *settings = NULL; static uid_t gdm_uid = -1; static gid_t gdm_gid = -1; static gboolean timed_exit_cb (GMainLoop *loop) { g_main_loop_quit (loop); return FALSE; } static DBusGProxy * get_bus_proxy (DBusGConnection *connection) { DBusGProxy *bus_proxy; bus_proxy = dbus_g_proxy_new_for_name (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); return bus_proxy; } static gboolean acquire_name_on_proxy (DBusGProxy *bus_proxy) { GError *error; guint result; gboolean res; gboolean ret; ret = FALSE; if (bus_proxy == NULL) { goto out; } error = NULL; res = dbus_g_proxy_call (bus_proxy, "RequestName", &error, G_TYPE_STRING, GDM_DBUS_NAME, G_TYPE_UINT, 0, G_TYPE_INVALID, G_TYPE_UINT, &result, G_TYPE_INVALID); if (! res) { if (error != NULL) { g_warning ("Failed to acquire %s: %s", GDM_DBUS_NAME, error->message); g_error_free (error); } else { g_warning ("Failed to acquire %s", GDM_DBUS_NAME); } goto out; } if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { if (error != NULL) { g_warning ("Failed to acquire %s: %s", GDM_DBUS_NAME, error->message); g_error_free (error); } else { g_warning ("Failed to acquire %s", GDM_DBUS_NAME); } goto out; } ret = TRUE; out: return ret; } static DBusGConnection * get_system_bus (void) { GError *error; DBusGConnection *bus; DBusConnection *connection; error = NULL; bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (bus == NULL) { g_warning ("Couldn't connect to system bus: %s", error->message); g_error_free (error); goto out; } connection = dbus_g_connection_get_connection (bus); dbus_connection_set_exit_on_disconnect (connection, FALSE); out: return bus; } static gboolean bus_reconnect (GdmManager *manager) { DBusGConnection *bus; DBusGProxy *bus_proxy; gboolean ret; ret = TRUE; bus = get_system_bus (); if (bus == NULL) { goto out; } bus_proxy = get_bus_proxy (bus); if (bus_proxy == NULL) { g_warning ("Could not construct bus_proxy object; will retry"); goto out; } if (! acquire_name_on_proxy (bus_proxy) ) { g_warning ("Could not acquire name; will retry"); goto out; } manager = gdm_manager_new (); if (manager == NULL) { g_warning ("Could not construct manager object"); exit (1); } g_signal_connect (bus_proxy, "destroy", G_CALLBACK (bus_proxy_destroyed_cb), &manager); g_debug ("Successfully reconnected to D-Bus"); gdm_manager_start (manager); ret = FALSE; out: return ret; } static void bus_proxy_destroyed_cb (DBusGProxy *bus_proxy, GdmManager **managerp) { g_debug ("Disconnected from D-Bus"); if (managerp == NULL || *managerp == NULL) { /* probably shutting down or something */ return; } g_object_unref (*managerp); *managerp = NULL; g_timeout_add_seconds (3, (GSourceFunc)bus_reconnect, managerp); } static void delete_pid (void) { g_unlink (GDM_PID_FILE); } static void write_pid (void) { int pf; ssize_t written; char pid[9]; errno = 0; pf = open (GDM_PID_FILE, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644); if (pf < 0) { g_warning (_("Cannot write PID file %s: possibly out of diskspace: %s"), GDM_PID_FILE, g_strerror (errno)); return; } snprintf (pid, sizeof (pid), "%lu\n", (long unsigned) getpid ()); errno = 0; written = write (pf, pid, strlen (pid)); close (pf); if (written < 0) { g_warning (_("Cannot write PID file %s: possibly out of diskspace: %s"), GDM_PID_FILE, g_strerror (errno)); return; } g_atexit (delete_pid); } static void check_logdir (void) { struct stat statbuf; int r; const char *log_path; log_path = LOGDIR; r = g_stat (log_path, &statbuf); if (r < 0 || ! S_ISDIR (statbuf.st_mode)) { if (g_mkdir (log_path, 0755) < 0) { gdm_fail (_("Logdir %s does not exist or isn't a directory."), log_path); } g_chmod (log_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); } } static void check_servauthdir (const char *auth_path, struct stat *statbuf) { int r; /* Enter paranoia mode */ r = g_stat (auth_path, statbuf); if (r < 0) { gdm_fail (_("Authdir %s does not exist. Aborting."), auth_path); } if (! S_ISDIR (statbuf->st_mode)) { gdm_fail (_("Authdir %s is not a directory. Aborting."), auth_path); } } static void set_effective_user (uid_t uid) { int res; res = 0; if (geteuid () != uid) { res = seteuid (uid); } if (res != 0) { g_error ("Cannot set uid to %d: %s", (int)uid, g_strerror (errno)); } } static void set_effective_group (gid_t gid) { int res; res = 0; if (getegid () != gid) { res = setegid (gid); } if (res != 0) { g_error ("Cannot set gid to %d: %s", (int)gid, g_strerror (errno)); } } static void set_effective_user_group (uid_t uid, gid_t gid) { set_effective_user (0); set_effective_group (gid); if (uid != 0) { set_effective_user (0); } } static void gdm_daemon_check_permissions (uid_t uid, gid_t gid) { struct stat statbuf; const char *auth_path; auth_path = LOGDIR; /* Enter paranoia mode */ check_servauthdir (auth_path, &statbuf); set_effective_user_group (0, 0); /* Now set things up for us as */ chown (auth_path, 0, gid); g_chmod (auth_path, (S_IRWXU|S_IRWXG|S_ISVTX)); set_effective_user_group (uid, gid); /* Again paranoid */ check_servauthdir (auth_path, &statbuf); if G_UNLIKELY (statbuf.st_uid != 0 || statbuf.st_gid != gid) { gdm_fail (_("Authdir %s is not owned by user %d, group %d. Aborting."), auth_path, (int)uid, (int)gid); } if G_UNLIKELY (statbuf.st_mode != (S_IFDIR|S_IRWXU|S_IRWXG|S_ISVTX)) { gdm_fail (_("Authdir %s has wrong permissions %o. Should be %o. Aborting."), auth_path, statbuf.st_mode, (S_IRWXU|S_IRWXG|S_ISVTX)); } } static void gdm_daemon_change_user (uid_t *uidp, gid_t *gidp) { char *username; char *groupname; uid_t uid; gid_t gid; struct passwd *pwent; struct group *grent; username = NULL; groupname = NULL; uid = 0; gid = 0; gdm_settings_direct_get_string (GDM_KEY_USER, &username); gdm_settings_direct_get_string (GDM_KEY_GROUP, &groupname); if (username == NULL || groupname == NULL) { return; } g_debug ("Changing user:group to %s:%s", username, groupname); /* Lookup user and groupid for the GDM user */ pwent = getpwnam (username); /* Set uid and gid */ if G_UNLIKELY (pwent == NULL) { gdm_fail (_("Can't find the GDM user '%s'. Aborting!"), username); } else { uid = pwent->pw_uid; } if G_UNLIKELY (uid == 0) { gdm_fail (_("The GDM user should not be root. Aborting!")); } grent = getgrnam (groupname); if G_UNLIKELY (grent == NULL) { gdm_fail (_("Can't find the GDM group '%s'. Aborting!"), groupname); } else { gid = grent->gr_gid; } if G_UNLIKELY (gid == 0) { gdm_fail (_("The GDM group should not be root. Aborting!")); } /* gid remains 'gdm' */ set_effective_user_group (uid, gid); if (uidp != NULL) { *uidp = uid; } if (gidp != NULL) { *gidp = gid; } g_free (username); g_free (groupname); } static gboolean signal_cb (int signo, gpointer data) { int ret; g_debug ("Got callback for signal %d", signo); ret = TRUE; switch (signo) { case SIGFPE: case SIGPIPE: /* let the fatal signals interrupt us */ g_debug ("Caught signal %d, shutting down abnormally.", signo); ret = FALSE; break; case SIGINT: case SIGTERM: /* let the fatal signals interrupt us */ g_debug ("Caught signal %d, shutting down normally.", signo); ret = FALSE; break; case SIGHUP: g_debug ("Got HUP signal"); /* FIXME: * Reread config stuff like system config files, VPN service files, etc */ ret = TRUE; break; case SIGUSR1: g_debug ("Got USR1 signal"); /* FIXME: * Play with log levels or something */ ret = TRUE; gdm_log_toggle_debug (); break; default: g_debug ("Caught unhandled signal %d", signo); ret = TRUE; break; } return ret; } static gboolean is_debug_set (void) { gboolean debug = FALSE; /* enable debugging for unstable builds */ if (gdm_is_version_unstable ()) { return TRUE; } gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug); return debug; } int main (int argc, char **argv) { GMainLoop *main_loop; GOptionContext *context; DBusGProxy *bus_proxy; DBusGConnection *connection; GError *error; int ret; gboolean res; gboolean xdmcp_enabled; GdmSignalHandler *signal_handler; static gboolean do_timed_exit = FALSE; static gboolean print_version = FALSE; static gboolean fatal_warnings = FALSE; static GOptionEntry entries [] = { { "fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &fatal_warnings, N_("Make all warnings fatal"), NULL }, { "timed-exit", 0, 0, G_OPTION_ARG_NONE, &do_timed_exit, N_("Exit after a time - for debugging"), NULL }, { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, N_("Print GDM version"), NULL }, { NULL } }; bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain (GETTEXT_PACKAGE); setlocale (LC_ALL, ""); ret = 1; gdm_set_fatal_warnings_if_unstable (); g_type_init (); context = g_option_context_new (_("GNOME Display Manager")); g_option_context_add_main_entries (context, entries, NULL); g_option_context_set_ignore_unknown_options (context, TRUE); error = NULL; res = g_option_context_parse (context, &argc, &argv, &error); g_option_context_free (context); if (! res) { g_warning ("%s", error->message); g_error_free (error); goto out; } if (fatal_warnings) { GLogLevelFlags fatal_mask; fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; g_log_set_always_fatal (fatal_mask); } connection = get_system_bus (); if (connection == NULL) { goto out; } bus_proxy = get_bus_proxy (connection); if (bus_proxy == NULL) { g_warning ("Could not construct bus_proxy object; bailing out"); goto out; } if (! acquire_name_on_proxy (bus_proxy) ) { g_warning ("Could not acquire name; bailing out"); goto out; } gdm_log_init (); settings = gdm_settings_new (); if (settings == NULL) { g_warning ("Unable to initialize settings"); goto out; } if (! gdm_settings_direct_init (settings, GDMCONFDIR "/gdm.schemas", "/")) { g_warning ("Unable to initialize settings"); goto out; } gdm_log_set_debug (is_debug_set ()); gdm_daemon_change_user (&gdm_uid, &gdm_gid); gdm_daemon_check_permissions (gdm_uid, gdm_gid); set_effective_user_group (0, 0); check_logdir (); /* XDM compliant error message */ if (getuid () != 0) { /* make sure the pid file doesn't get wiped */ g_warning (_("Only the root user can run GDM")); exit (-1); } /* pid file */ delete_pid (); write_pid (); g_chdir (AUTHDIR); manager = gdm_manager_new (); if (manager == NULL) { goto out; } xdmcp_enabled = FALSE; gdm_settings_direct_get_boolean (GDM_KEY_XDMCP_ENABLE, &xdmcp_enabled); gdm_manager_set_xdmcp_enabled (manager, xdmcp_enabled); g_signal_connect (bus_proxy, "destroy", G_CALLBACK (bus_proxy_destroyed_cb), &manager); main_loop = g_main_loop_new (NULL, FALSE); signal_handler = gdm_signal_handler_new (); gdm_signal_handler_set_fatal_func (signal_handler, (GDestroyNotify)g_main_loop_quit, main_loop); gdm_signal_handler_add_fatal (signal_handler); gdm_signal_handler_add (signal_handler, SIGTERM, signal_cb, NULL); gdm_signal_handler_add (signal_handler, SIGINT, signal_cb, NULL); gdm_signal_handler_add (signal_handler, SIGFPE, signal_cb, NULL); gdm_signal_handler_add (signal_handler, SIGHUP, signal_cb, NULL); gdm_signal_handler_add (signal_handler, SIGUSR1, signal_cb, NULL); if (do_timed_exit) { g_timeout_add_seconds (30, (GSourceFunc) timed_exit_cb, main_loop); } gdm_manager_start (manager); g_main_loop_run (main_loop); g_debug ("GDM finished, cleaning up..."); if (manager != NULL) { g_object_unref (manager); } if (settings != NULL) { g_object_unref (settings); } if (signal_handler != NULL) { g_object_unref (signal_handler); } gdm_settings_direct_shutdown (); gdm_log_shutdown (); g_main_loop_unref (main_loop); ret = 0; out: return ret; }