Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2014 Stefan Walter
3 : : *
4 : : * This program is free software; you can redistribute it and/or modify
5 : : * it under the terms of the GNU Lesser General Public License as
6 : : * published by the Free Software Foundation; either version 2.1 of
7 : : * the License, or (at your option) any later version.
8 : : *
9 : : * This program is distributed in the hope that it will be useful, but
10 : : * WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : : * Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with this program; if not, see <http://www.gnu.org/licenses/>.
16 : : *
17 : : * Auther: Stef Walter <stefw@gnome.org>
18 : : */
19 : :
20 : : #include "config.h"
21 : :
22 : : #include "gcr-ssh-askpass.h"
23 : :
24 : : #include <glib-unix.h>
25 : : #include <glib/gi18n.h>
26 : : #include <glib/gstdio.h>
27 : :
28 : : #include <sys/socket.h>
29 : : #include <sys/un.h>
30 : :
31 : : #include <errno.h>
32 : : #include <unistd.h>
33 : :
34 : : #define GCR_SSH_ASKPASS_BIN "gcr4-ssh-askpass"
35 : :
36 : : /* Used from tests to override location */
37 : : const char *gcr_ssh_askpass_executable = LIBEXECDIR "/" GCR_SSH_ASKPASS_BIN;
38 : :
39 : : /**
40 : : * GcrSshAskpass:
41 : : *
42 : : * When used as the setup function while spawning an ssh command like ssh-add
43 : : * or ssh, this allows callbacks for passwords on the provided interaction.
44 : : */
45 : :
46 : : enum {
47 : : PROP_0,
48 : : PROP_INTERACTION
49 : : };
50 : :
51 : : struct _GcrSshAskpass {
52 : : GObject parent;
53 : : GTlsInteraction *interaction;
54 : : gchar *directory;
55 : : gchar *socket;
56 : : guint source;
57 : : gint fd;
58 : : GCancellable *cancellable;
59 : : GMainContext *context;
60 : : };
61 : :
62 [ + + + - : 27 : G_DEFINE_TYPE (GcrSshAskpass, gcr_ssh_askpass, G_TYPE_OBJECT);
+ + ]
63 : :
64 : : static void
65 : 4 : gcr_ssh_askpass_init (GcrSshAskpass *self)
66 : : {
67 : 4 : self->cancellable = g_cancellable_new ();
68 : 4 : self->context = g_main_context_ref_thread_default ();
69 : 4 : }
70 : :
71 : : static void
72 : 4 : gcr_ssh_askpass_set_property (GObject *obj,
73 : : guint prop_id,
74 : : const GValue *value,
75 : : GParamSpec *pspec)
76 : : {
77 : 4 : GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
78 : :
79 [ + - ]: 4 : switch (prop_id) {
80 : 4 : case PROP_INTERACTION:
81 : 4 : self->interaction = g_value_dup_object (value);
82 [ - + ]: 4 : g_return_if_fail (self->interaction != NULL);
83 : 4 : break;
84 : 0 : default:
85 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
86 : 0 : break;
87 : : }
88 : : }
89 : :
90 : : static void
91 : 1 : gcr_ssh_askpass_get_property (GObject *obj,
92 : : guint prop_id,
93 : : GValue *value,
94 : : GParamSpec *pspec)
95 : : {
96 : 1 : GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
97 : :
98 [ + - ]: 1 : switch (prop_id) {
99 : 1 : case PROP_INTERACTION:
100 : 1 : g_value_set_object (value, gcr_ssh_askpass_get_interaction (self));
101 : 1 : break;
102 : 0 : default:
103 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
104 : 0 : break;
105 : : }
106 : 1 : }
107 : :
108 : : static gboolean
109 : 6 : write_all (gint fd,
110 : : const guchar *buf,
111 : : gsize len)
112 : : {
113 : 6 : guint all = len;
114 : : int res;
115 : :
116 [ + + ]: 12 : while (len > 0) {
117 : 6 : res = write (fd, buf, len);
118 [ - + ]: 6 : if (res <= 0) {
119 [ # # # # ]: 0 : if (errno == EAGAIN || errno == EINTR)
120 : 0 : continue;
121 [ # # ]: 0 : if (errno != EPIPE)
122 [ # # ]: 0 : g_warning ("couldn't write %u bytes to client: %s", all,
123 : : res < 0 ? g_strerror (errno) : "");
124 : 0 : return FALSE;
125 : : } else {
126 : 6 : len -= res;
127 : 6 : buf += res;
128 : : }
129 : : }
130 : 6 : return TRUE;
131 : : }
132 : :
133 : : static GString *
134 : 2 : read_all_into_string (gint fd)
135 : : {
136 : 2 : GString *input = g_string_new ("");
137 : : gsize len;
138 : : gssize ret;
139 : :
140 : : for (;;) {
141 : 4 : len = input->len;
142 : 4 : g_string_set_size (input, len + 256);
143 : 4 : ret = read (fd, input->str + len, 256);
144 [ - + ]: 4 : if (ret < 0) {
145 [ # # # # ]: 0 : if (errno != EINTR && errno != EAGAIN) {
146 : 0 : g_critical ("couldn't read from " GCR_SSH_ASKPASS_BIN ": %s", g_strerror (errno));
147 : 0 : g_string_free (input, TRUE);
148 : 0 : return NULL;
149 : : }
150 [ + + ]: 4 : } else if (ret == 0) {
151 : 2 : return input;
152 : : } else {
153 : 2 : input->len = len + ret;
154 : 2 : input->str[input->len] = '\0';
155 : : }
156 : : }
157 : : }
158 : :
159 : : typedef struct {
160 : : gint fd;
161 : : GTlsInteraction *interaction;
162 : : GCancellable *cancellable;
163 : : } AskpassContext;
164 : :
165 : : static gpointer
166 : 2 : askpass_thread (gpointer data)
167 : : {
168 : 2 : AskpassContext *ctx = data;
169 : 2 : gboolean success = FALSE;
170 : 2 : GTlsPassword *password = NULL;
171 : : GTlsInteractionResult res;
172 : 2 : GError *error = NULL;
173 : : const guchar *value;
174 : : GString *input;
175 : : gsize length;
176 : :
177 : 2 : input = read_all_into_string (ctx->fd);
178 [ - + ]: 2 : if (!input)
179 : 0 : goto out;
180 : :
181 [ - + ]: 2 : if (input->len == 0)
182 : 0 : g_string_append (input, _("Enter your OpenSSH passphrase"));
183 : :
184 : 2 : g_debug ("asking for ssh-askpass password: %s", input->str);
185 : :
186 : 2 : password = g_tls_password_new (G_TLS_PASSWORD_NONE, input->str);
187 : 2 : res = g_tls_interaction_invoke_ask_password (ctx->interaction, password, ctx->cancellable, &error);
188 : :
189 : 2 : g_debug ("ask password returned %d", res);
190 : :
191 : 2 : success = FALSE;
192 [ + + ]: 2 : if (res == G_TLS_INTERACTION_HANDLED) {
193 : 1 : value = g_tls_password_get_value (password, &length);
194 [ + - ]: 1 : if (write_all (ctx->fd, (const guchar *)value, length))
195 : 1 : g_debug ("password written to " GCR_SSH_ASKPASS_BIN);
196 : : else
197 : 0 : g_message ("failed to write password to " GCR_SSH_ASKPASS_BIN);
198 : 1 : success = TRUE;
199 [ - + - - ]: 1 : } else if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
200 : 0 : g_warning ("couldn't prompt for password: %s", error->message);
201 : : } else {
202 : 1 : g_debug ("unhandled or cancelled ask password");
203 : : }
204 : :
205 : 2 : out:
206 [ + + ]: 2 : if (!success) {
207 : 1 : g_debug ("writing failure to " GCR_SSH_ASKPASS_BIN);
208 : 1 : write_all (ctx->fd, (const guchar *)"\xff", 1);
209 : : }
210 [ + - ]: 2 : if (password)
211 : 2 : g_object_unref (password);
212 [ + - ]: 2 : if (input)
213 : 2 : g_string_free (input, TRUE);
214 : 2 : g_clear_error (&error);
215 : :
216 : 2 : g_close (ctx->fd, NULL);
217 : 2 : g_object_unref (ctx->interaction);
218 : 2 : g_object_unref (ctx->cancellable);
219 : 2 : g_free (ctx);
220 : :
221 : 2 : return NULL;
222 : : }
223 : :
224 : : static gboolean
225 : 2 : askpass_accept (gint fd,
226 : : GIOCondition cond,
227 : : gpointer user_data)
228 : : {
229 : 2 : GcrSshAskpass *self = user_data;
230 : : AskpassContext *ctx;
231 : : struct sockaddr_un addr;
232 : : socklen_t addrlen;
233 : : GThread *thread;
234 : : gint new_fd;
235 : :
236 : 2 : addrlen = sizeof (addr);
237 : 2 : new_fd = accept (fd, (struct sockaddr *) &addr, &addrlen);
238 [ - + ]: 2 : if (new_fd < 0) {
239 [ # # # # ]: 0 : if (errno != EAGAIN && errno != EINTR)
240 : 0 : g_warning ("couldn't accept new control request: %s", g_strerror (errno));
241 : 0 : return TRUE;
242 : : }
243 : :
244 : 2 : g_debug ("accepted new connection from " GCR_SSH_ASKPASS_BIN);
245 : :
246 : 2 : ctx = g_new0 (AskpassContext, 1);
247 : 2 : ctx->fd = new_fd;
248 : 2 : ctx->interaction = g_object_ref (self->interaction);
249 : 2 : ctx->cancellable = g_object_ref (self->cancellable);
250 : :
251 : 2 : thread = g_thread_new ("ssh-askpass", askpass_thread, ctx);
252 : 2 : g_thread_unref (thread);
253 : :
254 : 2 : return TRUE;
255 : : }
256 : :
257 : : static void
258 : 4 : gcr_ssh_askpass_constructed (GObject *obj)
259 : : {
260 : 4 : GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
261 : : struct sockaddr_un addr;
262 : :
263 : 4 : G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->constructed (obj);
264 : :
265 : 4 : self->directory = g_build_filename (g_get_user_runtime_dir (), "ssh-askpass.XXXXXX", NULL);
266 [ - + ]: 4 : if (!g_mkdtemp_full (self->directory, 0700)) {
267 : 0 : g_warning ("couldn't create temporary directory: %s: %s", self->directory, g_strerror (errno));
268 : 0 : return;
269 : : }
270 : :
271 : 4 : self->socket = g_build_filename (self->directory, "socket", NULL);
272 : :
273 : 4 : self->fd = socket (AF_UNIX, SOCK_STREAM, 0);
274 [ - + ]: 4 : if (self->fd < 0) {
275 : 0 : g_warning ("couldn't open socket: %s", g_strerror (errno));
276 : 0 : return;
277 : : }
278 : :
279 [ - + ]: 4 : if (!g_unix_set_fd_nonblocking (self->fd, TRUE, NULL))
280 : 0 : g_return_if_reached ();
281 : :
282 : 4 : memset (&addr, 0, sizeof (addr));
283 : 4 : addr.sun_family = AF_UNIX;
284 : 4 : g_strlcpy (addr.sun_path, self->socket, sizeof (addr.sun_path));
285 [ - + ]: 4 : if (bind (self->fd, (struct sockaddr*) &addr, sizeof (addr)) < 0) {
286 : 0 : g_warning ("couldn't bind to askpass socket: %s: %s", self->socket, g_strerror (errno));
287 : 0 : return;
288 : : }
289 : :
290 [ - + ]: 4 : if (listen (self->fd, 128) < 0) {
291 : 0 : g_warning ("couldn't listen on askpass socket: %s: %s", self->socket, g_strerror (errno));
292 : 0 : return;
293 : : }
294 : :
295 : 4 : g_debug ("listening for " GCR_SSH_ASKPASS_BIN " at: %s", self->socket);
296 : :
297 : 4 : self->source = g_unix_fd_add (self->fd, G_IO_IN, askpass_accept, self);
298 : : }
299 : :
300 : : static void
301 : 4 : gcr_ssh_askpass_dispose (GObject *obj)
302 : : {
303 : 4 : GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
304 : :
305 : 4 : g_cancellable_cancel (self->cancellable);
306 : :
307 [ + - ]: 4 : if (self->source) {
308 : 4 : g_source_remove (self->source);
309 : 4 : self->source = 0;
310 : : }
311 : :
312 [ + - ]: 4 : if (self->fd >= 0) {
313 : 4 : g_close (self->fd, NULL);
314 : 4 : self->fd = -1;
315 : : }
316 : :
317 [ + - ]: 4 : if (self->socket) {
318 : 4 : g_unlink (self->socket);
319 : 4 : g_free (self->socket);
320 : 4 : self->socket = NULL;
321 : : }
322 : :
323 [ + - ]: 4 : if (self->directory) {
324 : 4 : g_rmdir (self->directory);
325 : 4 : g_free (self->directory);
326 : 4 : self->directory = NULL;
327 : : }
328 : :
329 [ + - ]: 4 : if (self->interaction) {
330 : 4 : g_object_unref (self->interaction);
331 : 4 : self->interaction = NULL;
332 : : }
333 : :
334 : 4 : G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->dispose (obj);
335 : 4 : }
336 : :
337 : : static void
338 : 4 : gcr_ssh_askpass_finalize (GObject *obj)
339 : : {
340 : 4 : GcrSshAskpass *self = GCR_SSH_ASKPASS (obj);
341 : :
342 : 4 : g_object_unref (self->cancellable);
343 : 4 : g_main_context_unref (self->context);
344 : :
345 : 4 : G_OBJECT_CLASS (gcr_ssh_askpass_parent_class)->finalize (obj);
346 : 4 : }
347 : :
348 : : /**
349 : : * gcr_ssh_askpass_new:
350 : : * @interaction: the interaction to use for prompting paswords
351 : : *
352 : : * Create a new GcrSshAskpass object which can be used to spawn an
353 : : * ssh command and prompt for any necessary passwords.
354 : : *
355 : : * Use the gcr_ssh_askpass_child_setup() function as a callback with
356 : : * g_spawn_sync(), g_spawn_async() or g_spawn_async_with_pipes().
357 : : *
358 : : * Returns: (transfer full): A new #GcrSshAskpass object
359 : : */
360 : : GcrSshAskpass *
361 : 4 : gcr_ssh_askpass_new (GTlsInteraction *interaction)
362 : : {
363 [ - + + - : 4 : g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), NULL);
- + - + ]
364 : 4 : return g_object_new (GCR_TYPE_SSH_ASKPASS,
365 : : "interaction", interaction,
366 : : NULL);
367 : : }
368 : :
369 : : /**
370 : : * gcr_ssh_askpass_get_interaction:
371 : : * @self: a #GcrSshAskpass object
372 : : *
373 : : * Get the interaction associated with this object.
374 : : *
375 : : * Returns: (transfer none): the interaction
376 : : */
377 : : GTlsInteraction *
378 : 2 : gcr_ssh_askpass_get_interaction (GcrSshAskpass *self)
379 : : {
380 [ - + ]: 2 : g_return_val_if_fail (GCR_IS_SSH_ASKPASS (self), NULL);
381 : 2 : return self->interaction;
382 : : }
383 : :
384 : : /**
385 : : * gcr_ssh_askpass_child_setup:
386 : : * @askpass: a #GcrSshAskpass object
387 : : *
388 : : * Use this function as a callback setup function passed to g_spawn_sync(),
389 : : * g_spawn_async(), g_spawn_async_with_pipes().
390 : : */
391 : : void
392 : 0 : gcr_ssh_askpass_child_setup (gpointer askpass)
393 : : {
394 : 0 : GcrSshAskpass *self = askpass;
395 : :
396 : 0 : g_setenv ("SSH_ASKPASS", gcr_ssh_askpass_executable, TRUE);
397 : :
398 : : /* ssh wants DISPLAY set in order to use SSH_ASKPASS */
399 [ # # ]: 0 : if (!g_getenv ("DISPLAY"))
400 : 0 : g_setenv ("DISPLAY", "x", TRUE);
401 : :
402 : : /* For communicating back with ourselves */
403 [ # # ]: 0 : if (self->socket)
404 : 0 : g_setenv ("GCR_SSH_ASKPASS_SOCKET", self->socket, TRUE);
405 : :
406 : : /* Close the control terminal */
407 : 0 : setsid ();
408 : 0 : }
409 : :
410 : : static void
411 : 2 : gcr_ssh_askpass_class_init (GcrSshAskpassClass *klass)
412 : : {
413 : 2 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
414 : :
415 : 2 : gobject_class->get_property = gcr_ssh_askpass_get_property;
416 : 2 : gobject_class->set_property = gcr_ssh_askpass_set_property;
417 : 2 : gobject_class->constructed = gcr_ssh_askpass_constructed;
418 : 2 : gobject_class->dispose = gcr_ssh_askpass_dispose;
419 : 2 : gobject_class->finalize = gcr_ssh_askpass_finalize;
420 : :
421 : : /**
422 : : * GcrSshAskpass:interaction:
423 : : *
424 : : * The interaction used to prompt for passwords.
425 : : */
426 : 2 : g_object_class_install_property (gobject_class, PROP_INTERACTION,
427 : : g_param_spec_object ("interaction", "Interaction", "Interaction",
428 : : G_TYPE_TLS_INTERACTION,
429 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
430 : 2 : }
431 : :
432 : : #ifdef GCR_SSH_ASKPASS_TOOL
433 : :
434 : : #include "egg/egg-secure-memory.h"
435 : :
436 : 8 : EGG_SECURE_DEFINE_GLIB_GLOBALS ();
437 : 2 : EGG_SECURE_DECLARE ("ssh-askpass");
438 : :
439 : : int
440 : 2 : main (int argc,
441 : : char *argv[])
442 : : {
443 : : GString *message;
444 : : struct sockaddr_un addr;
445 : : const gchar *path;
446 : : guchar *buf;
447 : : gint count;
448 : : gint i;
449 : : int ret;
450 : : int fd;
451 : :
452 : 2 : path = g_getenv ("GCR_SSH_ASKPASS_SOCKET");
453 [ - + ]: 2 : if (path == NULL) {
454 : 0 : g_printerr (GCR_SSH_ASKPASS_BIN ": this program is not meant to be run directly");
455 : 0 : return 2;
456 : : }
457 : :
458 : 2 : fd = socket (AF_UNIX, SOCK_STREAM, 0);
459 [ - + ]: 2 : if (fd < 0) {
460 : 0 : g_warning ("couldn't open socket: %s", g_strerror (errno));
461 : 0 : return -1;
462 : : }
463 : :
464 : 2 : memset (&addr, 0, sizeof (addr));
465 : 2 : addr.sun_family = AF_UNIX;
466 : 2 : g_strlcpy (addr.sun_path, path, sizeof (addr.sun_path));
467 [ - + ]: 2 : if (connect (fd, (struct sockaddr*) &addr, sizeof (addr)) < 0) {
468 : 0 : g_warning ("couldn't connect to askpass socket: %s: %s", path, g_strerror (errno));
469 : 0 : return -1;
470 : : }
471 : :
472 : 2 : message = g_string_new ("");
473 [ + - ]: 2 : if (argc > 1) {
474 [ + + ]: 4 : for (i = 1; i < argc; i++) {
475 [ + - ]: 2 : if (i == 1)
476 : 2 : g_string_append_c (message, ' ');
477 : 2 : g_string_append (message, argv[i]);
478 : : }
479 : : }
480 : :
481 [ - + ]: 2 : if (!write_all (fd, (const guchar *)message->str, message->len)) {
482 : 0 : g_string_free (message, TRUE);
483 : 0 : return -1;
484 : : }
485 : 2 : g_string_free (message, TRUE);
486 : :
487 [ - + ]: 2 : if (shutdown (fd, SHUT_WR) < 0) {
488 : 0 : g_warning ("couldn't shutdown socket: %s", g_strerror (errno));
489 : 0 : return -1;
490 : : }
491 : :
492 : 2 : count = 0;
493 : 2 : buf = egg_secure_alloc (128);
494 : :
495 : : for (;;) {
496 : 4 : ret = read (fd, buf, 128);
497 [ - + ]: 4 : if (ret < 0) {
498 [ # # # # ]: 0 : if (errno != EINTR && errno != EAGAIN) {
499 [ # # ]: 0 : if (errno != ECONNRESET) {
500 : 0 : g_critical ("couldn't read from ssh-askpass socket: %s",
501 : : g_strerror (errno));
502 : : }
503 : 0 : egg_secure_free (buf);
504 : 0 : return -1;
505 : : }
506 : 0 : ret = 0;
507 [ + + ]: 4 : } else if (ret == 0) {
508 : 2 : break;
509 [ - + ]: 2 : } else if (!write_all (1, buf, ret)) {
510 : 0 : egg_secure_free (buf);
511 : 0 : return -1;
512 : : }
513 : 2 : count += ret;
514 : : }
515 : :
516 [ + + + - ]: 2 : if (count == 1 && buf[0] == 0xff) {
517 : 1 : egg_secure_free (buf);
518 : 1 : return -1;
519 : : }
520 : :
521 : 1 : egg_secure_free (buf);
522 : 1 : return 0;
523 : : }
524 : :
525 : : #endif /* GCR_SSH_ASKPASS_TOOL */
|