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