/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* gnome-keyring-daemon.c - main keyring daemon code. Copyright (C) 2003 Red Hat, Inc Gnome keyring 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. Gnome keyring 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., 675 Mass Ave, Cambridge, MA 02139, USA. Author: Alexander Larsson Author: Stef Walter */ #include "config.h" #include "gkd-glue.h" #include "gkd-main.h" #include "gkd-capability.h" #include "gkd-pkcs11.h" #include "gkd-util.h" #include "control/gkd-control.h" #include "dbus/gkd-dbus.h" #include "egg/egg-cleanup.h" #include "egg/egg-error.h" #include "egg/egg-libgcrypt.h" #include "egg/egg-secure-memory.h" #include "egg/egg-unix-credentials.h" #include "login/gkd-login.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* preset file descriptors */ #define STDIN 0 #define STDOUT 1 #define STDERR 2 #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif #define GKD_COMP_KEYRING "keyring" #define GKD_COMP_PKCS11 "pkcs11" #define GKD_COMP_SECRETS "secrets" #define GKD_COMP_SSH "ssh" #define GKD_COMP_GPG "gpg" EGG_SECURE_DECLARE (daemon_main); /* ----------------------------------------------------------------------------- * COMMAND LINE */ /* All the components to run on startup if not specified on command line */ #ifdef WITH_SSH # ifdef WITH_GPG # define DEFAULT_COMPONENTS GKD_COMP_PKCS11 "," GKD_COMP_SECRETS "," GKD_COMP_SSH "," GKD_COMP_GPG # else # define DEFAULT_COMPONENTS GKD_COMP_PKCS11 "," GKD_COMP_SECRETS "," GKD_COMP_SSH # endif #else # ifdef WITH_GPG # define DEFAULT_COMPONENTS GKD_COMP_PKCS11 "," GKD_COMP_SECRETS "," GKD_COMP_GPG # else # define DEFAULT_COMPONENTS GKD_COMP_PKCS11 "," GKD_COMP_SECRETS # endif #endif /* * If --login is used and then daemon is not initialized within LOGIN_TIMEOUT * seconds, then we exit. See on_login_timeout() below. */ #define LOGIN_TIMEOUT 120 static gchar* run_components = DEFAULT_COMPONENTS; static gboolean pkcs11_started = FALSE; static gboolean secrets_started = FALSE; static gboolean ssh_started = FALSE; static gboolean gpg_started = FALSE; static gboolean dbus_started = FALSE; static gboolean run_foreground = FALSE; static gboolean run_daemonized = FALSE; static gboolean run_version = FALSE; static gboolean run_for_login = FALSE; static gboolean perform_unlock = FALSE; static gboolean run_for_start = FALSE; static gboolean run_for_replace = FALSE; static gchar* login_password = NULL; static gchar* control_directory = NULL; static guint timeout_id = 0; static gboolean initialization_completed = FALSE; static GMainLoop *loop = NULL; static GOptionEntry option_entries[] = { { "start", 's', 0, G_OPTION_ARG_NONE, &run_for_start, "Start a dameon or initialize an already running daemon." }, { "replace", 'r', 0, G_OPTION_ARG_NONE, &run_for_replace, "Replace the daemon for this desktop login environment." }, { "foreground", 'f', 0, G_OPTION_ARG_NONE, &run_foreground, "Run in the foreground", NULL }, { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &run_daemonized, "Run as a daemon", NULL }, { "login", 'l', 0, G_OPTION_ARG_NONE, &run_for_login, "Run by PAM for a user login. Read login password from stdin", NULL }, { "unlock", 0, 0, G_OPTION_ARG_NONE, &perform_unlock, "Prompt for login keyring password, or read from stdin", NULL }, { "components", 'c', 0, G_OPTION_ARG_STRING, &run_components, "The optional components to run", DEFAULT_COMPONENTS }, { "control-directory", 'C', 0, G_OPTION_ARG_FILENAME, &control_directory, "The directory for sockets and control data", NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &run_version, "Show the version number and exit.", NULL }, { NULL } }; static void parse_arguments (int *argc, char** argv[]) { GError *err = NULL; GOptionContext *context; context = g_option_context_new ("- The Gnome Keyring Daemon"); g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE); if (!g_option_context_parse (context, argc, argv, &err)) { g_printerr ("gnome-keyring-daemon: %s\n", egg_error_message (err)); g_clear_error (&err); } if (!run_components || !run_components[0]) { run_components = DEFAULT_COMPONENTS; } else { run_components = g_strdup (run_components); egg_cleanup_register (g_free, run_components); } /* Check the arguments */ if (run_for_login && run_for_start) { g_printerr ("gnome-keyring-daemon: The --start option is incompatible with --login\n"); run_for_login = FALSE; } if (run_for_login && run_for_replace) { g_printerr ("gnome-keyring-daemon: The --replace option is incompatible with --login\n"); run_for_login = FALSE; } if (run_for_start && run_for_replace) { g_printerr ("gnome-keyring-daemon: The --replace option is incompatible with --start\n"); run_for_start = FALSE; } if (run_for_start && perform_unlock) { g_printerr ("gnome-keyring-daemon: The --start option is incompatible with --unlock"); perform_unlock = FALSE; } if (run_for_login) perform_unlock = TRUE; g_option_context_free (context); } /* ----------------------------------------------------------------------------- * MEMORY */ static gboolean do_warning = TRUE; #define WARNING "couldn't allocate secure memory to keep passwords " \ "and or keys from being written to the disk" #define ABORTMSG "The GNOME_KEYRING_PARANOID environment variable was set. " \ "Exiting..." /* * These are called from gkr-secure-memory.c to provide appropriate * locking for memory between threads */ G_LOCK_DEFINE_STATIC (memory_mutex); static void egg_memory_lock (void) { G_LOCK (memory_mutex); } static void egg_memory_unlock (void) { G_UNLOCK (memory_mutex); } static void * egg_memory_fallback (void *p, size_t sz) { const gchar *env; /* We were asked to free memory */ if (!sz) { g_free (p); return NULL; } /* We were asked to allocate */ if (!p) { if (do_warning) { g_message (WARNING); do_warning = FALSE; } env = g_getenv ("GNOME_KEYRING_PARANOID"); if (env && *env) g_error (ABORTMSG); return g_malloc0 (sz); } /* * Reallocation is a bit of a gray area, as we can be asked * by external libraries (like libgcrypt) to reallocate a * non-secure block into secure memory. We cannot satisfy * this request (as we don't know the size of the original * block) so we just try our best here. */ return g_realloc (p, sz); } EGG_SECURE_DEFINE_GLOBALS (egg_memory_lock, egg_memory_unlock, egg_memory_fallback); /* ----------------------------------------------------------------------------- * LOGS */ static void log_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { int level; /* Note that crit and err are the other way around in syslog */ switch (G_LOG_LEVEL_MASK & log_level) { case G_LOG_LEVEL_ERROR: level = LOG_CRIT; break; case G_LOG_LEVEL_CRITICAL: level = LOG_ERR; break; case G_LOG_LEVEL_WARNING: level = LOG_WARNING; break; case G_LOG_LEVEL_MESSAGE: level = LOG_NOTICE; break; case G_LOG_LEVEL_INFO: level = LOG_INFO; break; case G_LOG_LEVEL_DEBUG: level = -1; break; default: level = LOG_ERR; break; } /* Log to syslog first */ if (level != -1) { if (log_domain) syslog (level, "%s: %s", log_domain, message); else syslog (level, "%s", message); } /* And then to default handler for aborting and stuff like that */ g_log_default_handler (log_domain, log_level, message, user_data); } static void printerr_handler (const gchar *string) { /* Print to syslog and stderr */ syslog (LOG_WARNING, "%s", string); fprintf (stderr, "%s", string); } static void prepare_logging () { GLogLevelFlags flags = G_LOG_FLAG_FATAL | G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO; openlog ("gnome-keyring-daemon", LOG_PID, LOG_AUTH); g_log_set_handler (NULL, flags, log_handler, NULL); g_log_set_handler ("Glib", flags, log_handler, NULL); g_log_set_handler ("Gtk", flags, log_handler, NULL); g_log_set_handler ("Gnome", flags, log_handler, NULL); g_log_set_default_handler (log_handler, NULL); g_set_printerr_handler (printerr_handler); } #ifdef WITH_DEBUG static void dump_diagnostics (void) { egg_secure_rec *records; egg_secure_rec *rec; unsigned int count, i; GHashTable *table; GHashTableIter iter; gsize request = 0; gsize block = 0; g_printerr ("------------------- Secure Memory --------------------\n"); g_printerr (" Tag Used Space\n"); g_printerr ("------------------------------------------------------\n"); records = egg_secure_records (&count); table = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0; i < count; i++) { if (!records[i].tag) records[i].tag = ""; rec = g_hash_table_lookup (table, records[i].tag); if (rec == NULL) g_hash_table_insert (table, (gchar *)records[i].tag, &records[i]); else { rec->block_length += records[i].block_length; rec->request_length += records[i].request_length; } block += records[i].block_length; request += records[i].request_length; } g_hash_table_iter_init (&iter, table); while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&rec)) g_printerr (" %-20s %12lu %16lu\n", rec->tag, (unsigned long)rec->request_length, (unsigned long)rec->block_length); if (count > 0) g_printerr ("------------------------------------------------------\n"); g_printerr (" %-20s %12lu %16lu\n", "Total", (unsigned long)request, (unsigned long)block); g_printerr ("------------------------------------------------------\n"); g_hash_table_destroy (table); free (records); } #endif /* WITH_DEBUG */ /* ----------------------------------------------------------------------------- * SIGNALS */ void gkd_main_quit (void) { /* Always stop accepting control connections immediately */ gkd_control_stop (); g_main_loop_quit (loop); } static gboolean on_signal_term (gpointer user_data) { gkd_main_quit (); g_debug ("received signal, terminating"); return FALSE; } static gboolean on_signal_usr1 (gpointer user_data) { #ifdef WITH_DEBUG dump_diagnostics (); #endif return TRUE; } /* ----------------------------------------------------------------------------- * STARTUP */ static int sane_dup2 (int fd1, int fd2) { int ret; retry: ret = dup2 (fd1, fd2); if (ret < 0 && errno == EINTR) goto retry; return ret; } static gchar* read_login_password (int fd) { /* We only accept a max of 8K as the login password */ #define MAX_LENGTH 8192 #define MAX_BLOCK 256 /* * When --login is specified then the login password is passed * in on stdin. All data (including newlines) are part of the * password. A zero length password is no password. */ gchar *buf = egg_secure_alloc (MAX_BLOCK); gchar *ret = NULL; int r, len = 0; for (;;) { r = read (fd, buf, MAX_BLOCK); if (r < 0) { if (errno == EAGAIN) continue; egg_secure_free (ret); egg_secure_free (buf); return NULL; } else if (r == 0 || len > MAX_LENGTH) { break; } else { ret = egg_secure_realloc (ret, len + r + 1); memset (ret + len, 0, r + 1); len = len + r; strncat (ret, buf, r); } } egg_secure_free (buf); return ret; } static void cleanup_and_exit (int code) { egg_cleanup_perform (); exit (code); } static void clear_login_password (void) { if(login_password) egg_secure_strfree (login_password); login_password = NULL; } static void print_environment (void) { const gchar **env; for (env = gkd_util_get_environment (); *env; ++env) printf ("%s\n", *env); fflush (stdout); } static gboolean initialize_daemon_at (const gchar *directory) { gchar **ourenv, **daemonenv, **e; /* Exchange environment variables, and try to initialize daemon */ ourenv = gkd_util_build_environment (GKD_UTIL_IN_ENVIRONMENT); daemonenv = gkd_control_initialize (directory, run_components, (const gchar**)ourenv); g_strfreev (ourenv); /* Initialization failed, start this process up as a daemon */ if (!daemonenv) return FALSE; /* Setup all the environment variables we were passed */ for (e = daemonenv; *e; ++e) gkd_util_push_environment_full (*e); g_strfreev (daemonenv); return TRUE; } static gboolean replace_daemon_at (const gchar *directory) { gboolean ret; /* * The first control_directory is the environment one, always * prefer that since it's the one that ssh and gpg will connect to */ if (control_directory == NULL) control_directory = g_strdup (directory); ret = gkd_control_quit (directory, GKD_CONTROL_QUIET_IF_NO_PEER); /* * If we quit, wait a short time before initializing so the other * daemon can quit completely */ if (ret == TRUE) g_usleep (200 * 1000); /* 200 ms in us */ /* * Note that we don't return TRUE, since we want to quit all the * running daemons (for this session) that may have been started * by dbus or elsewhere. */ return FALSE; } typedef gboolean (*DiscoverFunc) (const gchar *control_directory); static gboolean discover_other_daemon (DiscoverFunc callback, gboolean acquire) { const gchar *control_env; gchar *control = NULL; gboolean acquired = FALSE; gboolean ret; /* A pre-specified directory to control at, don't try anything else */ if (control_directory) return (callback) (control_directory); /* An environment variable from an already running daemon */ control_env = g_getenv (GKD_UTIL_ENV_CONTROL); if (control_env && control_env[0]) { if ((callback)(control_env)) return TRUE; } /* Or the default location when no evironment variable */ control_env = g_getenv ("XDG_RUNTIME_DIR"); if (control_env) { control = g_build_filename (control_env, "keyring", NULL); ret = (callback) (control); g_free (control); if (ret == TRUE) return TRUE; } /* See if we can contact a daemon running, that didn't set an env variable */ if (acquire && !gkd_dbus_singleton_acquire (&acquired)) return FALSE; /* We're the main daemon */ if (acquired) return FALSE; control = gkd_dbus_singleton_control (); if (control) { ret = (callback) (control); g_free (control); if (ret == TRUE) return TRUE; } return FALSE; } static void fork_and_print_environment (void) { int status; pid_t pid; int fd, i; if (run_foreground) { print_environment (); return; } pid = fork (); if (pid != 0) { /* Here we are in the initial process */ if (run_daemonized) { /* Initial process, waits for intermediate child */ if (pid == -1) exit (1); waitpid (pid, &status, 0); if (WEXITSTATUS (status) != 0) exit (WEXITSTATUS (status)); } else { /* Not double forking */ print_environment (); } /* The initial process exits successfully */ exit (0); } if (run_daemonized) { /* * Become session leader of a new session, process group leader of a new * process group, and detach from the controlling TTY, so that SIGHUP is * not sent to this process when the previous session leader dies */ setsid (); /* Double fork if need to daemonize properly */ pid = fork (); if (pid != 0) { /* Here we are in the intermediate child process */ /* * This process exits, so that the final child will inherit * init as parent to avoid zombies */ if (pid == -1) exit (1); /* We've done two forks. */ print_environment (); /* The intermediate child exits */ exit (0); } } /* Here we are in the resulting daemon or background process. */ for (i = 0; i < 3; ++i) { fd = open ("/dev/null", O_RDONLY); sane_dup2 (fd, i); close (fd); } } static gboolean gkr_daemon_startup_steps (const gchar *components) { g_assert (components); /* * Startup that must run before forking. * Note that we set initialized flags early so that two * initializations don't overlap */ #ifdef WITH_SSH if (strstr (components, GKD_COMP_SSH)) { if (ssh_started) { g_message ("The SSH agent was already initialized"); } else { ssh_started = TRUE; if (!gkd_daemon_startup_ssh ()) { ssh_started = FALSE; return FALSE; } } } #endif #ifdef WITH_GPG if (strstr (components, GKD_COMP_GPG)) { if (gpg_started) { g_message ("The GPG agent was already initialized"); } else { gpg_started = TRUE; if (!gkd_daemon_startup_gpg ()) { gpg_started = FALSE; return FALSE; } } } #endif return TRUE; } static gboolean gkr_daemon_initialize_steps (const gchar *components) { g_assert (components); /* * Startup that can run after forking. * Note that we set initialized flags early so that two * initializations don't overlap */ if (!initialization_completed) { /* The LANG environment variable may have changed */ setlocale (LC_ALL, ""); initialization_completed = TRUE; if (timeout_id) g_source_remove (timeout_id); /* Initialize new style PKCS#11 components */ if (!gkd_pkcs11_initialize ()) return FALSE; /* * Unlock the login keyring if we were given a password on STDIN. * If it does not exist. We create it. */ if (login_password) { if (!gkd_login_unlock (login_password)) g_message ("failed to unlock login keyring on startup"); egg_secure_strclear (login_password); } dbus_started = TRUE; if (!gkd_dbus_setup ()) dbus_started = FALSE; } /* The Secret Service API */ if (strstr (components, GKD_COMP_SECRETS) || strstr (components, GKD_COMP_KEYRING)) { if (secrets_started) { g_message ("The Secret Service was already initialized"); } else { if (!dbus_started) { dbus_started = TRUE; if (!gkd_dbus_setup ()) dbus_started = FALSE; } if (dbus_started) { secrets_started = TRUE; if (!gkd_dbus_secrets_startup ()) { secrets_started = FALSE; return FALSE; } } } } /* The PKCS#11 remoting */ if (strstr (components, GKD_COMP_PKCS11)) { if (pkcs11_started) { g_message ("The PKCS#11 component was already initialized"); } else { pkcs11_started = TRUE; if (!gkd_pkcs11_startup_pkcs11 ()) { pkcs11_started = FALSE; return FALSE; } } } return TRUE; } void gkd_main_complete_initialization (const gchar *components) { g_assert (components); /* * Sometimes we don't initialize the full daemon right on * startup. When run with --login is one such case. */ gkr_daemon_startup_steps (components); gkr_daemon_initialize_steps (components); } static gboolean on_login_timeout (gpointer data) { if (!initialization_completed) cleanup_and_exit (0); return FALSE; } int main (int argc, char *argv[]) { /* * The gnome-keyring startup is not as simple as I wish it could be. * * It's often started in the primordial stages of a session, where * there's no DBus, and no proper X display. This is the strange world * of PAM. * * When started with the --login option, we do as little initialization * as possible. We expect a login password on the stdin, and unlock * or create the login keyring. * * Then later we expect gnome-keyring-dameon to be run again with the * --start option. This second gnome-keyring-daemon will hook the * original daemon up with environment variables necessary to initialize * itself and bring it into the session. This second daemon usually exits. * * Without either of these options, we follow a more boring and * predictable startup. */ /* * Before we do ANYTHING, we drop privileges so we don't become * a security issue ourselves. */ gkd_capability_obtain_capability_and_drop_privileges (); #ifdef WITH_STRICT g_setenv ("DBUS_FATAL_WARNINGS", "1", FALSE); if (!g_getenv ("G_DEBUG")) g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); #endif #if !GLIB_CHECK_VERSION(2,35,0) g_type_init (); #endif #ifdef HAVE_LOCALE_H /* internationalisation */ setlocale (LC_ALL, ""); #endif #ifdef HAVE_GETTEXT bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); textdomain (GETTEXT_PACKAGE); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); #endif egg_libgcrypt_initialize (); /* Send all warning or error messages to syslog */ prepare_logging (); parse_arguments (&argc, &argv); /* The --version option. This is machine parseable output */ if (run_version) { g_print ("gnome-keyring-daemon: %s\n", VERSION); g_print ("testing: %s\n", #ifdef WITH_DEBUG "enabled"); #else "disabled"); #endif exit (0); } /* The --start option */ if (run_for_start) { if (discover_other_daemon (initialize_daemon_at, TRUE)) { /* * Another daemon was initialized, print out environment * for any callers, and quit or go comatose. */ print_environment (); if (run_foreground) while (sleep(0x08000000) == 0); cleanup_and_exit (0); } /* The --replace option */ } else if (run_for_replace) { discover_other_daemon (replace_daemon_at, FALSE); if (control_directory) g_message ("Replacing daemon, using directory: %s", control_directory); else g_message ("Could not find daemon to replace, staring normally"); } /* Initialize the main directory */ gkd_util_init_master_directory (control_directory); /* Initialize our daemon main loop and threading */ loop = g_main_loop_new (NULL, FALSE); /* Initialize our control socket */ if (!gkd_control_listen ()) return FALSE; if (perform_unlock) { login_password = read_login_password (STDIN); atexit (clear_login_password); } /* The --login option. Delayed initialization */ if (run_for_login) { timeout_id = g_timeout_add_seconds (LOGIN_TIMEOUT, (GSourceFunc) on_login_timeout, NULL); /* Not a login daemon. Startup stuff now.*/ } else { /* These are things that can run before forking */ if (!gkr_daemon_startup_steps (run_components)) cleanup_and_exit (1); } signal (SIGPIPE, SIG_IGN); /* The whole forking and daemonizing dance starts here. */ fork_and_print_environment(); g_unix_signal_add (SIGTERM, on_signal_term, loop); g_unix_signal_add (SIGHUP, on_signal_term, loop); g_unix_signal_add (SIGUSR1, on_signal_usr1, loop); /* Prepare logging a second time, since we may be in a different process */ prepare_logging(); /* Remainder initialization after forking, if initialization not delayed */ if (!run_for_login) { gkr_daemon_initialize_steps (run_components); /* * Close stdout and so that the caller knows that we're * all initialized, (when run in foreground mode). * * However since some logging goes to stdout, redirect that * to stderr. We don't want the caller confusing that with * valid output anyway. */ if (dup2 (2, 1) < 1) g_warning ("couldn't redirect stdout to stderr"); g_debug ("initialization complete"); } g_main_loop_run (loop); /* This wraps everything up in order */ egg_cleanup_perform (); g_free (control_directory); g_debug ("exiting cleanly"); return 0; }