Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-sftp-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <glib/gi18n.h>
9 : : #include <gio/gio.h>
10 : : #include <json-glib/json-glib.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-sftp-plugin.h"
14 : :
15 : :
16 : : typedef struct _ValentSftpSession ValentSftpSession;
17 : :
18 : : struct _ValentSftpPlugin
19 : : {
20 : : ValentDevicePlugin parent_instance;
21 : :
22 : : GDBusConnection *connection;
23 : : GVolumeMonitor *monitor;
24 : : ValentSftpSession *session;
25 : : unsigned int privkey_ready : 1;
26 : : };
27 : :
28 : : static void valent_sftp_plugin_mount_volume (ValentSftpPlugin *self);
29 : : static void valent_sftp_plugin_unmount_volume (ValentSftpPlugin *self);
30 : : static void valent_sftp_plugin_update_menu (ValentSftpPlugin *self);
31 : :
32 [ + + + - ]: 59 : G_DEFINE_FINAL_TYPE (ValentSftpPlugin, valent_sftp_plugin, VALENT_TYPE_DEVICE_PLUGIN)
33 : :
34 : : /*< private >
35 : : * get_device_host:
36 : : * @self: a `ValentSftpPlugin`
37 : : *
38 : : * Try to find a IP-based device channel with a host.
39 : : *
40 : : * Returns: (nullable) (transfer full): a hostname or IP address
41 : : */
42 : : static char *
43 : 7 : get_device_host (ValentSftpPlugin *self)
44 : : {
45 : 7 : ValentDevice *device;
46 : 7 : char *host = NULL;
47 : :
48 [ - + ]: 7 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
49 : :
50 : 7 : device = valent_resource_get_source (VALENT_RESOURCE (self));
51 [ + - ]: 7 : if (device != NULL)
52 : : {
53 : 7 : GListModel *channels;
54 : 7 : unsigned int n_channels;
55 : :
56 : 7 : channels = valent_device_get_channels (device);
57 : 7 : n_channels = g_list_model_get_n_items (channels);
58 [ + + ]: 7 : for (unsigned int i = 0; i < n_channels; i++)
59 : : {
60 : 4 : g_autoptr (ValentChannel) channel = NULL;
61 : 4 : GParamSpec *pspec = NULL;
62 : :
63 : 4 : channel = g_list_model_get_item (channels, i);
64 : 4 : pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (channel),
65 : : "host");
66 [ + - ]: 4 : if (pspec != NULL)
67 : : {
68 : 4 : g_object_get (channel, "host", &host, NULL);
69 : 4 : break;
70 : : }
71 : : }
72 : : }
73 : :
74 : 7 : return g_steal_pointer (&host);
75 : : }
76 : :
77 : : /**
78 : : * ValentSftpSession:
79 : : * @host: Host name or IP
80 : : * @port: Port
81 : : * @username: Username (deprecated)
82 : : * @password: Password (deprecated)
83 : : * @mount: A `GMount` for the session
84 : : *
85 : : * `ValentSftpSession` is a simple representation of a SFTP session.
86 : : */
87 : : typedef struct _ValentSftpSession
88 : : {
89 : : char *host;
90 : : uint16_t port;
91 : : char *username;
92 : : char *password;
93 : : GHashTable *paths;
94 : :
95 : : /* Gvfs state */
96 : : GMount *mount;
97 : : char *uri;
98 : : } ValentSftpSession;
99 : :
100 : :
101 : : static ValentSftpSession *
102 : 0 : sftp_session_new (ValentSftpPlugin *self,
103 : : JsonNode *packet)
104 : : {
105 : 0 : ValentSftpSession *session;
106 : 0 : g_autofree char *host = NULL;
107 : 0 : int64_t port;
108 : 0 : const char *password;
109 : 0 : const char *username;
110 : 0 : JsonArray *multi_paths = NULL;
111 : 0 : JsonArray *path_names = NULL;
112 : :
113 : : /* Ultimately, these are the only packet fields we really need */
114 [ # # ]: 0 : if (!valent_packet_get_int (packet, "port", &port) ||
115 [ # # ]: 0 : (port < 0 || port > G_MAXUINT16))
116 : : {
117 : 0 : g_debug ("%s(): expected \"port\" field holding a uint16",
118 : : G_STRFUNC);
119 : 0 : return NULL;
120 : : }
121 : :
122 [ # # ]: 0 : if ((host = get_device_host (self)) == NULL)
123 : : {
124 : 0 : g_warning ("%s(): failed to get host address",
125 : : G_STRFUNC);
126 : 0 : return NULL;
127 : : }
128 : :
129 : : // Create a session struct
130 : 0 : session = g_new0 (ValentSftpSession, 1);
131 : 0 : session->host = g_steal_pointer (&host);
132 : 0 : session->port = (uint16_t)port;
133 : 0 : session->paths = g_hash_table_new_full (g_str_hash, g_str_equal,
134 : : g_free, g_free);
135 : 0 : session->uri = g_strdup_printf ("sftp://%s:%u/", session->host, session->port);
136 : :
137 [ # # ]: 0 : if (valent_packet_get_string (packet, "user", &username))
138 [ # # ]: 0 : session->username = g_strdup (username);
139 : :
140 [ # # ]: 0 : if (valent_packet_get_string (packet, "password", &password))
141 [ # # ]: 0 : session->password = g_strdup (password);
142 : :
143 [ # # # # ]: 0 : if (valent_packet_get_array (packet, "multiPaths", &multi_paths) &&
144 : 0 : valent_packet_get_array (packet, "pathNames", &path_names))
145 : : {
146 : 0 : unsigned int n_paths = json_array_get_length (multi_paths);
147 : 0 : unsigned int n_names = json_array_get_length (path_names);
148 : :
149 [ # # ]: 0 : for (unsigned int i = 0; i < n_paths && i < n_names; i++)
150 : : {
151 : 0 : const char *path = json_array_get_string_element (multi_paths, i);
152 : 0 : const char *name = json_array_get_string_element (path_names, i);
153 : 0 : g_autofree char *uri = NULL;
154 : :
155 : 0 : uri = g_strdup_printf ("sftp://%s:%u%s",
156 : : session->host,
157 : : session->port,
158 : : path);
159 [ # # ]: 0 : g_hash_table_replace (session->paths,
160 : : g_steal_pointer (&uri),
161 : 0 : g_strdup (name));
162 : : }
163 : : }
164 : :
165 : : return session;
166 : : }
167 : :
168 : : static void
169 : 0 : sftp_session_free (gpointer data)
170 : : {
171 : 0 : ValentSftpSession *session = data;
172 : :
173 [ # # ]: 0 : g_clear_pointer (&session->host, g_free);
174 [ # # ]: 0 : g_clear_pointer (&session->username, g_free);
175 [ # # ]: 0 : g_clear_pointer (&session->password, g_free);
176 [ # # ]: 0 : g_clear_pointer (&session->paths, g_hash_table_unref);
177 : :
178 [ # # ]: 0 : g_clear_object (&session->mount);
179 [ # # ]: 0 : g_clear_pointer (&session->uri, g_free);
180 : :
181 : 0 : g_free (session);
182 : 0 : }
183 : :
184 : : static void
185 : 0 : sftp_session_end_cb (GMount *mount,
186 : : GAsyncResult *result,
187 : : gpointer user_data)
188 : : {
189 : 0 : g_autoptr (GError) error = NULL;
190 : :
191 [ # # ]: 0 : if (!g_mount_unmount_with_operation_finish (mount, result, &error))
192 : 0 : g_debug ("Failed unmounting: %s", error->message);
193 : 0 : }
194 : :
195 : : static void
196 : 0 : sftp_session_end (gpointer data)
197 : : {
198 : 0 : ValentSftpSession *session = data;
199 : 0 : g_autoptr (GMount) mount = NULL;
200 : 0 : g_autoptr (GMountOperation) op = NULL;
201 : :
202 : 0 : mount = g_steal_pointer (&session->mount);
203 : 0 : sftp_session_free (session);
204 : :
205 [ # # ]: 0 : if (mount == NULL)
206 : : return;
207 : :
208 : 0 : op = g_mount_operation_new ();
209 [ # # ]: 0 : g_mount_unmount_with_operation (mount,
210 : : G_MOUNT_UNMOUNT_FORCE,
211 : : op,
212 : : NULL,
213 : : (GAsyncReadyCallback)sftp_session_end_cb,
214 : : NULL);
215 : : }
216 : :
217 : : static gboolean
218 : 7 : sftp_session_find (ValentSftpPlugin *self)
219 : : {
220 : 14 : g_autofree char *host = NULL;
221 : 7 : g_autofree char *host_pattern = NULL;
222 : 7 : g_autoptr (GRegex) regex = NULL;
223 [ + + ]: 7 : g_autolist (GMount) mounts = NULL;
224 : :
225 [ - + - - ]: 7 : if (self->session && self->session->mount)
226 : : return TRUE;
227 : :
228 [ + + ]: 7 : if ((host = get_device_host (self)) == NULL)
229 : : return FALSE;
230 : :
231 : : // TODO: is this reasonable?
232 : 4 : host_pattern = g_strdup_printf ("sftp://(%s):([22-65535])", host);
233 : 4 : regex = g_regex_new (host_pattern, G_REGEX_OPTIMIZE, 0, NULL);
234 : :
235 : : /* Search through each mount in the volume monitor... */
236 : 4 : mounts = g_volume_monitor_get_mounts (self->monitor);
237 : :
238 [ - + ]: 4 : for (const GList *iter = mounts; iter; iter = iter->next)
239 : : {
240 : 7 : g_autoptr (GFile) root = NULL;
241 [ # # # # ]: 0 : g_autofree char *uri = NULL;
242 : :
243 : 0 : root = g_mount_get_root (iter->data);
244 : 0 : uri = g_file_get_uri (root);
245 : :
246 : : /* The URI matches our mount */
247 [ # # ]: 0 : if (g_regex_match (regex, uri, 0, NULL))
248 : : {
249 [ # # ]: 0 : if (self->session == NULL)
250 : 0 : self->session = g_new0 (ValentSftpSession, 1);
251 : :
252 : 0 : g_set_object (&self->session->mount, iter->data);
253 : 0 : g_set_str (&self->session->uri, uri);
254 : :
255 : 0 : valent_sftp_plugin_update_menu (self);
256 : 0 : return TRUE;
257 : : }
258 : : }
259 : :
260 : : return FALSE;
261 : : }
262 : :
263 : :
264 : : /*
265 : : * GVolumeMonitor Callbacks
266 : : */
267 : : static void
268 : 0 : on_mount_added (GVolumeMonitor *volume_monitor,
269 : : GMount *mount,
270 : : ValentSftpPlugin *self)
271 : : {
272 : 0 : g_autoptr (GFile) root = NULL;
273 [ # # ]: 0 : g_autofree char *uri = NULL;
274 : :
275 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
276 : :
277 [ # # ]: 0 : if (self->session == NULL)
278 : 0 : return;
279 : :
280 : 0 : root = g_mount_get_root (mount);
281 : 0 : uri = g_file_get_uri (root);
282 : :
283 [ # # ]: 0 : if (g_strcmp0 (self->session->uri, uri) == 0)
284 : : {
285 : 0 : g_set_object (&self->session->mount, mount);
286 : 0 : valent_sftp_plugin_update_menu (self);
287 : : }
288 : : }
289 : :
290 : : static void
291 : 0 : on_mount_removed (GVolumeMonitor *volume_monitor,
292 : : GMount *mount,
293 : : ValentSftpPlugin *self)
294 : : {
295 : 0 : g_autoptr (GFile) root = NULL;
296 [ # # ]: 0 : g_autofree char *uri = NULL;
297 : :
298 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
299 : :
300 [ # # ]: 0 : if (self->session == NULL)
301 : 0 : return;
302 : :
303 : 0 : root = g_mount_get_root (mount);
304 : 0 : uri = g_file_get_uri (root);
305 : :
306 [ # # ]: 0 : if (g_strcmp0 (self->session->uri, uri) == 0)
307 : : {
308 [ # # ]: 0 : g_clear_object (&self->session->mount);
309 : 0 : valent_sftp_plugin_update_menu (self);
310 : : }
311 : : }
312 : :
313 : : /**
314 : : * remove_host_key:
315 : : * @host: An IP or hostname
316 : : *
317 : : * Remove all host keys associated with @host from the ssh-agent.
318 : : */
319 : : static void
320 : 0 : remove_host_key (const char *host)
321 : : {
322 [ # # ]: 0 : g_assert (host != NULL);
323 : :
324 [ # # ]: 0 : for (uint16_t port = 1739; port <= 1764; port++)
325 : : {
326 : 0 : g_autoptr (GSubprocess) proc = NULL;
327 [ # # ]: 0 : g_autofree char *match = NULL;
328 : :
329 : 0 : match = g_strdup_printf ("[%s]:%d", host, port);
330 : 0 : proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE |
331 : : G_SUBPROCESS_FLAGS_STDERR_MERGE,
332 : : NULL,
333 : : "ssh-keygen", "-R", match,
334 : : NULL);
335 : :
336 [ # # ]: 0 : if (proc != NULL)
337 : 0 : g_subprocess_wait_async (proc, NULL, NULL, NULL);
338 : : }
339 : 0 : }
340 : :
341 : : /*
342 : : * GMountOperation
343 : : */
344 : : static void
345 : 0 : ask_password_cb (GMountOperation *operation,
346 : : char *message,
347 : : char *default_user,
348 : : char *default_domain,
349 : : GAskPasswordFlags flags,
350 : : ValentSftpPlugin *self)
351 : : {
352 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
353 [ # # ]: 0 : g_return_if_fail (self->session != NULL);
354 : :
355 : : /* The username/password are only set in response to a request, to prefer
356 : : * public key authentication when possible.
357 : : */
358 [ # # ]: 0 : if ((flags & G_ASK_PASSWORD_NEED_USERNAME) != 0)
359 : 0 : g_mount_operation_set_username (operation, self->session->username);
360 : :
361 [ # # ]: 0 : if ((flags & G_ASK_PASSWORD_NEED_PASSWORD) != 0)
362 : : {
363 : 0 : g_mount_operation_set_password (operation, self->session->password);
364 : 0 : g_mount_operation_set_password_save (operation, G_PASSWORD_SAVE_NEVER);
365 : : }
366 : :
367 : 0 : g_mount_operation_reply (operation, G_MOUNT_OPERATION_HANDLED);
368 : : }
369 : :
370 : : static void
371 : 0 : ask_question_cb (GMountOperation *operation,
372 : : char *message,
373 : : GStrv choices,
374 : : gpointer user_data)
375 : : {
376 : : /* Host keys are automatically accepted, since we use the host address of
377 : : * the authenticated device connection.
378 : : */
379 : 0 : g_mount_operation_reply (operation, G_MOUNT_OPERATION_HANDLED);
380 : 0 : }
381 : :
382 : : static void
383 : 0 : g_file_mount_enclosing_volume_cb (GFile *file,
384 : : GAsyncResult *result,
385 : : gpointer user_data)
386 : : {
387 : 0 : g_autoptr (ValentSftpPlugin) self = VALENT_SFTP_PLUGIN (g_steal_pointer (&user_data));
388 : 0 : g_autoptr (GError) error = NULL;
389 : :
390 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
391 [ # # ]: 0 : g_return_if_fail (self->session != NULL);
392 : :
393 : : /* On success we will acquire the mount from the volume monitor */
394 [ # # ]: 0 : if (g_file_mount_enclosing_volume_finish (file, result, &error))
395 : : return;
396 : :
397 : : /* On the off-chance this happens, we will just ensure we have the mount */
398 [ # # # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED) &&
399 : 0 : sftp_session_find (self))
400 : : return;
401 : :
402 : : /* On failure, we're particularly interested in host key failures so that
403 : : * we can remove those from the ssh-agent. These are reported by GVfs as
404 : : * G_IO_ERROR_FAILED with a localized string, so we just assume. */
405 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED))
406 : : {
407 : 0 : g_warning ("%s(): Error mounting: %s", G_STRFUNC, error->message);
408 : :
409 [ # # # # ]: 0 : if (self->session && self->session->host)
410 : 0 : remove_host_key (self->session->host);
411 : : }
412 : :
413 [ # # # # ]: 0 : g_clear_pointer (&self->session, sftp_session_free);
414 : : }
415 : :
416 : : static void
417 : 0 : valent_sftp_plugin_mount_volume (ValentSftpPlugin *self)
418 : : {
419 : 0 : g_autoptr (GFile) file = NULL;
420 [ # # ]: 0 : g_autoptr (GMountOperation) operation = NULL;
421 [ # # ]: 0 : g_autoptr (GCancellable) destroy = NULL;
422 : :
423 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
424 [ # # ]: 0 : g_return_if_fail (self->session != NULL);
425 : :
426 : 0 : file = g_file_new_for_uri (self->session->uri);
427 : 0 : operation = g_mount_operation_new ();
428 : 0 : g_signal_connect_object (operation,
429 : : "ask-password",
430 : : G_CALLBACK (ask_password_cb),
431 : : self,
432 : : G_CONNECT_DEFAULT);
433 : 0 : g_signal_connect_object (operation,
434 : : "ask-question",
435 : : G_CALLBACK (ask_question_cb),
436 : : self,
437 : : G_CONNECT_DEFAULT);
438 : :
439 : 0 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
440 [ # # ]: 0 : g_file_mount_enclosing_volume (file,
441 : : G_MOUNT_MOUNT_NONE,
442 : : operation,
443 : : destroy,
444 : : (GAsyncReadyCallback)g_file_mount_enclosing_volume_cb,
445 : : g_object_ref (self));
446 : :
447 : : }
448 : :
449 : : static void
450 : 0 : g_mount_unmount_with_operation_cb (GMount *mount,
451 : : GAsyncResult *result,
452 : : gpointer user_data)
453 : : {
454 : 0 : g_autoptr (GError) error = NULL;
455 : :
456 [ # # ]: 0 : if (!g_mount_unmount_with_operation_finish (mount, result, &error))
457 : : {
458 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
459 : 0 : g_warning ("%s(): Error unmounting: %s", G_STRFUNC, error->message);
460 : :
461 [ # # ]: 0 : return;
462 : : }
463 : : }
464 : :
465 : : static void
466 : 0 : valent_sftp_plugin_unmount_volume (ValentSftpPlugin *self)
467 : : {
468 : 0 : g_autoptr (GMountOperation) operation = NULL;
469 [ # # ]: 0 : g_autoptr (GCancellable) destroy = NULL;
470 : :
471 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
472 [ # # ]: 0 : g_return_if_fail (self->session != NULL);
473 : :
474 [ # # ]: 0 : if (self->session->mount == NULL)
475 : : return;
476 : :
477 : 0 : operation = g_mount_operation_new ();
478 : 0 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
479 [ # # ]: 0 : g_mount_unmount_with_operation (self->session->mount,
480 : : G_MOUNT_UNMOUNT_NONE,
481 : : operation,
482 : : destroy,
483 : : (GAsyncReadyCallback)g_mount_unmount_with_operation_cb,
484 : : NULL);
485 : : }
486 : :
487 : : static void
488 : 0 : valent_sftp_plugin_register_privkey_cb (GSubprocess *proc,
489 : : GAsyncResult *result,
490 : : gpointer user_data)
491 : : {
492 : 0 : g_autoptr (ValentSftpPlugin) self = VALENT_SFTP_PLUGIN (g_steal_pointer (&user_data));
493 : 0 : g_autoptr (GError) error = NULL;
494 : :
495 [ # # ]: 0 : if (!g_subprocess_wait_check_finish (proc, result, &error))
496 : : {
497 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
498 : : {
499 : 0 : g_warning ("%s(): Failed to register private key: %s",
500 : : G_STRFUNC,
501 : : error->message);
502 : : }
503 : :
504 [ # # ]: 0 : g_clear_pointer (&self->session, sftp_session_free);
505 [ # # ]: 0 : return;
506 : : }
507 : :
508 : 0 : self->privkey_ready = TRUE;
509 [ # # ]: 0 : valent_sftp_plugin_mount_volume (self);
510 : : }
511 : :
512 : : static void
513 : 0 : valent_sftp_plugin_register_privkey (ValentSftpPlugin *self)
514 : : {
515 : 0 : g_autoptr (ValentContext) context = NULL;
516 [ # # # # ]: 0 : g_autoptr (GSubprocess) proc = NULL;
517 [ # # ]: 0 : g_autoptr (GFile) private_key = NULL;
518 [ # # # # ]: 0 : g_autoptr (GCancellable) destroy = NULL;
519 [ # # # # ]: 0 : g_autoptr (GError) error = NULL;
520 : :
521 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
522 [ # # ]: 0 : g_return_if_fail (self->session != NULL);
523 : :
524 : : /* Get the root context and add the private key to the ssh-agent */
525 : 0 : context = valent_context_new (NULL, NULL, NULL);
526 : 0 : private_key = valent_context_get_config_file (context, "private.pem");
527 : 0 : proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE |
528 : : G_SUBPROCESS_FLAGS_STDERR_MERGE,
529 : : &error,
530 : : "ssh-add", g_file_peek_path (private_key),
531 : : NULL);
532 : :
533 [ # # ]: 0 : if (proc == NULL)
534 : : {
535 : 0 : g_warning ("%s(): Failed to add host key: %s", G_STRFUNC, error->message);
536 [ # # ]: 0 : g_clear_pointer (&self->session, sftp_session_free);
537 : 0 : return;
538 : : }
539 : :
540 : 0 : destroy = valent_object_ref_cancellable (VALENT_OBJECT (self));
541 [ # # ]: 0 : g_subprocess_wait_check_async (proc,
542 : : destroy,
543 : : (GAsyncReadyCallback)valent_sftp_plugin_register_privkey_cb,
544 : : g_object_ref (self));
545 : : }
546 : :
547 : : /*
548 : : * Packet Handlers
549 : : */
550 : : static void
551 : 1 : handle_sftp_error (ValentSftpPlugin *self,
552 : : JsonNode *packet)
553 : : {
554 : 1 : ValentDevice *device;
555 : 2 : g_autoptr (GNotification) notification = NULL;
556 [ + - ]: 1 : g_autoptr (GIcon) error_icon = NULL;
557 [ + - ]: 1 : g_autofree char *error_title = NULL;
558 : 1 : const char *error_message;
559 : 1 : const char *device_name;
560 : 1 : JsonObject *body;
561 : :
562 [ - + ]: 1 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
563 : :
564 : 1 : body = valent_packet_get_body (packet);
565 : :
566 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
567 : 1 : device_name = valent_device_get_name (device);
568 : :
569 : 1 : error_icon = g_themed_icon_new ("dialog-error-symbolic");
570 : 1 : error_title = g_strdup_printf ("%s: SFTP", device_name);
571 : 1 : error_message = json_object_get_string_member (body, "errorMessage");
572 : :
573 : : /* Send notification */
574 : 1 : notification = g_notification_new (error_title);
575 : 1 : g_notification_set_body (notification, error_message);
576 : 1 : g_notification_set_icon (notification, error_icon);
577 : 1 : g_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_HIGH);
578 : 1 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
579 : : "sftp-error",
580 : : notification);
581 : 1 : }
582 : :
583 : : static void
584 : 0 : handle_sftp_mount (ValentSftpPlugin *self,
585 : : JsonNode *packet)
586 : : {
587 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
588 : :
589 : : /* Check if we're already mounted or mounting */
590 [ # # ]: 0 : if (self->session != NULL)
591 : : return;
592 : :
593 : : /* Parse the connection data */
594 : 0 : self->session = sftp_session_new (self, packet);
595 [ # # ]: 0 : if (self->session != NULL)
596 : : {
597 [ # # ]: 0 : if (self->privkey_ready)
598 : 0 : valent_sftp_plugin_mount_volume (self);
599 : : else
600 : 0 : valent_sftp_plugin_register_privkey (self);
601 : : }
602 : : }
603 : :
604 : : static void
605 : 1 : valent_sftp_plugin_handle_sftp (ValentSftpPlugin *self,
606 : : JsonNode *packet)
607 : : {
608 [ - + ]: 1 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
609 : :
610 : : /* The request for mount information failed, most likely due to the remote
611 : : * device not being setup yet.
612 : : */
613 [ + - ]: 1 : if (valent_packet_check_field (packet, "errorMessage"))
614 : 1 : handle_sftp_error (self, packet);
615 : :
616 : : /* Otherwise we've been sent the information necessary to open an SSH/SFTP
617 : : * connection to the remote device.
618 : : */
619 : : else
620 : 0 : handle_sftp_mount (self, packet);
621 : 1 : }
622 : :
623 : : /*
624 : : * Packet Providers
625 : : */
626 : : static void
627 : 1 : valent_sftp_plugin_handle_request (ValentSftpPlugin *self,
628 : : JsonNode *packet)
629 : : {
630 : 1 : GSettings *settings;
631 : 1 : g_autoptr (JsonBuilder) builder = NULL;
632 [ - - - + ]: 1 : g_autoptr (JsonNode) response = NULL;
633 : :
634 [ - + ]: 1 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
635 : :
636 [ - + ]: 1 : if (!valent_packet_check_field (packet, "startBrowsing"))
637 [ # # ]: 0 : return;
638 : :
639 : 1 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
640 : 1 : valent_packet_init (&builder, "kdeconnect.sftp");
641 : :
642 [ - + ]: 1 : if (g_settings_get_boolean (settings, "local-allow"))
643 : : {
644 : 0 : uint16_t local_port;
645 : :
646 : 0 : json_builder_set_member_name (builder, "user");
647 : 0 : json_builder_add_string_value (builder, g_get_user_name ());
648 : :
649 : 0 : local_port = g_settings_get_uint (settings, "local-port");
650 : 0 : json_builder_set_member_name (builder, "port");
651 : 0 : json_builder_add_int_value (builder, local_port);
652 : :
653 : 0 : json_builder_set_member_name (builder, "multiPaths");
654 : 0 : json_builder_begin_array (builder);
655 : 0 : json_builder_add_string_value (builder, g_get_home_dir ());
656 : 0 : json_builder_end_array (builder);
657 : :
658 : 0 : json_builder_set_member_name (builder, "pathNames");
659 : 0 : json_builder_begin_array (builder);
660 : 0 : json_builder_add_string_value (builder, _("Home"));
661 : 0 : json_builder_end_array (builder);
662 : : }
663 : : else
664 : : {
665 : 1 : json_builder_set_member_name (builder, "errorMessage");
666 : 1 : json_builder_add_string_value (builder, _("Permission denied"));
667 : : }
668 : :
669 : 1 : response = valent_packet_end (&builder);
670 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), response);
671 : : }
672 : :
673 : : static void
674 : 1 : valent_sftp_plugin_sftp_request (ValentSftpPlugin *self)
675 : : {
676 : 1 : g_autoptr (JsonBuilder) builder = NULL;
677 [ - - - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
678 : :
679 [ - + ]: 1 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
680 : :
681 [ - + ]: 1 : if (sftp_session_find (self))
682 [ # # ]: 0 : return;
683 : :
684 : 1 : valent_packet_init (&builder, "kdeconnect.sftp.request");
685 : 1 : json_builder_set_member_name (builder, "startBrowsing");
686 : 1 : json_builder_add_boolean_value (builder, TRUE);
687 : 1 : packet = valent_packet_end (&builder);
688 : :
689 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
690 : : }
691 : :
692 : : /*
693 : : * GActions
694 : : */
695 : : static void
696 : 0 : valent_sftp_plugin_open_uri (ValentSftpPlugin *self,
697 : : const char *uri)
698 : : {
699 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
700 [ # # ]: 0 : g_assert (uri != NULL);
701 : :
702 [ # # ]: 0 : if (self->connection != NULL)
703 : : {
704 : 0 : g_dbus_connection_call (self->connection,
705 : : "org.freedesktop.FileManager1",
706 : : "/org/freedesktop/FileManager1",
707 : : "org.freedesktop.FileManager1",
708 : : "ShowFolders",
709 : : g_variant_new_parsed ("([%s], '')", uri),
710 : : NULL,
711 : : G_DBUS_CALL_FLAGS_NONE,
712 : : -1,
713 : : NULL, NULL, NULL);
714 : : }
715 : : else
716 : : {
717 : 0 : g_app_info_launch_default_for_uri_async (uri, NULL, NULL, NULL, NULL);
718 : : }
719 : 0 : }
720 : :
721 : : static void
722 : 1 : mount_action (GSimpleAction *action,
723 : : GVariant *parameter,
724 : : gpointer user_data)
725 : : {
726 : 1 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (user_data);
727 : :
728 [ - + ]: 1 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
729 : :
730 [ + - ]: 1 : if (self->session == NULL)
731 : 1 : valent_sftp_plugin_sftp_request (self);
732 [ # # ]: 0 : else if (self->session->mount == NULL)
733 : 0 : valent_sftp_plugin_mount_volume (self);
734 : 1 : }
735 : :
736 : : static void
737 : 0 : unmount_action (GSimpleAction *action,
738 : : GVariant *parameter,
739 : : gpointer user_data)
740 : : {
741 : 0 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (user_data);
742 : :
743 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
744 : :
745 [ # # # # ]: 0 : if (self->session != NULL && self->session->mount != NULL)
746 : 0 : valent_sftp_plugin_unmount_volume (self);
747 : 0 : }
748 : :
749 : : static void
750 : 0 : open_uri_action (GSimpleAction *action,
751 : : GVariant *parameter,
752 : : gpointer user_data)
753 : : {
754 : 0 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (user_data);
755 : 0 : const char *uri = NULL;
756 : :
757 [ # # ]: 0 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
758 : :
759 : 0 : uri = g_variant_get_string (parameter, NULL);
760 : 0 : valent_sftp_plugin_open_uri (self, uri);
761 : 0 : }
762 : :
763 : : static const GActionEntry actions[] = {
764 : : {"browse", mount_action, NULL, NULL, NULL},
765 : : {"unmount", unmount_action, NULL, NULL, NULL},
766 : : {"open-uri", open_uri_action, "s", NULL, NULL},
767 : : };
768 : :
769 : : /*
770 : : * ValentSftpPlugin
771 : : */
772 : : static void
773 : 6 : valent_sftp_plugin_update_menu (ValentSftpPlugin *self)
774 : : {
775 : 6 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (self);
776 : :
777 [ - + ]: 6 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
778 : :
779 [ - + ]: 6 : if (sftp_session_find (self))
780 : : {
781 : 0 : GHashTableIter iter;
782 : 0 : const char *uri = NULL;
783 : 0 : const char *name = NULL;
784 : 0 : g_autoptr (GMenuItem) item = NULL;
785 [ # # ]: 0 : g_autoptr (GIcon) icon = NULL;
786 [ # # ]: 0 : g_autoptr (GMenu) submenu = NULL;
787 [ # # ]: 0 : g_autoptr (GMenuItem) unmount_item = NULL;
788 [ # # ]: 0 : g_autoptr (GIcon) unmount_icon = NULL;
789 : :
790 : 0 : icon = g_themed_icon_new ("folder-remote-symbolic");
791 : 0 : item = g_menu_item_new (_("Browse Folders"), "device.sftp.browse");
792 : 0 : g_menu_item_set_icon (item, icon);
793 : 0 : g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
794 : :
795 : 0 : submenu = g_menu_new ();
796 : 0 : g_menu_item_set_submenu (item, G_MENU_MODEL (submenu));
797 : :
798 : 0 : g_hash_table_iter_init (&iter, self->session->paths);
799 [ # # ]: 0 : while (g_hash_table_iter_next (&iter, (void **)&uri, (void **)&name))
800 : : {
801 : 0 : g_autofree char *action_name = NULL;
802 : :
803 : 0 : action_name = g_strdup_printf ("device.sftp.open-uri::%s", uri);
804 : 0 : g_menu_append (submenu, name, action_name);
805 : : }
806 : :
807 : 0 : unmount_icon = g_themed_icon_new ("media-eject-symbolic");
808 : 0 : unmount_item = g_menu_item_new (_("Unmount"), "device.sftp.unmount");
809 : 0 : g_menu_item_set_icon (unmount_item, unmount_icon);
810 : 0 : g_menu_append_item (submenu, unmount_item);
811 : :
812 [ # # ]: 0 : valent_device_plugin_set_menu_item (plugin, "device.sftp.browse", item);
813 : : }
814 : : else
815 : : {
816 : 6 : valent_device_plugin_set_menu_action (plugin,
817 : : "device.sftp.browse",
818 : 6 : _("Mount"),
819 : : "folder-remote-symbolic");
820 : : }
821 : 6 : }
822 : :
823 : : /*
824 : : * ValentDevicePlugin
825 : : */
826 : : static void
827 : 6 : valent_sftp_plugin_update_state (ValentDevicePlugin *plugin,
828 : : ValentDeviceState state)
829 : : {
830 : 6 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (plugin);
831 : 6 : gboolean available;
832 : :
833 [ - + ]: 6 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
834 : :
835 [ + + ]: 6 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
836 [ - + ]: 3 : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
837 : :
838 : 6 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
839 : :
840 : : // FIXME: device may be in destruction when `get_device_host()` is invoked
841 : 6 : valent_sftp_plugin_update_menu (self);
842 : :
843 [ + + ]: 6 : if (available)
844 : : {
845 : 3 : GSettings *settings;
846 : :
847 : 3 : settings = valent_extension_get_settings (VALENT_EXTENSION (plugin));
848 [ - + ]: 3 : if (g_settings_get_boolean (settings, "auto-mount"))
849 : 0 : valent_sftp_plugin_sftp_request (self);
850 : : }
851 [ - + ]: 3 : else if ((state & VALENT_DEVICE_STATE_PAIRED) == 0)
852 : : {
853 [ # # ]: 0 : g_clear_pointer (&self->session, sftp_session_end);
854 : : }
855 [ + - ]: 3 : else if ((state & VALENT_DEVICE_STATE_CONNECTED) == 0)
856 : : {
857 [ - + ]: 3 : g_clear_pointer (&self->session, sftp_session_free);
858 : : }
859 : 6 : }
860 : :
861 : : static void
862 : 2 : valent_sftp_plugin_handle_packet (ValentDevicePlugin *plugin,
863 : : const char *type,
864 : : JsonNode *packet)
865 : : {
866 : 2 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (plugin);
867 : :
868 [ - + ]: 2 : g_assert (VALENT_IS_SFTP_PLUGIN (self));
869 [ + - ]: 2 : g_assert (type != NULL);
870 [ + - ]: 2 : g_assert (VALENT_IS_PACKET (packet));
871 : :
872 [ + + ]: 2 : if (g_str_equal (type, "kdeconnect.sftp"))
873 : 1 : valent_sftp_plugin_handle_sftp (self, packet);
874 : :
875 [ + - ]: 1 : else if (g_str_equal (type, "kdeconnect.sftp.request"))
876 : 1 : valent_sftp_plugin_handle_request (self, packet);
877 : :
878 : : else
879 : 0 : g_warn_if_reached ();
880 : 2 : }
881 : :
882 : : /*
883 : : * ValentObject
884 : : */
885 : : static void
886 : 6 : valent_sftp_plugin_destroy (ValentObject *object)
887 : : {
888 : 6 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (object);
889 : 6 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
890 : :
891 : : /* Stop watching the volume monitor and unmount any current session */
892 [ + + ]: 6 : if (self->monitor != NULL)
893 : : {
894 : 3 : g_signal_handlers_disconnect_by_data (self->monitor, self);
895 [ + - ]: 3 : g_clear_object (&self->monitor);
896 : : }
897 [ - + ]: 6 : g_clear_pointer (&self->session, sftp_session_end);
898 [ + + ]: 6 : g_clear_object (&self->connection);
899 : :
900 : 6 : valent_device_plugin_set_menu_item (plugin, "device.sftp.browse", NULL);
901 : :
902 : 6 : VALENT_OBJECT_CLASS (valent_sftp_plugin_parent_class)->destroy (object);
903 : 6 : }
904 : :
905 : : /*
906 : : * GObject
907 : : */
908 : : static void
909 : 3 : valent_sftp_plugin_constructed (GObject *object)
910 : : {
911 : 3 : ValentSftpPlugin *self = VALENT_SFTP_PLUGIN (object);
912 : 3 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
913 : :
914 : 3 : G_OBJECT_CLASS (valent_sftp_plugin_parent_class)->constructed (object);
915 : :
916 : 3 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
917 : : actions,
918 : : G_N_ELEMENTS (actions),
919 : : plugin);
920 : :
921 : 3 : self->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
922 : 3 : self->monitor = g_volume_monitor_get ();
923 : 3 : g_signal_connect_object (self->monitor,
924 : : "mount-added",
925 : : G_CALLBACK (on_mount_added),
926 : : self,
927 : : G_CONNECT_DEFAULT);
928 : 3 : g_signal_connect_object (self->monitor,
929 : : "mount-removed",
930 : : G_CALLBACK (on_mount_removed),
931 : : self,
932 : : G_CONNECT_DEFAULT);
933 : 3 : }
934 : :
935 : : static void
936 : 11 : valent_sftp_plugin_class_init (ValentSftpPluginClass *klass)
937 : : {
938 : 11 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
939 : 11 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
940 : 11 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
941 : :
942 : 11 : object_class->constructed = valent_sftp_plugin_constructed;
943 : :
944 : 11 : vobject_class->destroy = valent_sftp_plugin_destroy;
945 : :
946 : 11 : plugin_class->handle_packet = valent_sftp_plugin_handle_packet;
947 : 11 : plugin_class->update_state = valent_sftp_plugin_update_state;
948 : : }
949 : :
950 : : static void
951 : 3 : valent_sftp_plugin_init (ValentSftpPlugin *self)
952 : : {
953 : 3 : }
954 : :
|