/* * gnome-keyring * * Copyright (C) 2014 Stef Walter * Copyright (C) 2018 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 Lesser General Public * License along with this program; if not, see * . * * Author: Stef Walter , Daiki Ueno */ #include "config.h" #include "gcr-ssh-agent-process.h" #include "gcr-ssh-agent-private.h" #include "gcr-ssh-agent-util.h" #include #include #include enum { PROP_0, PROP_PATH }; enum { CLOSED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; struct _GcrSshAgentProcess { GObject object; gchar *path; gint output; GMutex lock; GPid pid; guint output_id; guint child_id; gboolean ready; }; G_DEFINE_TYPE (GcrSshAgentProcess, gcr_ssh_agent_process, G_TYPE_OBJECT); static void gcr_ssh_agent_process_init (GcrSshAgentProcess *self) { self->output = -1; g_mutex_init (&self->lock); } static void gcr_ssh_agent_process_finalize (GObject *object) { GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (object); if (self->output != -1) close (self->output); if (self->output_id) g_source_remove (self->output_id); if (self->child_id) g_source_remove (self->child_id); if (self->pid) kill (self->pid, SIGTERM); g_unlink (self->path); g_free (self->path); g_mutex_clear (&self->lock); G_OBJECT_CLASS (gcr_ssh_agent_process_parent_class)->finalize (object); } static void gcr_ssh_agent_process_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (object); switch (prop_id) { case PROP_PATH: self->path = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gcr_ssh_agent_process_class_init (GcrSshAgentProcessClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gcr_ssh_agent_process_finalize; gobject_class->set_property = gcr_ssh_agent_process_set_property; g_object_class_install_property (gobject_class, PROP_PATH, g_param_spec_string ("path", "Path", "Path", "", G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); signals[CLOSED] = g_signal_new_class_handler ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void on_child_watch (GPid pid, gint status, gpointer user_data) { GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (user_data); GError *error = NULL; if (pid != self->pid) return; g_mutex_lock (&self->lock); self->pid = 0; self->output_id = 0; self->child_id = 0; if (!g_spawn_check_exit_status (status, &error)) { g_message ("ssh-agent: %s", error->message); g_error_free (error); } g_spawn_close_pid (pid); g_mutex_unlock (&self->lock); g_signal_emit (self, signals[CLOSED], 0); } static gboolean on_output_watch (gint fd, GIOCondition condition, gpointer user_data) { GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (user_data); guint8 buf[1024]; gssize len; if (condition & G_IO_IN) { self->ready = TRUE; len = read (fd, buf, sizeof (buf)); if (len < 0) { if (errno != EAGAIN && errno != EINTR) g_message ("couldn't read from ssh-agent stdout: %m"); condition |= G_IO_ERR; } } if (condition & G_IO_HUP || condition & G_IO_ERR) return FALSE; return TRUE; } static gboolean agent_start_inlock (GcrSshAgentProcess *self, GError **error) { const gchar *argv[] = { SSH_AGENT_EXECUTABLE, "-D", "-a", self->path, NULL }; GPid pid; if (!g_spawn_async_with_pipes ("/", (gchar **)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_CLOEXEC_PIPES, NULL, NULL, &pid, NULL, &self->output, NULL, error)) return FALSE; self->ready = FALSE; self->output_id = g_unix_fd_add (self->output, G_IO_IN | G_IO_HUP | G_IO_ERR, on_output_watch, self); self->pid = pid; self->child_id = g_child_watch_add (self->pid, on_child_watch, self); return TRUE; } static gboolean on_timeout (gpointer user_data) { gboolean *timedout = user_data; *timedout = TRUE; return TRUE; } GSocketConnection * gcr_ssh_agent_process_connect (GcrSshAgentProcess *self, GCancellable *cancellable, GError **error) { gboolean started = FALSE; gboolean timedout = FALSE; guint source; GSocketClient *client; GSocketAddress *address; GSocketConnection *connection; g_mutex_lock (&self->lock); if (self->pid == 0) { if (!agent_start_inlock (self, error)) { g_mutex_unlock (&self->lock); return NULL; } started = TRUE; } if (started && !self->ready) { source = g_timeout_add_seconds (5, on_timeout, &timedout); while (!self->ready && !timedout) g_main_context_iteration (NULL, FALSE); g_source_remove (source); } if (!self->ready) { g_mutex_unlock (&self->lock); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "ssh-agent process is not ready"); return NULL; } address = g_unix_socket_address_new (self->path); client = g_socket_client_new (); connection = g_socket_client_connect (client, G_SOCKET_CONNECTABLE (address), cancellable, error); g_object_unref (address); g_object_unref (client); g_mutex_unlock (&self->lock); return connection; } GcrSshAgentProcess * gcr_ssh_agent_process_new (const gchar *path) { g_return_val_if_fail (path, NULL); return g_object_new (GCR_TYPE_SSH_AGENT_PROCESS, "path", path, NULL); } GPid gcr_ssh_agent_process_get_pid (GcrSshAgentProcess *self) { return self->pid; }