Line data Source code
1 : /*
2 : * gnome-keyring
3 : *
4 : * Copyright (C) 2014 Stef Walter
5 : * Copyright (C) 2018 Red Hat, Inc.
6 : *
7 : * This program is free software; you can redistribute it and/or modify
8 : * it under the terms of the GNU Lesser General Public License as
9 : * published by the Free Software Foundation; either version 2.1 of
10 : * the License, or (at your option) any later version.
11 : *
12 : * This program is distributed in the hope that it will be useful, but
13 : * WITHOUT ANY WARRANTY; without even the implied warranty of
14 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : * Lesser General Public License for more details.
16 : *
17 : * You should have received a copy of the GNU Lesser General Public
18 : * License along with this program; if not, see
19 : * <http://www.gnu.org/licenses/>.
20 : *
21 : * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
22 : */
23 :
24 : #include "config.h"
25 :
26 : #include "gkd-ssh-agent-process.h"
27 : #include "gkd-ssh-agent-private.h"
28 : #include "gkd-ssh-agent-util.h"
29 :
30 : #include <gio/gunixsocketaddress.h>
31 : #include <glib-unix.h>
32 : #include <glib/gstdio.h>
33 :
34 : enum {
35 : PROP_0,
36 : PROP_PATH
37 : };
38 :
39 : enum {
40 : CLOSED,
41 : LAST_SIGNAL
42 : };
43 :
44 : static guint signals[LAST_SIGNAL] = { 0 };
45 :
46 : struct _GkdSshAgentProcess
47 : {
48 : GObject object;
49 : gchar *path;
50 : gint output;
51 : GMutex lock;
52 : GPid pid;
53 : guint output_id;
54 : guint child_id;
55 : gboolean ready;
56 : };
57 :
58 0 : G_DEFINE_TYPE (GkdSshAgentProcess, gkd_ssh_agent_process, G_TYPE_OBJECT);
59 :
60 : static void
61 0 : gkd_ssh_agent_process_init (GkdSshAgentProcess *self)
62 : {
63 0 : self->output = -1;
64 0 : g_mutex_init (&self->lock);
65 0 : }
66 :
67 : static void
68 0 : gkd_ssh_agent_process_finalize (GObject *object)
69 : {
70 0 : GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (object);
71 :
72 0 : if (self->output != -1)
73 0 : close (self->output);
74 0 : if (self->output_id)
75 0 : g_source_remove (self->output_id);
76 0 : if (self->child_id)
77 0 : g_source_remove (self->child_id);
78 0 : if (self->pid)
79 0 : kill (self->pid, SIGTERM);
80 0 : g_unlink (self->path);
81 0 : g_free (self->path);
82 0 : g_mutex_clear (&self->lock);
83 :
84 0 : G_OBJECT_CLASS (gkd_ssh_agent_process_parent_class)->finalize (object);
85 0 : }
86 :
87 : static void
88 0 : gkd_ssh_agent_process_set_property (GObject *object,
89 : guint prop_id,
90 : const GValue *value,
91 : GParamSpec *pspec)
92 : {
93 0 : GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (object);
94 :
95 0 : switch (prop_id) {
96 0 : case PROP_PATH:
97 0 : self->path = g_value_dup_string (value);
98 0 : break;
99 0 : default:
100 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
101 0 : break;
102 : }
103 0 : }
104 :
105 : static void
106 0 : gkd_ssh_agent_process_class_init (GkdSshAgentProcessClass *klass)
107 : {
108 0 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
109 0 : gobject_class->finalize = gkd_ssh_agent_process_finalize;
110 0 : gobject_class->set_property = gkd_ssh_agent_process_set_property;
111 0 : g_object_class_install_property (gobject_class, PROP_PATH,
112 : g_param_spec_string ("path", "Path", "Path",
113 : "",
114 : G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
115 0 : signals[CLOSED] = g_signal_new_class_handler ("closed",
116 : G_TYPE_FROM_CLASS (klass),
117 : G_SIGNAL_RUN_LAST,
118 : NULL, NULL, NULL, NULL,
119 : G_TYPE_NONE, 0);
120 0 : }
121 :
122 : static void
123 0 : on_child_watch (GPid pid,
124 : gint status,
125 : gpointer user_data)
126 : {
127 0 : GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (user_data);
128 0 : GError *error = NULL;
129 :
130 0 : if (pid != self->pid)
131 0 : return;
132 :
133 0 : g_mutex_lock (&self->lock);
134 :
135 0 : self->pid = 0;
136 0 : self->output_id = 0;
137 0 : self->child_id = 0;
138 :
139 0 : if (!g_spawn_check_exit_status (status, &error)) {
140 0 : g_message ("ssh-agent: %s", error->message);
141 0 : g_error_free (error);
142 : }
143 :
144 0 : g_spawn_close_pid (pid);
145 :
146 0 : g_mutex_unlock (&self->lock);
147 :
148 0 : g_signal_emit (self, signals[CLOSED], 0);
149 : }
150 :
151 : static gboolean
152 0 : on_output_watch (gint fd,
153 : GIOCondition condition,
154 : gpointer user_data)
155 : {
156 0 : GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (user_data);
157 : guint8 buf[1024];
158 : gssize len;
159 :
160 0 : if (condition & G_IO_IN) {
161 0 : self->ready = TRUE;
162 :
163 0 : len = read (fd, buf, sizeof (buf));
164 0 : if (len < 0) {
165 0 : if (errno != EAGAIN && errno != EINTR)
166 0 : g_message ("couldn't read from ssh-agent stdout: %m");
167 0 : condition |= G_IO_ERR;
168 : }
169 : }
170 :
171 0 : if (condition & G_IO_HUP || condition & G_IO_ERR)
172 0 : return FALSE;
173 :
174 0 : return TRUE;
175 : }
176 :
177 : static gboolean
178 0 : agent_start_inlock (GkdSshAgentProcess *self,
179 : GError **error)
180 : {
181 0 : const gchar *argv[] = { SSH_AGENT, "-D", "-a", self->path, NULL };
182 : GPid pid;
183 :
184 0 : if (!g_spawn_async_with_pipes ("/", (gchar **)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
185 : NULL, NULL, &pid, NULL, &self->output, NULL, error))
186 0 : return FALSE;
187 :
188 0 : self->ready = FALSE;
189 0 : self->output_id = g_unix_fd_add (self->output,
190 : G_IO_IN | G_IO_HUP | G_IO_ERR,
191 : on_output_watch, self);
192 :
193 0 : self->pid = pid;
194 0 : self->child_id = g_child_watch_add (self->pid, on_child_watch, self);
195 :
196 0 : return TRUE;
197 : }
198 :
199 : static gboolean
200 0 : on_timeout (gpointer user_data)
201 : {
202 0 : gboolean *timedout = user_data;
203 0 : *timedout = TRUE;
204 0 : return TRUE;
205 : }
206 :
207 : GSocketConnection *
208 0 : gkd_ssh_agent_process_connect (GkdSshAgentProcess *self,
209 : GCancellable *cancellable,
210 : GError **error)
211 : {
212 0 : gboolean started = FALSE;
213 0 : gboolean timedout = FALSE;
214 : guint source;
215 : GSocketClient *client;
216 : GSocketAddress *address;
217 : GSocketConnection *connection;
218 :
219 0 : g_mutex_lock (&self->lock);
220 :
221 0 : if (self->pid == 0) {
222 0 : if (!agent_start_inlock (self, error)) {
223 0 : g_mutex_unlock (&self->lock);
224 0 : return NULL;
225 : }
226 0 : started = TRUE;
227 : }
228 :
229 0 : if (started && self->pid && !self->ready) {
230 0 : source = g_timeout_add_seconds (5, on_timeout, &timedout);
231 0 : while (self->pid && !self->ready && !timedout) {
232 0 : g_mutex_unlock (&self->lock);
233 0 : g_main_context_iteration (NULL, FALSE);
234 0 : g_mutex_lock (&self->lock);
235 : }
236 0 : g_source_remove (source);
237 : }
238 :
239 0 : if (!self->ready) {
240 0 : g_mutex_unlock (&self->lock);
241 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
242 : "ssh-agent process is not ready");
243 0 : return NULL;
244 : }
245 :
246 0 : address = g_unix_socket_address_new (self->path);
247 0 : client = g_socket_client_new ();
248 :
249 0 : connection = g_socket_client_connect (client,
250 0 : G_SOCKET_CONNECTABLE (address),
251 : cancellable,
252 : error);
253 0 : g_object_unref (address);
254 0 : g_object_unref (client);
255 :
256 0 : g_mutex_unlock (&self->lock);
257 :
258 0 : return connection;
259 : }
260 :
261 : GkdSshAgentProcess *
262 0 : gkd_ssh_agent_process_new (const gchar *path)
263 : {
264 0 : g_return_val_if_fail (path, NULL);
265 :
266 0 : return g_object_new (GKD_TYPE_SSH_AGENT_PROCESS, "path", path, NULL);
267 : }
268 :
269 : GPid
270 0 : gkd_ssh_agent_process_get_pid (GkdSshAgentProcess *self)
271 : {
272 0 : return self->pid;
273 : }
|