1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22 
23 
24 #include <config.h>
25 
26 #include <stdlib.h>
27 #include <sys/poll.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <errno.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <string.h>
35 
36 #include <glib/gstdio.h>
37 #include <glib/gi18n.h>
38 #include <gio/gio.h>
39 #include <gio/gunixinputstream.h>
40 #include <gio/gunixoutputstream.h>
41 
42 #include "gvfsicon.h"
43 
44 #include "gvfsbackendsftp.h"
45 #include "gvfsjobopenforread.h"
46 #include "gvfsjobopeniconforread.h"
47 #include "gvfsjobmount.h"
48 #include "gvfsjobread.h"
49 #include "gvfsjobseekread.h"
50 #include "gvfsjobopenforwrite.h"
51 #include "gvfsjobwrite.h"
52 #include "gvfsjobclosewrite.h"
53 #include "gvfsjobseekwrite.h"
54 #include "gvfsjobtruncate.h"
55 #include "gvfsjobsetdisplayname.h"
56 #include "gvfsjobqueryinfo.h"
57 #include "gvfsjobqueryinforead.h"
58 #include "gvfsjobqueryinfowrite.h"
59 #include "gvfsjobmove.h"
60 #include "gvfsjobdelete.h"
61 #include "gvfsjobqueryfsinfo.h"
62 #include "gvfsjobqueryattributes.h"
63 #include "gvfsjobenumerate.h"
64 #include "gvfsjobmakedirectory.h"
65 #include "gvfsjobprogress.h"
66 #include "gvfsjobpush.h"
67 #include "gvfsjobpull.h"
68 #include "gvfsjobsetattribute.h"
69 #include "gvfsdaemonprotocol.h"
70 #include "gvfsutils.h"
71 #include "gvfskeyring.h"
72 #include <gvfsutils.h>
73 #include "sftp.h"
74 #include "pty_open.h"
75 
76 /* TODO for sftp:
77  * Implement can_delete & can_rename
78  * fstat
79  */
80 
81 #if defined(HAVE_GRANTPT) || defined(HAVE_OPENPTY)
82 /* We only use this on systems with unix98 or BSD ptys */
83 #define USE_PTY 1
84 #endif
85 
86 #define SFTP_READ_TIMEOUT 40   /* seconds */
87 
88 /*
89  * All servers SHOULD support packets of at least 34000 bytes (where the packet
90  * size refers to the full length, including the header above). This should
91  * allow for reads and writes of at most 32768 bytes. For more details, see
92  * draft-ietf-secsh-filexfer-02.txt.
93  */
94 #define MAX_BUFFER_SIZE 32768
95 
96 static GQuark id_q;
97 
98 typedef enum {
99   SFTP_EXT_OPENSSH_STATVFS,
100 } SFTPServerExtensions;
101 
102 typedef enum {
103   SFTP_VENDOR_INVALID = 0,
104   SFTP_VENDOR_OPENSSH,
105   SFTP_VENDOR_SSH
106 } SFTPClientVendor;
107 
108 typedef struct _MultiReply MultiReply;
109 
110 typedef void (*ReplyCallback) (GVfsBackendSftp *backend,
111                                int reply_type,
112                                GDataInputStream *reply,
113                                guint32 len,
114                                GVfsJob *job,
115                                gpointer user_data);
116 
117 typedef void (*MultiReplyCallback) (GVfsBackendSftp *backend,
118                                     MultiReply *replies,
119                                     int n_replies,
120                                     GVfsJob *job,
121                                     gpointer user_data);
122 
123 
124 typedef struct {
125   MultiReply *replies;
126   int n_replies;
127   int n_outstanding;
128   gpointer user_data;
129   MultiReplyCallback callback;
130 } MultiRequest;
131 
132 struct _MultiReply {
133   int type;
134   GDataInputStream *data;
135   guint32 data_len;
136 
137   MultiRequest *request;
138 };
139 
140 
141 
142 typedef struct {
143   guchar *data;
144   gsize size;
145 } DataBuffer;
146 
147 typedef struct {
148   DataBuffer *raw_handle;
149   goffset offset;
150   char *filename;
151   char *tempname;
152   guint32 permissions;
153   gboolean set_permissions;
154   gboolean make_backup;
155 } SftpHandle;
156 
157 
158 typedef struct {
159   ReplyCallback callback;
160   GVfsJob *job;
161   gpointer user_data;
162 } ExpectedReply;
163 
164 typedef struct {
165   GVfsBackendSftp *op_backend;
166 
167   GOutputStream *command_stream;
168   GInputStream *reply_stream;
169   GDataInputStream *error_stream;
170 
171   GCancellable *reply_stream_cancellable;
172 
173   /* Output Queue */
174 
175   gsize command_bytes_written;
176   GList *command_queue;
177 
178   /* Reply reading: */
179   GHashTable *expected_replies;
180   guint32 reply_size;
181   guint32 reply_size_read;
182   guint8 *reply;
183 } Connection;
184 
185 struct _GVfsBackendSftp
186 {
187   GVfsBackend parent_instance;
188 
189   SFTPClientVendor client_vendor;
190   char *host;
191   int port;
192   gboolean user_specified;
193   gboolean user_specified_in_uri;
194   char *user;
195   char *tmp_password;
196   GPasswordSave password_save;
197 
198   guint32 my_uid;
199   guint32 my_gid;
200 
201   int protocol_version;
202   SFTPServerExtensions extensions;
203 
204   guint32 current_id;
205 
206   Connection command_connection;
207   Connection data_connection;
208 
209   gboolean force_unmounted;
210 };
211 
212 static void parse_attributes (GVfsBackendSftp *backend,
213                               GFileInfo *info,
214                               const char *basename,
215                               GDataInputStream *reply,
216                               GFileAttributeMatcher *attribute_matcher);
217 
G_DEFINE_TYPE(GVfsBackendSftp,g_vfs_backend_sftp,G_VFS_TYPE_BACKEND)218 G_DEFINE_TYPE (GVfsBackendSftp, g_vfs_backend_sftp, G_VFS_TYPE_BACKEND)
219 
220 static void
221 data_buffer_free (DataBuffer *buffer)
222 {
223   if (buffer)
224     {
225       g_free (buffer->data);
226       g_slice_free (DataBuffer, buffer);
227     }
228 }
229 
230 static void
make_fd_nonblocking(int fd)231 make_fd_nonblocking (int fd)
232 {
233   fcntl (fd, F_SETFL, O_NONBLOCK | fcntl (fd, F_GETFL));
234 }
235 
236 static SFTPClientVendor
get_sftp_client_vendor(void)237 get_sftp_client_vendor (void)
238 {
239   char *ssh_stderr;
240   char *args[3];
241   gint ssh_exitcode;
242   SFTPClientVendor res = SFTP_VENDOR_INVALID;
243 
244   args[0] = g_strdup (SSH_PROGRAM);
245   args[1] = g_strdup ("-V");
246   args[2] = NULL;
247   if (g_spawn_sync (NULL, args, NULL,
248 		    G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
249 		    NULL, NULL,
250 		    NULL, &ssh_stderr,
251 		    &ssh_exitcode, NULL))
252     {
253       if (ssh_stderr == NULL)
254 	res = SFTP_VENDOR_INVALID;
255       else if ((strstr (ssh_stderr, "OpenSSH") != NULL) ||
256 	       (strstr (ssh_stderr, "Sun_SSH") != NULL))
257 	res = SFTP_VENDOR_OPENSSH;
258       else if (strstr (ssh_stderr, "SSH Secure Shell") != NULL)
259 	res = SFTP_VENDOR_SSH;
260       else
261 	res = SFTP_VENDOR_INVALID;
262     }
263 
264   g_free (ssh_stderr);
265   g_free (args[0]);
266   g_free (args[1]);
267 
268   return res;
269 }
270 
271 static gboolean
has_extension(GVfsBackendSftp * backend,SFTPServerExtensions extension)272 has_extension (GVfsBackendSftp *backend, SFTPServerExtensions extension)
273 {
274   return (backend->extensions & (1 << extension)) != 0;
275 }
276 
277 static void
destroy_connection(Connection * conn)278 destroy_connection (Connection *conn)
279 {
280   if (conn->expected_replies)
281     {
282       g_hash_table_destroy (conn->expected_replies);
283       conn->expected_replies = NULL;
284     }
285 
286   g_clear_object (&conn->command_stream);
287   g_clear_object (&conn->reply_stream_cancellable);
288   g_clear_object (&conn->reply_stream);
289   g_clear_object (&conn->error_stream);
290 }
291 
292 static gboolean
connection_is_usable(Connection * conn)293 connection_is_usable (Connection *conn)
294 {
295   return conn->command_stream != NULL;
296 }
297 
298 static void
g_vfs_backend_sftp_finalize(GObject * object)299 g_vfs_backend_sftp_finalize (GObject *object)
300 {
301   GVfsBackendSftp *backend;
302 
303   backend = G_VFS_BACKEND_SFTP (object);
304   destroy_connection (&backend->command_connection);
305   destroy_connection (&backend->data_connection);
306 
307   if (G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize)
308     (*G_OBJECT_CLASS (g_vfs_backend_sftp_parent_class)->finalize) (object);
309 }
310 
311 static void
expected_reply_free(ExpectedReply * reply)312 expected_reply_free (ExpectedReply *reply)
313 {
314   g_object_unref (reply->job);
315   g_slice_free (ExpectedReply, reply);
316 }
317 
318 static void
g_vfs_backend_sftp_init(GVfsBackendSftp * backend)319 g_vfs_backend_sftp_init (GVfsBackendSftp *backend)
320 {
321 }
322 
323 static void
look_for_stderr_errors(Connection * conn,GError ** error)324 look_for_stderr_errors (Connection *conn, GError **error)
325 {
326   char *line;
327 
328   while (1)
329     {
330       line = g_data_input_stream_read_line (conn->error_stream, NULL, NULL, NULL);
331 
332       if (line == NULL)
333         {
334           /* Error (real or WOULDBLOCK) or EOF */
335           g_set_error_literal (error,
336 	                       G_IO_ERROR, G_IO_ERROR_FAILED,
337                                _("Connection failed"));
338           return;
339         }
340 
341       g_debug ("stderr: %s\n", line);
342       if (strstr (line, "Permission denied") != NULL)
343         {
344           g_set_error_literal (error,
345 	                       G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
346         	               _("Permission denied"));
347           return;
348         }
349       else if (strstr (line, "Name or service not known") != NULL)
350         {
351           g_set_error_literal (error,
352 	                       G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
353         	               _("Hostname not known"));
354           return;
355         }
356       else if (strstr (line, "No route to host") != NULL)
357         {
358           g_set_error_literal (error,
359 	                       G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND,
360         	               _("No route to host"));
361           return;
362         }
363       else if (strstr (line, "Connection refused") != NULL ||
364                strstr (line, "subsystem request failed") != NULL)
365         {
366           g_set_error_literal (error,
367                                G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
368         	               _("Connection refused by server"));
369           return;
370         }
371       else if (strstr (line, "Host key verification failed") != NULL)
372         {
373           g_set_error_literal (error,
374 	                       G_IO_ERROR, G_IO_ERROR_FAILED,
375         	               _("Host key verification failed"));
376           return;
377         }
378       else if (strstr (line, "Too many authentication failures") != NULL)
379         {
380           g_set_error_literal (error,
381                                G_IO_ERROR, G_IO_ERROR_FAILED,
382                                _("Too many authentication failures"));
383           return;
384         }
385 
386       g_free (line);
387     }
388 }
389 
390 static gchar*
read_dbus_string_dict_value(GVariant * args,const gchar * key)391 read_dbus_string_dict_value (GVariant *args, const gchar *key)
392 {
393   GVariant *a;
394   GVariantIter iter;
395   const gchar *str, *val;
396   gchar *res;
397 
398   if (! g_variant_is_of_type (args, G_VARIANT_TYPE ("(a{ss})")))
399     return NULL;
400 
401   g_variant_get (args, "(@a{ss})", &a);
402 
403   res = NULL;
404   g_variant_iter_init (&iter, a);
405   while (g_variant_iter_next (&iter, "{&s&s}", &str, &val))
406     {
407       if (g_strcmp0 (str, key) == 0)
408         {
409           res = g_strdup (val);
410           break;
411         }
412     }
413 
414   g_variant_unref (a);
415 
416   return res;
417 }
418 
419 static void
setup_ssh_environment(void)420 setup_ssh_environment (void)
421 {
422   GDBusConnection *conn;
423   GError *error;
424   GVariant *iter;
425   gchar *env;
426 
427   error = NULL;
428   conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
429   if (! conn)
430     {
431       g_warning ("Failed to setup SSH evironment: %s (%s, %d)",
432                  error->message, g_quark_to_string (error->domain), error->code);
433       g_error_free (error);
434       return;
435     }
436 
437   iter = g_dbus_connection_call_sync (conn,
438                                       "org.gnome.keyring",
439                                       "/org/gnome/keyring/daemon",
440                                       "org.gnome.keyring.Daemon",
441                                       "GetEnvironment",
442                                       NULL,
443                                       NULL,
444                                       G_DBUS_CALL_FLAGS_NONE,
445                                       -1,
446                                       NULL,
447                                       &error);
448   if (! iter)
449     {
450       g_dbus_error_strip_remote_error (error);
451       g_warning ("Failed to setup SSH evironment: %s (%s, %d)",
452                  error->message, g_quark_to_string (error->domain), error->code);
453       g_error_free (error);
454     }
455   else
456     {
457       env = read_dbus_string_dict_value (iter, "SSH_AUTH_SOCK");
458       if (env && env[0])
459         g_setenv ("SSH_AUTH_SOCK", env, TRUE);
460       g_free (env);
461       g_variant_unref (iter);
462     }
463 
464   g_object_unref (conn);
465 }
466 
467 static char **
setup_ssh_commandline(GVfsBackend * backend)468 setup_ssh_commandline (GVfsBackend *backend)
469 {
470   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
471   guint last_arg;
472   gchar **args;
473 
474   args = g_new0 (gchar *, 20); /* 20 is enought for now, bump size if code below changes */
475 
476   /* Fill in the first few args */
477   last_arg = 0;
478   args[last_arg++] = g_strdup (SSH_PROGRAM);
479 
480   if (op_backend->client_vendor == SFTP_VENDOR_OPENSSH)
481     {
482       args[last_arg++] = g_strdup ("-oForwardX11 no");
483       args[last_arg++] = g_strdup ("-oForwardAgent no");
484       args[last_arg++] = g_strdup ("-oPermitLocalCommand no");
485       args[last_arg++] = g_strdup ("-oClearAllForwardings yes");
486       args[last_arg++] = g_strdup ("-oProtocol 2");
487       args[last_arg++] = g_strdup ("-oNoHostAuthenticationForLocalhost yes");
488 #ifndef USE_PTY
489       args[last_arg++] = g_strdup ("-oBatchMode yes");
490 #endif
491 
492     }
493   else if (op_backend->client_vendor == SFTP_VENDOR_SSH)
494     args[last_arg++] = g_strdup ("-x");
495 
496   if (op_backend->port != -1)
497     {
498       args[last_arg++] = g_strdup ("-p");
499       args[last_arg++] = g_strdup_printf ("%d", op_backend->port);
500     }
501 
502 
503   if (op_backend->user_specified)
504     {
505       args[last_arg++] = g_strdup ("-l");
506       args[last_arg++] = g_strdup (op_backend->user);
507     }
508 
509   args[last_arg++] = g_strdup ("-s");
510 
511   if (op_backend->client_vendor == SFTP_VENDOR_SSH)
512     {
513       args[last_arg++] = g_strdup ("sftp");
514       args[last_arg++] = g_strdup (op_backend->host);
515     }
516   else
517     {
518       args[last_arg++] = g_strdup (op_backend->host);
519       args[last_arg++] = g_strdup ("sftp");
520     }
521 
522   args[last_arg++] = NULL;
523 
524   return args;
525 }
526 
527 static gboolean
spawn_ssh(GVfsBackend * backend,char * args[],pid_t * pid,int * tty_fd,int * stdin_fd,int * stdout_fd,int * stderr_fd,int * slave_fd,GError ** error)528 spawn_ssh (GVfsBackend *backend,
529            char *args[],
530            pid_t *pid,
531            int *tty_fd,
532            int *stdin_fd,
533            int *stdout_fd,
534            int *stderr_fd,
535            int *slave_fd,
536            GError **error)
537 {
538   if (gvfs_get_debug ())
539     {
540       const char **arg;
541       GString *cmd;
542 
543       cmd = g_string_new (NULL);
544       for (arg = (const char **)args; *arg != NULL; arg++)
545         {
546           g_string_append (cmd, *arg);
547           g_string_append (cmd, " ");
548         }
549 
550       g_debug ("spawn_ssh: %s\n", cmd->str);
551       g_string_free (cmd, TRUE);
552     }
553 
554 #ifdef USE_PTY
555   *tty_fd = pty_open(pid, PTY_REAP_CHILD, NULL,
556 		     args[0], args, NULL,
557 		     300, 300,
558 		     stdin_fd, stdout_fd, stderr_fd, slave_fd);
559   if (*tty_fd == -1)
560     {
561       g_set_error_literal (error,
562 			   G_IO_ERROR, G_IO_ERROR_FAILED,
563 			   _("Unable to spawn SSH program"));
564       return FALSE;
565     }
566 #else
567   GError *my_error;
568   GPid gpid;
569 
570   *tty_fd = -1;
571 
572   my_error = NULL;
573   if (!g_spawn_async_with_pipes (NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
574 				 &gpid,
575 				 stdin_fd, stdout_fd, stderr_fd, &my_error))
576     {
577       g_set_error (error,
578                    G_IO_ERROR, G_IO_ERROR_FAILED,
579                    _("Unable to spawn SSH program: %s"), my_error->message);
580       g_error_free (my_error);
581       return FALSE;
582     }
583   *pid = gpid;
584 #endif
585 
586   return TRUE;
587 }
588 
589 static guint32
get_new_id(GVfsBackendSftp * backend)590 get_new_id (GVfsBackendSftp *backend)
591 {
592   return backend->current_id++;
593 }
594 
595 static GDataOutputStream *
new_command_stream(GVfsBackendSftp * backend,int type)596 new_command_stream (GVfsBackendSftp *backend, int type)
597 {
598   GOutputStream *mem_stream;
599   GDataOutputStream *data_stream;
600   guint32 id;
601 
602   mem_stream = g_memory_output_stream_new (NULL, 0, (GReallocFunc)g_realloc, NULL);
603   data_stream = g_data_output_stream_new (mem_stream);
604   g_object_unref (mem_stream);
605 
606   g_data_output_stream_put_int32 (data_stream, 0, NULL, NULL); /* LEN */
607   g_data_output_stream_put_byte (data_stream, type, NULL, NULL);
608   if (type != SSH_FXP_INIT)
609     {
610       id = get_new_id (backend);
611       g_data_output_stream_put_uint32 (data_stream, id, NULL, NULL);
612       g_object_set_qdata (G_OBJECT (data_stream), id_q, GUINT_TO_POINTER (id));
613     }
614 
615   return data_stream;
616 }
617 
618 static gpointer
get_data_from_command_stream(GDataOutputStream * command_stream,gsize * len)619 get_data_from_command_stream (GDataOutputStream *command_stream, gsize *len)
620 {
621   GOutputStream *mem_stream;
622   gpointer data;
623   guint32 *len_ptr;
624 
625   mem_stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (command_stream));
626   *len = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (mem_stream));
627   data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (mem_stream));
628 
629   len_ptr = (guint32 *)data;
630   *len_ptr = GUINT32_TO_BE (*len - 4);
631 
632   return data;
633 }
634 
635 static gboolean
send_command_sync_and_unref_command(Connection * conn,GDataOutputStream * command_stream,GCancellable * cancellable,GError ** error)636 send_command_sync_and_unref_command (Connection *conn,
637                                      GDataOutputStream *command_stream,
638                                      GCancellable *cancellable,
639                                      GError **error)
640 {
641   gpointer data;
642   gsize len;
643   gsize bytes_written;
644   gboolean res;
645 
646   data = get_data_from_command_stream (command_stream, &len);
647 
648   res = g_output_stream_write_all (conn->command_stream,
649                                    data, len,
650                                    &bytes_written,
651                                    cancellable, error);
652 
653   if (error == NULL && !res)
654     g_warning ("Ignored send_command error\n");
655 
656   g_free (data);
657   g_object_unref (command_stream);
658 
659   return res;
660 }
661 
662 static gboolean
wait_for_reply(GVfsBackend * backend,int stdout_fd,GError ** error)663 wait_for_reply (GVfsBackend *backend, int stdout_fd, GError **error)
664 {
665   fd_set ifds;
666   struct timeval tv;
667   int ret;
668 
669   FD_ZERO (&ifds);
670   FD_SET (stdout_fd, &ifds);
671 
672   tv.tv_sec = SFTP_READ_TIMEOUT;
673   tv.tv_usec = 0;
674 
675   ret = select (stdout_fd+1, &ifds, NULL, NULL, &tv);
676 
677   if (ret <= 0)
678     {
679       g_set_error_literal (error,
680 			   G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
681 			   _("Timed out when logging in"));
682       return FALSE;
683     }
684   return TRUE;
685 }
686 
687 static GDataInputStream *
make_reply_stream(guint8 * data,gsize len)688 make_reply_stream (guint8 *data, gsize len)
689 {
690   GInputStream *mem_stream;
691   GDataInputStream *data_stream;
692 
693   mem_stream = g_memory_input_stream_new_from_data (data, len, g_free);
694   data_stream = g_data_input_stream_new (mem_stream);
695   g_object_unref (mem_stream);
696 
697   return data_stream;
698 }
699 
700 static GDataInputStream *
read_reply_sync(Connection * conn,gsize * len_out,GError ** error)701 read_reply_sync (Connection *conn, gsize *len_out, GError **error)
702 {
703   guint32 len;
704   gsize bytes_read;
705   GByteArray *array;
706   guint8 *data;
707 
708   if (!g_input_stream_read_all (conn->reply_stream,
709 				&len, 4,
710 				&bytes_read, NULL, error))
711     return NULL;
712 
713   /* Make sure we handle SSH exiting early, e.g. if no further
714      authentication methods */
715   if (bytes_read == 0)
716     {
717       g_set_error_literal (error,
718 			   G_IO_ERROR, G_IO_ERROR_FAILED,
719                            _("Connection failed"));
720       return NULL;
721     }
722 
723   len = GUINT32_FROM_BE (len);
724 
725   array = g_byte_array_sized_new (len);
726 
727   if (!g_input_stream_read_all (conn->reply_stream,
728 				array->data, len,
729 				&bytes_read, NULL, error))
730     {
731       g_byte_array_free (array, TRUE);
732       return NULL;
733     }
734 
735   if (len_out)
736     *len_out = len;
737 
738   data = array->data;
739   g_byte_array_free (array, FALSE);
740 
741   return make_reply_stream (data, len);
742 }
743 
744 static void
put_string(GDataOutputStream * stream,const char * str)745 put_string (GDataOutputStream *stream, const char *str)
746 {
747   g_data_output_stream_put_uint32 (stream, strlen (str), NULL, NULL);
748   g_data_output_stream_put_string (stream, str, NULL, NULL);
749 }
750 
751 static void
put_data_buffer(GDataOutputStream * stream,DataBuffer * buffer)752 put_data_buffer (GDataOutputStream *stream, DataBuffer *buffer)
753 {
754   g_data_output_stream_put_uint32 (stream, buffer->size, NULL, NULL);
755   g_output_stream_write_all (G_OUTPUT_STREAM (stream),
756                              buffer->data, buffer->size,
757                              NULL,
758                              NULL, NULL);
759 }
760 
761 static char *
read_string(GDataInputStream * stream,gsize * len_out)762 read_string (GDataInputStream *stream, gsize *len_out)
763 {
764   guint32 len;
765   char *data;
766   GError *error;
767 
768   error = NULL;
769   len = g_data_input_stream_read_uint32 (stream, NULL, &error);
770   if (error)
771     {
772       g_error_free (error);
773       return NULL;
774     }
775 
776   data = g_malloc (len + 1);
777 
778   if (!g_input_stream_read_all (G_INPUT_STREAM (stream), data, len, NULL, NULL, NULL))
779     {
780       g_free (data);
781       return NULL;
782     }
783 
784   data[len] = 0;
785 
786   if (len_out)
787     *len_out = len;
788 
789   return data;
790 }
791 
792 static DataBuffer *
read_data_buffer(GDataInputStream * stream)793 read_data_buffer (GDataInputStream *stream)
794 {
795   DataBuffer *buffer;
796 
797   buffer = g_slice_new (DataBuffer);
798   buffer->data = (guchar *)read_string (stream, &buffer->size);
799 
800   return buffer;
801 }
802 
803 static gboolean
get_hostname_from_line(const gchar * buffer,gchar ** hostname_out)804 get_hostname_from_line (const gchar *buffer,
805                         gchar **hostname_out)
806 {
807   gchar *startpos;
808   gchar *endpos;
809 
810   /* Parse a line that looks like: "username@hostname's password:". */
811 
812   startpos = strchr (buffer, '@');
813   if (!startpos)
814     return FALSE;
815 
816   endpos = strchr (buffer, '\'');
817   if (!endpos)
818     return FALSE;
819 
820   *hostname_out = g_strndup (startpos + 1, endpos - startpos - 1);
821 
822   return TRUE;
823 }
824 
825 static gboolean
get_hostname_and_fingerprint_from_line(const gchar * buffer,gchar ** hostname_out,gchar ** fingerprint_out)826 get_hostname_and_fingerprint_from_line (const gchar *buffer,
827                                         gchar      **hostname_out,
828                                         gchar      **fingerprint_out)
829 {
830   gchar *pos;
831   gchar *startpos;
832   gchar *endpos;
833   gchar *hostname = NULL;
834   gchar *fingerprint = NULL;
835 
836   if (g_str_has_prefix (buffer, "The authenticity of host '"))
837     {
838       /* OpenSSH */
839       pos = strchr (&buffer[26], '\'');
840       if (pos == NULL)
841         return FALSE;
842 
843       hostname = g_strndup (&buffer[26], pos - (&buffer[26]));
844 
845       startpos = strstr (pos, " key fingerprint is ");
846       if (startpos == NULL)
847         {
848           g_free (hostname);
849           return FALSE;
850         }
851 
852       startpos = startpos + 20;
853       endpos = strchr (startpos, '.');
854       if (endpos == NULL)
855         {
856           g_free (hostname);
857           return FALSE;
858         }
859 
860       fingerprint = g_strndup (startpos, endpos - startpos);
861     }
862   else if (strstr (buffer, "Key fingerprint:") != NULL)
863     {
864       /* SSH.com*/
865       startpos = strstr (buffer, "Key fingerprint:");
866       if (startpos == NULL)
867         {
868           g_free (hostname);
869           return FALSE;
870         }
871 
872       startpos = startpos + 18;
873       endpos = strchr (startpos, '\r');
874       fingerprint = g_strndup (startpos, endpos - startpos);
875     }
876 
877   *hostname_out = hostname;
878   *fingerprint_out = fingerprint;
879 
880   return TRUE;
881 }
882 
883 static gboolean
get_hostname_and_ip_address(const gchar * buffer,gchar ** hostname_out,gchar ** ip_address_out)884 get_hostname_and_ip_address (const gchar *buffer,
885                              gchar      **hostname_out,
886                              gchar      **ip_address_out)
887 {
888   char *startpos, *endpos, *hostname;
889 
890   /* Parse a line that looks like:
891    * Warning: the ECDSA/RSA host key for 'hostname' differs from the key for the IP address '...'
892    * First get the hostname.
893    */
894   startpos = strchr (buffer, '\'');
895   if (!startpos)
896     return FALSE;
897   startpos++;
898 
899   endpos = strchr (startpos, '\'');
900   if (!endpos)
901     return FALSE;
902 
903   hostname = g_strndup (startpos, endpos - startpos);
904 
905   /* Then get the ip address. */
906   startpos = strchr (endpos + 1, '\'');
907   if (!startpos)
908     {
909       g_free (hostname);
910       return FALSE;
911     }
912   startpos++;
913 
914   endpos = strchr (startpos, '\'');
915   if (!endpos)
916     {
917       g_free (hostname);
918       return FALSE;
919     }
920 
921   *hostname_out = hostname;
922   *ip_address_out = g_strndup (startpos, endpos - startpos);
923 
924   return TRUE;
925 }
926 
927 static gboolean
login_answer_yes_no(GMountSource * mount_source,char * message,GOutputStream * reply_stream,GError ** error)928 login_answer_yes_no (GMountSource *mount_source,
929                      char *message,
930                      GOutputStream *reply_stream,
931                      GError **error)
932 {
933   const char *choices[] = {_("Log In Anyway"), _("Cancel Login"), NULL};
934   const char *choice_string;
935   int choice;
936   gboolean aborted = FALSE;
937   gsize bytes_written;
938 
939   if (!g_mount_source_ask_question (mount_source,
940                                     message,
941                                     choices,
942                                     &aborted,
943                                     &choice) ||
944       aborted)
945     {
946       g_set_error_literal (error,
947                            G_IO_ERROR, G_IO_ERROR_FAILED,
948                            _("Login dialog cancelled"));
949       g_free (message);
950       return FALSE;
951     }
952   g_free (message);
953 
954   choice_string = (choice == 0) ? "yes" : "no";
955   if (!g_output_stream_write_all (reply_stream,
956                                   choice_string,
957                                   strlen (choice_string),
958                                   &bytes_written,
959                                   NULL, NULL) ||
960       !g_output_stream_write_all (reply_stream,
961                                   "\n", 1,
962                                   &bytes_written,
963                                   NULL, NULL))
964     {
965       g_set_error_literal (error,
966                            G_IO_ERROR, G_IO_ERROR_FAILED,
967                            _("Can’t send host identity confirmation"));
968       return FALSE;
969     }
970 
971   return TRUE;
972 }
973 
974 static const gchar *
get_authtype_from_password_line(const char * password_line)975 get_authtype_from_password_line (const char *password_line)
976 {
977   return g_str_has_prefix (password_line, "Enter passphrase for key") ?
978 	  "publickey" : "password";
979 }
980 
981 static char *
get_object_from_password_line(const char * password_line)982 get_object_from_password_line (const char *password_line)
983 {
984   char *chr, *ptr, *object = NULL;
985 
986   if (g_str_has_prefix (password_line, "Enter passphrase for key"))
987     {
988       ptr = strchr (password_line, '\'');
989       if (ptr != NULL)
990         {
991 	  ptr += 1;
992 	  chr = strchr (ptr, '\'');
993 	  if (chr != NULL)
994 	    {
995 	      object = g_strndup (ptr, chr - ptr);
996 	    }
997 	  else
998 	    {
999 	      object = g_strdup (ptr);
1000 	    }
1001 	}
1002     }
1003   return object;
1004 }
1005 
1006 static gboolean
handle_login(GVfsBackend * backend,GMountSource * mount_source,int tty_fd,int stdout_fd,int stderr_fd,gboolean initial_connection,GError ** error)1007 handle_login (GVfsBackend *backend,
1008               GMountSource *mount_source,
1009               int tty_fd, int stdout_fd, int stderr_fd,
1010               gboolean initial_connection,
1011               GError **error)
1012 {
1013   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1014   GInputStream *prompt_stream;
1015   GOutputStream *reply_stream;
1016   int ret;
1017   int prompt_fd;
1018   struct pollfd fds[2];
1019   char buffer[1024];
1020   gsize len;
1021   gboolean ret_val;
1022   char *new_password = NULL;
1023   char *new_user = NULL;
1024   gboolean password_in_keyring = FALSE;
1025   const gchar *authtype = NULL;
1026   gchar *object = NULL;
1027   char *prompt;
1028   int attempts = 0;
1029   static int i = 0;
1030 
1031   i++;
1032   g_debug ("handle_login #%d initial_connection = %d - user: %s, host: %s, port: %d\n",
1033            i, initial_connection, op_backend->user, op_backend->host, op_backend->port);
1034 
1035   if (op_backend->client_vendor == SFTP_VENDOR_SSH)
1036     prompt_fd = stderr_fd;
1037   else
1038     prompt_fd = tty_fd;
1039 
1040   prompt_stream = g_unix_input_stream_new (prompt_fd, FALSE);
1041   reply_stream = g_unix_output_stream_new (tty_fd, FALSE);
1042 
1043   ret_val = TRUE;
1044   while (1)
1045     {
1046       fds[0].fd = stdout_fd;
1047       fds[0].events = POLLIN;
1048       fds[1].fd = prompt_fd;
1049       fds[1].events = POLLIN;
1050 
1051       ret = poll(fds, 2, SFTP_READ_TIMEOUT * 1000);
1052 
1053       if (ret <= 0)
1054         {
1055           g_set_error_literal (error,
1056 	                       G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
1057         	               _("Timed out when logging in"));
1058           ret_val = FALSE;
1059           break;
1060         }
1061 
1062       if (fds[0].revents)
1063         break; /* Got reply to initial INIT request */
1064 
1065       if (!(fds[1].revents & POLLIN))
1066         continue;
1067 
1068       len = g_input_stream_read (prompt_stream,
1069                                  buffer, sizeof (buffer) - 1,
1070                                  NULL, error);
1071 
1072       if (len == -1)
1073         {
1074           ret_val = FALSE;
1075           break;
1076         }
1077 
1078       buffer[len] = 0;
1079       g_strchug (buffer);
1080 
1081       g_debug ("handle_login #%d - prompt: \"%s\"\n", i, buffer);
1082 
1083       /*
1084        * If logging in on a second connection (e.g. the data connection), use
1085        * the user and password stored in the backend and don't retry if it
1086        * fails.
1087        *
1088        * If the input URI contains a username
1089        *     if the input URI contains a password, we attempt one login and return GNOME_VFS_ERROR_ACCESS_DENIED on failure.
1090        *     if the input URI contains no password, we query the user until he provides a correct one, or he cancels.
1091        *
1092        * If the input URI contains no username
1093        *     (a) the user is queried for a user name and a password, with the default login being his
1094        *     local login name.
1095        *
1096        *     (b) if the user decides to change his remote login name, we go to tty_retry because we need a
1097        *     new SSH session, attempting one login with his provided credentials, and if that fails proceed
1098        *     with (a), but use his desired remote login name as default.
1099        *
1100        * The "password" variable is only used for the very first login attempt,
1101        * or for the first re-login attempt when the user decided to change his name.
1102        * Otherwise, we "new_password" and "new_user_name" is used, as output variable
1103        * for user and keyring input.
1104        */
1105       if (g_str_has_suffix (buffer, "password: ") ||
1106           g_str_has_suffix (buffer, "Password: ") ||
1107           g_str_has_suffix (buffer, "Password:")  ||
1108           g_str_has_prefix (buffer, "Password for ") ||
1109           g_str_has_prefix (buffer, "Enter Kerberos password") ||
1110           g_str_has_prefix (buffer, "Enter passphrase for key") ||
1111           g_str_has_prefix (buffer, "Enter PASSCODE"))
1112         {
1113           gboolean aborted = FALSE;
1114           gsize bytes_written;
1115 
1116           attempts++;
1117 	  authtype = get_authtype_from_password_line (buffer);
1118 	  object = get_object_from_password_line (buffer);
1119 
1120           if (!initial_connection && attempts > 1)
1121             {
1122 	      g_set_error_literal (error,
1123 				   G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
1124                                    _("Permission denied"));
1125 	      ret_val = FALSE;
1126 	      break;
1127             }
1128 
1129           /* If password is in keyring at this point is because it failed */
1130 	  if (!op_backend->tmp_password && (password_in_keyring ||
1131               !g_vfs_keyring_lookup_password (op_backend->user,
1132                                               op_backend->host,
1133                                               NULL,
1134                                               "sftp",
1135 					      object,
1136 					      authtype,
1137 					      op_backend->port != -1 ?
1138 					      op_backend->port
1139 					      :
1140 					      0,
1141                                               &new_user,
1142                                               NULL,
1143                                               &new_password)))
1144             {
1145               GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD;
1146               gchar *hostname = NULL;
1147 
1148               g_debug ("handle_login #%d - asking for password...\n", i);
1149 
1150               if (g_vfs_keyring_is_available ())
1151                 flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
1152 	      if (strcmp (authtype, "password") == 0 &&
1153 		  !op_backend->user_specified)
1154 	        flags |= G_ASK_PASSWORD_NEED_USERNAME;
1155 
1156               g_free (new_password);
1157 
1158               get_hostname_from_line (buffer, &hostname);
1159 
1160               if (op_backend->user_specified)
1161                 if (strcmp (authtype, "publickey") == 0)
1162                   /* Translators: the first %s is the username, the second the host name */
1163                   prompt = g_strdup_printf (_("Enter passphrase for secure key for %s on %s"), op_backend->user, op_backend->host);
1164                 else
1165                   /* Translators: the first %s is the username, the second the host name */
1166                   prompt = g_strdup_printf (_("Enter password for %s on %s"), op_backend->user, hostname ? hostname : op_backend->host);
1167               else
1168                 if (strcmp (authtype, "publickey") == 0)
1169                   /* Translators: %s is the hostname */
1170                   prompt = g_strdup_printf (_("Enter passphrase for secure key for %s"), op_backend->host);
1171                 else
1172                   /* Translators: %s is the hostname */
1173                   prompt = g_strdup_printf (_("Enter password for %s"), hostname ? hostname : op_backend->host);
1174 
1175               if (!g_mount_source_ask_password (mount_source,
1176                                                 prompt,
1177                                                 op_backend->user,
1178                                                 NULL,
1179                                                 flags,
1180                                                 &aborted,
1181                                                 &new_password,
1182                                                 &new_user,
1183                                                 NULL,
1184 						NULL,
1185                                                 &op_backend->password_save) ||
1186                   aborted)
1187                 {
1188                   g_set_error_literal (error, G_IO_ERROR,
1189                                        aborted ? G_IO_ERROR_FAILED_HANDLED : G_IO_ERROR_PERMISSION_DENIED,
1190         	                       _("Password dialog cancelled"));
1191                   ret_val = FALSE;
1192                   break;
1193                 }
1194                 g_free (prompt);
1195             }
1196 	  else if (op_backend->tmp_password)
1197 	    {
1198               /* I already have a password of a previous login attempt
1199                * (either because this is a second connection or because the
1200                * user provided a new user name).
1201 	       */
1202 	      new_password = op_backend->tmp_password;
1203 	      op_backend->tmp_password = NULL;
1204 
1205               g_debug ("handle_login #%d - using credentials from previous login attempt...\n", i);
1206 	    }
1207           else
1208             {
1209               password_in_keyring = TRUE;
1210 
1211               g_debug ("handle_login #%d - using credentials from keyring...\n", i);
1212             }
1213 
1214 	  if (new_user &&
1215 	      (op_backend->user == NULL ||
1216 	       strcmp (new_user, op_backend->user) != 0))
1217 	    {
1218               g_debug ("handle_login #%d - new_user: %s\n", i, new_user);
1219 
1220 	      g_free (op_backend->user);
1221 	      op_backend->user = new_user;
1222 
1223 	      op_backend->user_specified = TRUE;
1224 
1225 	      g_free (op_backend->tmp_password);
1226 	      op_backend->tmp_password = new_password;
1227 	      new_password = NULL;
1228 
1229 	      g_set_error_literal (error,
1230 				   G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1231 				   "Invalid user name");
1232 	      ret_val = FALSE;
1233 	      break;
1234 	    }
1235 	  else if (new_user)
1236 	    {
1237 	      g_free (new_user);
1238 	    }
1239 
1240 	  if (new_password == NULL)
1241 	    {
1242 	      /* This really should not happen, but was seen as bug #569203
1243 	       * avoid crash and ask for info
1244 	       */
1245 	      g_warning ("Got NULL password but no error in sftp login request. "
1246 			 "This should not happen, if you can reproduce this, please "
1247 			 "add information to http://bugzilla.gnome.org/show_bug.cgi?id=569203");
1248 	      new_password = g_strdup ("");
1249 	    }
1250 
1251           if (!g_output_stream_write_all (reply_stream,
1252                                           new_password, strlen (new_password),
1253                                           &bytes_written,
1254                                           NULL, NULL) ||
1255               !g_output_stream_write_all (reply_stream,
1256                                           "\n", 1,
1257                                           &bytes_written,
1258                                           NULL, NULL))
1259             {
1260               g_set_error_literal (error,
1261 	                           G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
1262         	                   _("Can’t send password"));
1263               ret_val = FALSE;
1264               break;
1265             }
1266         }
1267       else if (g_str_has_prefix (buffer, "The authenticity of host '") ||
1268                strstr (buffer, "Key fingerprint:") != NULL)
1269         {
1270 	  gchar *hostname = NULL;
1271 	  gchar *fingerprint = NULL;
1272 	  gchar *message;
1273 
1274           g_debug ("handle_login #%d - confirming authenticity of host...\n", i);
1275 
1276 	  get_hostname_and_fingerprint_from_line (buffer, &hostname, &fingerprint);
1277 
1278 	  message = g_strdup_printf (_("Can’t verify the identity of “%s”.\n"
1279 				       "This happens when you log in to a computer the first time.\n\n"
1280 				       "The identity sent by the remote computer is “%s”. "
1281 				       "If you want to be absolutely sure it is safe to continue, "
1282 				       "contact the system administrator."),
1283                                      hostname ? hostname : op_backend->host,
1284                                      fingerprint ? fingerprint : "???");
1285 
1286 	  g_free (hostname);
1287 	  g_free (fingerprint);
1288 
1289           if (!login_answer_yes_no (mount_source, message, reply_stream, error))
1290             {
1291               ret_val = FALSE;
1292               break;
1293             }
1294 	}
1295       else if (strstr (buffer, "differs from the key for the IP address"))
1296         {
1297           gchar *hostname = NULL;
1298           gchar *ip_address = NULL;
1299           gchar *message;
1300 
1301           g_debug ("handle_login #%d - host key / IP mismatch ...\n", i);
1302 
1303           get_hostname_and_ip_address (buffer, &hostname, &ip_address);
1304 
1305           message = g_strdup_printf (_("The host key for “%s” differs from the key for the IP address “%s”\n"
1306                                        "If you want to be absolutely sure it is safe to continue, "
1307                                        "contact the system administrator."),
1308                                      hostname ? hostname : op_backend->host,
1309                                      ip_address ? ip_address : "???");
1310 
1311           g_free (hostname);
1312           g_free (ip_address);
1313 
1314           if (!login_answer_yes_no (mount_source, message, reply_stream, error))
1315             {
1316               ret_val = FALSE;
1317               break;
1318             }
1319         }
1320     }
1321 
1322   if (ret_val && initial_connection)
1323     {
1324       g_debug ("handle_login #%d - password_save: %d\n", i, op_backend->password_save);
1325 
1326       /* Login succeed, save password in keyring */
1327       g_vfs_keyring_save_password (op_backend->user,
1328                                    op_backend->host,
1329                                    NULL,
1330                                    "sftp",
1331 				   object,
1332 				   authtype,
1333 				   op_backend->port != -1 ?
1334 				   op_backend->port
1335 				   :
1336 				   0,
1337                                    new_password,
1338                                    op_backend->password_save);
1339 
1340       /* Keep the successful password for subsequent connections. */
1341       op_backend->tmp_password = new_password;
1342       new_password = NULL;
1343     }
1344 
1345   g_debug ("handle_login #%d - ret_val: %d\n", i, ret_val);
1346 
1347   g_free (object);
1348   g_free (new_password);
1349   g_object_unref (prompt_stream);
1350   g_object_unref (reply_stream);
1351   return ret_val;
1352 }
1353 
1354 static void
fail_jobs(Connection * conn,GError * error)1355 fail_jobs (Connection *conn, GError *error)
1356 {
1357   GHashTableIter iter;
1358   gpointer key, value;
1359 
1360   if (!connection_is_usable (conn))
1361     return;
1362 
1363   g_hash_table_iter_init (&iter, conn->expected_replies);
1364   while (g_hash_table_iter_next (&iter, &key, &value))
1365     {
1366       ExpectedReply *expected_reply = (ExpectedReply *) value;
1367       g_vfs_job_failed_from_error (expected_reply->job, error);
1368     }
1369 }
1370 
1371 static void
fail_jobs_and_unmount(GVfsBackendSftp * backend,GError * error)1372 fail_jobs_and_unmount (GVfsBackendSftp *backend, GError *error)
1373 {
1374   if (backend->force_unmounted)
1375     return;
1376 
1377   backend->force_unmounted = TRUE;
1378 
1379   fail_jobs (&backend->command_connection, error);
1380   fail_jobs (&backend->data_connection, error);
1381 
1382   g_error_free (error);
1383 
1384   g_vfs_backend_force_unmount ((GVfsBackend*)backend);
1385 }
1386 
1387 static int
check_input_stream_read_result(Connection * conn,gssize res,GError * error)1388 check_input_stream_read_result (Connection *conn, gssize res, GError *error)
1389 {
1390   if (G_UNLIKELY (res <= 0))
1391     {
1392       if (res == 0 || error == NULL)
1393         {
1394           g_clear_error (&error);
1395           g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
1396                        res == 0 ? _("The connection is closed (the underlying SSH process exited)")
1397                                 : _("Internal error: Unknown Error"));
1398         }
1399 
1400       fail_jobs_and_unmount (conn->op_backend, error);
1401       return -1;
1402     }
1403 
1404   return 0;
1405 }
1406 
1407 static void read_reply_async (Connection *conn);
1408 
1409 static void
read_reply_async_got_data(GObject * source_object,GAsyncResult * result,gpointer user_data)1410 read_reply_async_got_data  (GObject *source_object,
1411                             GAsyncResult *result,
1412                             gpointer user_data)
1413 {
1414   Connection *conn = user_data;
1415   gssize res;
1416   GDataInputStream *reply;
1417   ExpectedReply *expected_reply;
1418   guint32 id;
1419   int type;
1420   GError *error;
1421 
1422   error = NULL;
1423   res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
1424 
1425   /* If we got an error, we've already called force_unmount so don't do
1426    * anything further. */
1427   if (check_input_stream_read_result (conn, res, error) == -1)
1428     return;
1429 
1430   conn->reply_size_read += res;
1431 
1432   if (conn->reply_size_read < conn->reply_size)
1433     {
1434       g_input_stream_read_async (conn->reply_stream,
1435 				 conn->reply + conn->reply_size_read, conn->reply_size - conn->reply_size_read,
1436 				 0, NULL, read_reply_async_got_data, conn);
1437       return;
1438     }
1439 
1440   reply = make_reply_stream (conn->reply, conn->reply_size);
1441   conn->reply = NULL;
1442 
1443   type = g_data_input_stream_read_byte (reply, NULL, NULL);
1444   id = g_data_input_stream_read_uint32 (reply, NULL, NULL);
1445 
1446   expected_reply = g_hash_table_lookup (conn->expected_replies, GINT_TO_POINTER (id));
1447   if (expected_reply)
1448     {
1449       if (expected_reply->callback != NULL)
1450         (expected_reply->callback) (conn->op_backend, type, reply, conn->reply_size,
1451                                     expected_reply->job, expected_reply->user_data);
1452       g_hash_table_remove (conn->expected_replies, GINT_TO_POINTER (id));
1453     }
1454   else
1455     g_warning ("Got unhandled reply of size %"G_GUINT32_FORMAT" for id %"G_GUINT32_FORMAT"\n", conn->reply_size, id);
1456 
1457   g_object_unref (reply);
1458 
1459   read_reply_async (conn);
1460 
1461 }
1462 
1463 static void
read_reply_async_got_len(GObject * source_object,GAsyncResult * result,gpointer user_data)1464 read_reply_async_got_len  (GObject *source_object,
1465                            GAsyncResult *result,
1466                            gpointer user_data)
1467 {
1468   Connection *conn = user_data;
1469   gssize res;
1470   GError *error;
1471 
1472   error = NULL;
1473   res = g_input_stream_read_finish (G_INPUT_STREAM (source_object), result, &error);
1474 
1475   /* Bail out if cancelled */
1476   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1477     {
1478       g_error_free (error);
1479       g_object_unref (conn->op_backend);
1480       return;
1481     }
1482 
1483   /* If we got an error, we've already called force_unmount so don't do
1484    * anything further. */
1485   if (check_input_stream_read_result (conn, res, error) == -1)
1486     return;
1487 
1488   conn->reply_size_read += res;
1489 
1490   if (conn->reply_size_read < 4)
1491     {
1492       g_input_stream_read_async (conn->reply_stream,
1493 				 (char *)&conn->reply_size + conn->reply_size_read, 4 - conn->reply_size_read,
1494 				 0, conn->reply_stream_cancellable, read_reply_async_got_len,
1495 				 conn);
1496       return;
1497     }
1498   conn->reply_size = GUINT32_FROM_BE (conn->reply_size);
1499 
1500   conn->reply_size_read = 0;
1501   conn->reply = g_malloc (conn->reply_size);
1502   g_input_stream_read_async (conn->reply_stream,
1503 			     conn->reply, conn->reply_size,
1504 			     0, NULL, read_reply_async_got_data, conn);
1505 }
1506 
1507 static void
read_reply_async(Connection * conn)1508 read_reply_async (Connection *conn)
1509 {
1510   conn->reply_size_read = 0;
1511   g_input_stream_read_async (conn->reply_stream,
1512                              &conn->reply_size, 4,
1513                              0, conn->reply_stream_cancellable,
1514                              read_reply_async_got_len,
1515                              conn);
1516 }
1517 
1518 static void send_command (Connection *conn);
1519 
1520 static void
send_command_data(GObject * source_object,GAsyncResult * result,gpointer user_data)1521 send_command_data (GObject *source_object,
1522                    GAsyncResult *result,
1523                    gpointer user_data)
1524 {
1525   Connection *conn = user_data;
1526   gssize res;
1527   DataBuffer *buffer;
1528 
1529   res = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), result, NULL);
1530 
1531   if (res <= 0)
1532     {
1533       g_warning ("Error sending command");
1534       fail_jobs_and_unmount (conn->op_backend, NULL);
1535       return;
1536     }
1537 
1538   buffer = conn->command_queue->data;
1539 
1540   conn->command_bytes_written += res;
1541 
1542   if (conn->command_bytes_written < buffer->size)
1543     {
1544       g_output_stream_write_async (conn->command_stream,
1545                                    buffer->data + conn->command_bytes_written,
1546                                    buffer->size - conn->command_bytes_written,
1547                                    0,
1548                                    NULL,
1549                                    send_command_data,
1550                                    conn);
1551       return;
1552     }
1553 
1554   data_buffer_free (buffer);
1555 
1556   conn->command_queue = g_list_delete_link (conn->command_queue, conn->command_queue);
1557 
1558   if (conn->command_queue != NULL)
1559     send_command (conn);
1560 }
1561 
1562 static void
send_command(Connection * conn)1563 send_command (Connection *conn)
1564 {
1565   DataBuffer *buffer;
1566 
1567   buffer = conn->command_queue->data;
1568 
1569   conn->command_bytes_written = 0;
1570   g_output_stream_write_async (conn->command_stream,
1571                                buffer->data,
1572                                buffer->size,
1573                                0,
1574                                NULL,
1575                                send_command_data,
1576                                conn);
1577 }
1578 
1579 static void
expect_reply(Connection * conn,guint32 id,ReplyCallback callback,GVfsJob * job,gpointer user_data)1580 expect_reply (Connection *conn,
1581               guint32 id,
1582               ReplyCallback callback,
1583               GVfsJob *job,
1584               gpointer user_data)
1585 {
1586   ExpectedReply *expected;
1587 
1588   expected = g_slice_new (ExpectedReply);
1589   expected->callback = callback;
1590   expected->job = g_object_ref (job);
1591   expected->user_data = user_data;
1592 
1593   g_hash_table_replace (conn->expected_replies, GINT_TO_POINTER (id), expected);
1594 }
1595 
1596 static DataBuffer *
data_buffer_new(guchar * data,gsize len)1597 data_buffer_new (guchar *data, gsize len)
1598 {
1599   DataBuffer *buffer;
1600 
1601   buffer = g_slice_new (DataBuffer);
1602   buffer->data = data;
1603   buffer->size = len;
1604 
1605   return buffer;
1606 }
1607 
1608 static void
queue_command_buffer(Connection * conn,DataBuffer * buffer)1609 queue_command_buffer (Connection *conn,
1610                       DataBuffer *buffer)
1611 {
1612   gboolean first;
1613 
1614   first = conn->command_queue == NULL;
1615 
1616   conn->command_queue = g_list_append (conn->command_queue, buffer);
1617 
1618   if (first)
1619     send_command (conn);
1620 }
1621 
1622 static void
queue_command_stream_and_free(Connection * conn,GDataOutputStream * command_stream,ReplyCallback callback,GVfsJob * job,gpointer user_data)1623 queue_command_stream_and_free (Connection *conn,
1624                                GDataOutputStream *command_stream,
1625                                ReplyCallback callback,
1626                                GVfsJob *job,
1627                                gpointer user_data)
1628 {
1629   gpointer data;
1630   gsize len;
1631   DataBuffer *buffer;
1632   guint32 id;
1633 
1634   id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (command_stream), id_q));
1635   data = get_data_from_command_stream (command_stream, &len);
1636 
1637   buffer = data_buffer_new (data, len);
1638   g_object_unref (command_stream);
1639 
1640   expect_reply (conn, id, callback, job, user_data);
1641   queue_command_buffer (conn, buffer);
1642 }
1643 
1644 
1645 static void
multi_request_cb(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply_stream,guint32 len,GVfsJob * job,gpointer user_data)1646 multi_request_cb (GVfsBackendSftp *backend,
1647                   int reply_type,
1648                   GDataInputStream *reply_stream,
1649                   guint32 len,
1650                   GVfsJob *job,
1651                   gpointer user_data)
1652 {
1653   MultiReply *reply;
1654   MultiRequest *request;
1655   int i;
1656 
1657   reply = user_data;
1658   request = reply->request;
1659 
1660   reply->type = reply_type;
1661   reply->data = g_object_ref (reply_stream);
1662   reply->data_len = len;
1663 
1664   if (--request->n_outstanding == 0)
1665     {
1666       /* Call callback */
1667       if (request->callback != NULL)
1668         (request->callback) (backend,
1669                              request->replies,
1670                              request->n_replies,
1671                              job,
1672                              request->user_data);
1673 
1674       /* Free request data */
1675 
1676       for (i = 0; i < request->n_replies; i++)
1677         {
1678           reply = &request->replies[i];
1679           if (reply->data)
1680             g_object_unref (reply->data);
1681         }
1682       g_free (request->replies);
1683 
1684       g_free (request);
1685 
1686     }
1687 }
1688 
1689 typedef struct
1690 {
1691   Connection *connection;
1692   GDataOutputStream *cmd;
1693 } Command;
1694 
1695 static void
queue_command_streams_and_free(Command * commands,int n_commands,MultiReplyCallback callback,GVfsJob * job,gpointer user_data)1696 queue_command_streams_and_free (Command *commands,
1697                                 int n_commands,
1698                                 MultiReplyCallback callback,
1699                                 GVfsJob *job,
1700                                 gpointer user_data)
1701 {
1702   MultiRequest *data;
1703   MultiReply *reply;
1704 
1705   int i;
1706 
1707   data = g_new0 (MultiRequest, 1);
1708 
1709   data->user_data = user_data;
1710   data->n_replies = n_commands;
1711   data->n_outstanding = n_commands;
1712   data->replies = g_new0 (MultiReply, n_commands);
1713   data->callback = callback;
1714 
1715   for (i = 0; i < n_commands; i++)
1716     {
1717       reply = &data->replies[i];
1718       reply->request = data;
1719       queue_command_stream_and_free (commands[i].connection,
1720                                      commands[i].cmd,
1721                                      multi_request_cb,
1722                                      job,
1723                                      reply);
1724     }
1725 }
1726 
1727 static gboolean
get_uid_sync(GVfsBackendSftp * backend)1728 get_uid_sync (GVfsBackendSftp *backend)
1729 {
1730   GDataOutputStream *command;
1731   GDataInputStream *reply;
1732   int type;
1733 
1734   command = new_command_stream (backend, SSH_FXP_STAT);
1735   put_string (command, ".");
1736   send_command_sync_and_unref_command (&backend->command_connection,
1737                                        command,
1738                                        NULL, NULL);
1739 
1740   reply = read_reply_sync (&backend->command_connection, NULL, NULL);
1741   if (reply == NULL)
1742     return FALSE;
1743 
1744   type = g_data_input_stream_read_byte (reply, NULL, NULL);
1745   /*id =*/ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1746 
1747   /* On error, set uid to -1 and ignore */
1748   backend->my_uid = (guint32)-1;
1749   backend->my_gid = (guint32)-1;
1750   if (type == SSH_FXP_ATTRS)
1751     {
1752       GFileInfo *info;
1753 
1754       info = g_file_info_new ();
1755       parse_attributes (backend, info, NULL, reply, NULL);
1756       if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID))
1757         {
1758           /* Both are always set if set */
1759           backend->my_uid = g_file_info_get_attribute_uint32 (info,
1760                                                               G_FILE_ATTRIBUTE_UNIX_UID);
1761           backend->my_gid = g_file_info_get_attribute_uint32 (info,
1762                                                               G_FILE_ATTRIBUTE_UNIX_GID);
1763         }
1764 
1765       g_object_unref (info);
1766     }
1767 
1768   g_object_unref (reply);
1769 
1770   return TRUE;
1771 }
1772 
1773 static gboolean
get_home_sync(GVfsBackendSftp * backend)1774 get_home_sync (GVfsBackendSftp *backend)
1775 {
1776   GDataOutputStream *command;
1777   GDataInputStream *reply;
1778   char *home_path;
1779   int type;
1780 
1781   command = new_command_stream (backend, SSH_FXP_REALPATH);
1782   put_string (command, ".");
1783   send_command_sync_and_unref_command (&backend->command_connection,
1784                                        command,
1785                                        NULL, NULL);
1786 
1787   reply = read_reply_sync (&backend->command_connection, NULL, NULL);
1788   if (reply == NULL)
1789     return FALSE;
1790 
1791   type = g_data_input_stream_read_byte (reply, NULL, NULL);
1792   /*id =*/ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1793 
1794   /* On error, set home to NULL and ignore */
1795   if (type == SSH_FXP_NAME)
1796     {
1797     /* count = */ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
1798 
1799       home_path = read_string (reply, NULL);
1800       g_vfs_backend_set_default_location (G_VFS_BACKEND (backend), home_path);
1801       g_free (home_path);
1802     }
1803 
1804   g_object_unref (reply);
1805 
1806   return TRUE;
1807 }
1808 
1809 static void
1810 do_mount (GVfsBackend *backend,
1811           GVfsJobMount *job,
1812           GMountSpec *mount_spec,
1813           GMountSource *mount_source,
1814           gboolean is_automount);
1815 
1816 static gboolean
setup_connection(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount,Connection * connection,gboolean initial_connection,GError ** error)1817 setup_connection (GVfsBackend *backend,
1818                   GVfsJobMount *job,
1819                   GMountSpec *mount_spec,
1820                   GMountSource *mount_source,
1821                   gboolean is_automount,
1822                   Connection *connection,
1823                   gboolean initial_connection,
1824                   GError **error)
1825 {
1826   const struct {
1827     const char *name;               /* extension_name field */
1828     const char *data;               /* extension_data field */
1829     SFTPServerExtensions enable;    /* flag to enable this extension */
1830   } extensions[] = {
1831     { "statvfs@openssh.com", "2", SFTP_EXT_OPENSSH_STATVFS },
1832   };
1833 
1834   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1835   gchar **args;
1836   pid_t pid;
1837   int tty_fd, stdout_fd, stdin_fd, stderr_fd, slave_fd;
1838   GInputStream *is;
1839   GDataOutputStream *command;
1840   GDataInputStream *reply;
1841   gboolean res;
1842   char *extension_name, *extension_data;
1843   int i;
1844 
1845   args = setup_ssh_commandline (backend);
1846 
1847   if (!spawn_ssh (backend,
1848 		  args, &pid,
1849 		  &tty_fd, &stdin_fd, &stdout_fd, &stderr_fd, &slave_fd,
1850 		  error))
1851     {
1852       g_strfreev (args);
1853       return FALSE;
1854     }
1855 
1856   g_strfreev (args);
1857 
1858   connection->op_backend = op_backend;
1859   connection->command_stream = g_unix_output_stream_new (stdin_fd, TRUE);
1860   connection->expected_replies = g_hash_table_new_full (NULL, NULL, NULL,
1861                                                         (GDestroyNotify)expected_reply_free);
1862 
1863   command = new_command_stream (op_backend, SSH_FXP_INIT);
1864   g_data_output_stream_put_int32 (command,
1865                                   SSH_FILEXFER_VERSION, NULL, NULL);
1866   send_command_sync_and_unref_command (connection, command, NULL, NULL);
1867 
1868   if (tty_fd == -1)
1869     res = wait_for_reply (backend, stdout_fd, error);
1870   else
1871     {
1872       res = handle_login (backend,
1873                           mount_source,
1874                           tty_fd, stdout_fd, stderr_fd,
1875                           initial_connection,
1876                           error);
1877       close (slave_fd);
1878     }
1879 
1880   if (!res)
1881     {
1882       if (error && (*error)->code == G_IO_ERROR_INVALID_ARGUMENT)
1883         {
1884 	  /* New username provided by the user,
1885 	   * we need to re-spawn the ssh command
1886 	   */
1887 	  g_clear_error (error);
1888 	  do_mount (backend, job, mount_spec, mount_source, is_automount);
1889 	}
1890 
1891       return FALSE;
1892     }
1893 
1894   connection->reply_stream = g_unix_input_stream_new (stdout_fd, TRUE);
1895   connection->reply_stream_cancellable = g_cancellable_new ();
1896 
1897   make_fd_nonblocking (stderr_fd);
1898   is = g_unix_input_stream_new (stderr_fd, TRUE);
1899   connection->error_stream = g_data_input_stream_new (is);
1900   g_object_unref (is);
1901 
1902   reply = read_reply_sync (connection, NULL, NULL);
1903   if (reply == NULL)
1904     {
1905       look_for_stderr_errors (connection, error);
1906       return FALSE;
1907     }
1908 
1909   if (g_data_input_stream_read_byte (reply, NULL, NULL) != SSH_FXP_VERSION)
1910     {
1911       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
1912       return FALSE;
1913     }
1914 
1915   op_backend->protocol_version = g_data_input_stream_read_uint32 (reply, NULL, NULL);
1916 
1917   while ((extension_name = read_string (reply, NULL)) != NULL)
1918     {
1919       extension_data = read_string (reply, NULL);
1920       if (extension_data)
1921         {
1922           for (i = 0; i < G_N_ELEMENTS (extensions); i++)
1923             {
1924               if (!strcmp (extension_name, extensions[i].name) &&
1925                   !strcmp (extension_data, extensions[i].data))
1926                 op_backend->extensions |= 1 << extensions[i].enable;
1927             }
1928         }
1929       g_free (extension_name);
1930       g_free (extension_data);
1931     }
1932 
1933   g_object_unref (reply);
1934 
1935   if (initial_connection &&
1936       (!get_uid_sync (op_backend) || !get_home_sync (op_backend)))
1937     {
1938       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Protocol error"));
1939       return FALSE;
1940     }
1941 
1942   g_object_ref (op_backend);
1943   read_reply_async (connection);
1944 
1945   return TRUE;
1946 }
1947 
1948 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)1949 do_mount (GVfsBackend *backend,
1950           GVfsJobMount *job,
1951           GMountSpec *mount_spec,
1952           GMountSource *mount_source,
1953           gboolean is_automount)
1954 {
1955   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
1956   GError *error = NULL;
1957   GMountSpec *sftp_mount_spec;
1958   char *display_name;
1959 
1960   if (!setup_connection (backend,
1961                          job,
1962                          mount_spec,
1963                          mount_source,
1964                          is_automount,
1965                          &op_backend->command_connection,
1966                          TRUE,
1967                          &error))
1968     {
1969       if (error)
1970         {
1971           g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1972           g_error_free (error);
1973         }
1974       /* When a new user is specified, do_mount is called recursively so we
1975        * need to return here without finishing the job. */
1976       return;
1977     }
1978 
1979   if (!setup_connection (backend,
1980                          job,
1981                          mount_spec,
1982                          mount_source,
1983                          is_automount,
1984                          &op_backend->data_connection,
1985                          FALSE,
1986                          NULL))
1987     {
1988       g_warning ("Setting up data connection failed\n");
1989       destroy_connection (&op_backend->data_connection);
1990     }
1991 
1992   g_clear_pointer (&op_backend->tmp_password, g_free);
1993 
1994   sftp_mount_spec = g_mount_spec_new ("sftp");
1995   if (op_backend->user_specified_in_uri)
1996     g_mount_spec_set (sftp_mount_spec, "user", op_backend->user);
1997   g_mount_spec_set (sftp_mount_spec, "host", op_backend->host);
1998   if (op_backend->port != -1)
1999     {
2000       char *v;
2001       v = g_strdup_printf ("%d", op_backend->port);
2002       g_mount_spec_set (sftp_mount_spec, "port", v);
2003       g_free (v);
2004     }
2005 
2006   g_vfs_backend_set_mount_spec (backend, sftp_mount_spec);
2007   g_mount_spec_unref (sftp_mount_spec);
2008 
2009   if (op_backend->user_specified_in_uri)
2010     /* Translators: This is the name of an SFTP share, like "<user> on <hostname>" */
2011     display_name = g_strdup_printf (_("%s on %s"), op_backend->user, op_backend->host);
2012   else
2013     display_name = g_strdup (op_backend->host);
2014   g_vfs_backend_set_display_name (backend, display_name);
2015   g_free (display_name);
2016 
2017   g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "folder-remote");
2018   g_vfs_backend_set_symbolic_icon_name (G_VFS_BACKEND (backend), "folder-remote-symbolic");
2019   g_vfs_job_succeeded (G_VFS_JOB (job));
2020 }
2021 
2022 static void
real_do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)2023 real_do_mount (GVfsBackend *backend,
2024 	       GVfsJobMount *job,
2025 	       GMountSpec *mount_spec,
2026 	       GMountSource *mount_source,
2027 	       gboolean is_automount)
2028 {
2029   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2030 
2031   setup_ssh_environment ();
2032 
2033   op_backend->password_save = G_PASSWORD_SAVE_NEVER;
2034   do_mount (backend, job, mount_spec, mount_source, is_automount);
2035 }
2036 
2037 static gboolean
try_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)2038 try_mount (GVfsBackend *backend,
2039            GVfsJobMount *job,
2040            GMountSpec *mount_spec,
2041            GMountSource *mount_source,
2042            gboolean is_automount)
2043 {
2044   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2045   const char *user, *host, *port;
2046 
2047   op_backend->client_vendor = get_sftp_client_vendor ();
2048 
2049   if (op_backend->client_vendor == SFTP_VENDOR_INVALID)
2050     {
2051       g_vfs_job_failed (G_VFS_JOB (job),
2052 			G_IO_ERROR, G_IO_ERROR_FAILED,
2053 			_("Unable to find supported SSH command"));
2054       return TRUE;
2055     }
2056 
2057   host = g_mount_spec_get (mount_spec, "host");
2058 
2059   if (host == NULL)
2060     {
2061       g_vfs_job_failed (G_VFS_JOB (job),
2062 			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
2063 			_("No hostname specified"));
2064       return TRUE;
2065     }
2066 
2067   port = g_mount_spec_get (mount_spec, "port");
2068   op_backend->port = -1;
2069   if (port != NULL)
2070     {
2071       int p = atoi (port);
2072       if (p != 22)
2073         op_backend->port = p;
2074     }
2075 
2076   user = g_mount_spec_get (mount_spec, "user");
2077 
2078   op_backend->host = g_strdup (host);
2079   op_backend->user = g_strdup (user);
2080   if (op_backend->user)
2081     {
2082       op_backend->user_specified = TRUE;
2083       op_backend->user_specified_in_uri = TRUE;
2084     }
2085 
2086 
2087   return FALSE;
2088 }
2089 
2090 static gboolean
try_unmount(GVfsBackend * backend,GVfsJobUnmount * job,GMountUnmountFlags flags,GMountSource * mount_source)2091 try_unmount (GVfsBackend *backend,
2092              GVfsJobUnmount *job,
2093              GMountUnmountFlags flags,
2094              GMountSource *mount_source)
2095 {
2096   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2097 
2098   if (op_backend->command_connection.reply_stream &&
2099       op_backend->command_connection.reply_stream_cancellable)
2100     g_cancellable_cancel (op_backend->command_connection.reply_stream_cancellable);
2101   if (op_backend->data_connection.reply_stream &&
2102       op_backend->data_connection.reply_stream_cancellable)
2103     g_cancellable_cancel (op_backend->data_connection.reply_stream_cancellable);
2104   g_vfs_job_succeeded (G_VFS_JOB (job));
2105 
2106   return TRUE;
2107 }
2108 
2109 static int
io_error_code_for_sftp_error(guint32 code,int failure_error)2110 io_error_code_for_sftp_error (guint32 code, int failure_error)
2111 {
2112   int error_code;
2113 
2114   error_code = G_IO_ERROR_FAILED;
2115 
2116   switch (code)
2117     {
2118     default:
2119     case SSH_FX_EOF:
2120     case SSH_FX_BAD_MESSAGE:
2121     case SSH_FX_NO_CONNECTION:
2122     case SSH_FX_CONNECTION_LOST:
2123       break;
2124 
2125     case SSH_FX_FAILURE:
2126       error_code = failure_error;
2127       break;
2128 
2129     case SSH_FX_NO_SUCH_FILE:
2130       error_code = G_IO_ERROR_NOT_FOUND;
2131       break;
2132 
2133     case SSH_FX_PERMISSION_DENIED:
2134       error_code = G_IO_ERROR_PERMISSION_DENIED;
2135       break;
2136 
2137     case SSH_FX_OP_UNSUPPORTED:
2138       error_code = G_IO_ERROR_NOT_SUPPORTED;
2139       break;
2140     }
2141   return error_code;
2142 }
2143 
2144 static char *
error_message(GIOErrorEnum error)2145 error_message (GIOErrorEnum error)
2146 {
2147   switch (error)
2148     {
2149     case G_IO_ERROR_NOT_EMPTY:
2150       return _("Directory not empty");
2151     case G_IO_ERROR_EXISTS:
2152       return _("Target file exists");
2153     case G_IO_ERROR_NOT_SUPPORTED:
2154       return _("Operation not supported");
2155     case G_IO_ERROR_PERMISSION_DENIED:
2156       return _("Permission denied");
2157     case G_IO_ERROR_NOT_FOUND:
2158       return _("No such file or directory");
2159     default:
2160       return _("Unknown reason");
2161     }
2162 }
2163 
2164 static gboolean
error_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error,GError ** error)2165 error_from_status_code (GVfsJob *job,
2166 			guint32 code,
2167 			int failure_error,
2168 			int allowed_sftp_error,
2169 			GError **error)
2170 {
2171   gint error_code;
2172 
2173   if (failure_error == -1)
2174     failure_error = G_IO_ERROR_FAILED;
2175 
2176   if (code == SSH_FX_OK ||
2177       (allowed_sftp_error != -1 &&
2178        code == allowed_sftp_error))
2179     return TRUE;
2180 
2181   if (error)
2182     {
2183       error_code = io_error_code_for_sftp_error (code, failure_error);
2184       *error = g_error_new_literal (G_IO_ERROR, error_code,
2185 				    error_message (error_code));
2186     }
2187 
2188   return FALSE;
2189 }
2190 
2191 static gboolean
failure_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error)2192 failure_from_status_code (GVfsJob *job,
2193 			  guint32 code,
2194 			  int failure_error,
2195 			  int allowed_sftp_error)
2196 {
2197   GError *error;
2198 
2199   error = NULL;
2200   if (error_from_status_code (job, code, failure_error, allowed_sftp_error, &error))
2201     return TRUE;
2202   else
2203     {
2204       g_vfs_job_failed_from_error (job, error);
2205       g_error_free (error);
2206     }
2207   return FALSE;
2208 }
2209 
2210 static gboolean
result_from_status_code(GVfsJob * job,guint32 code,int failure_error,int allowed_sftp_error)2211 result_from_status_code (GVfsJob *job,
2212 			 guint32 code,
2213 			 int failure_error,
2214 			 int allowed_sftp_error)
2215 {
2216   gboolean res;
2217 
2218   res = failure_from_status_code (job, code,
2219 				  failure_error,
2220 				  allowed_sftp_error);
2221   if (res)
2222     g_vfs_job_succeeded (job);
2223 
2224   return res;
2225 }
2226 
2227 static guint32
read_status_code(GDataInputStream * status_reply)2228 read_status_code (GDataInputStream *status_reply)
2229 {
2230   return g_data_input_stream_read_uint32 (status_reply, NULL, NULL);
2231 }
2232 
2233 static gboolean
error_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error,GError ** error)2234 error_from_status (GVfsJob *job,
2235                    GDataInputStream *reply,
2236                    int failure_error,
2237                    int allowed_sftp_error,
2238                    GError **error)
2239 {
2240   guint32 code;
2241 
2242   code = read_status_code (reply);
2243 
2244   return error_from_status_code (job, code,
2245 				 failure_error, allowed_sftp_error,
2246 				 error);
2247 }
2248 
2249 static gboolean
failure_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error)2250 failure_from_status (GVfsJob *job,
2251                      GDataInputStream *reply,
2252                      int failure_error,
2253                      int allowed_sftp_error)
2254 {
2255   GError *error;
2256 
2257   error = NULL;
2258   if (error_from_status (job, reply, failure_error, allowed_sftp_error, &error))
2259     return TRUE;
2260   else
2261     {
2262       g_vfs_job_failed_from_error (job, error);
2263       g_error_free (error);
2264     }
2265   return FALSE;
2266 }
2267 
2268 static gboolean
result_from_status(GVfsJob * job,GDataInputStream * reply,int failure_error,int allowed_sftp_error)2269 result_from_status (GVfsJob *job,
2270                     GDataInputStream *reply,
2271                     int failure_error,
2272                     int allowed_sftp_error)
2273 {
2274   gboolean res;
2275 
2276   res = failure_from_status (job, reply,
2277                              failure_error,
2278                              allowed_sftp_error);
2279   if (res)
2280     g_vfs_job_succeeded (job);
2281 
2282   return res;
2283 }
2284 
2285 
2286 
2287 typedef struct _ErrorFromStatData ErrorFromStatData;
2288 
2289 typedef void (*ErrorFromStatCallback) (GVfsBackendSftp *backend,
2290 				       GVfsJob *job,
2291 				       gint original_error,
2292 				       gint stat_error,
2293 				       GFileInfo *info,
2294 				       gpointer user_data);
2295 
2296 
2297 struct _ErrorFromStatData {
2298   gint original_error;
2299   ErrorFromStatCallback callback;
2300   gpointer user_data;
2301 };
2302 
2303 static void
error_from_lstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2304 error_from_lstat_reply (GVfsBackendSftp *backend,
2305 			int reply_type,
2306 			GDataInputStream *reply,
2307 			guint32 len,
2308 			GVfsJob *job,
2309 			gpointer user_data)
2310 {
2311   ErrorFromStatData *data = user_data;
2312   GFileInfo *info;
2313   gint stat_error;
2314 
2315   stat_error = 0;
2316   info = NULL;
2317   if (reply_type == SSH_FXP_STATUS)
2318     stat_error = read_status_code (reply);
2319   else if (reply_type == SSH_FXP_ATTRS)
2320     {
2321       info = g_file_info_new ();
2322       parse_attributes (backend,
2323 			info,
2324 			NULL,
2325 			reply,
2326 			NULL);
2327     }
2328   else
2329     {
2330       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2331                         _("Invalid reply received"));
2332       goto out;
2333     }
2334 
2335   data->callback (backend, job,
2336 		  data->original_error,
2337 		  stat_error,
2338 		  info,
2339 		  data->user_data);
2340 
2341  out:
2342   g_slice_free (ErrorFromStatData, data);
2343 }
2344 
2345 static void
error_from_lstat(GVfsBackendSftp * backend,GVfsJob * job,guint32 original_error,const char * path,ErrorFromStatCallback callback,gpointer user_data)2346 error_from_lstat (GVfsBackendSftp *backend,
2347 		  GVfsJob *job,
2348 		  guint32 original_error,
2349 		  const char *path,
2350 		  ErrorFromStatCallback callback,
2351 		  gpointer user_data)
2352 {
2353   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2354   GDataOutputStream *command;
2355   ErrorFromStatData *data;
2356 
2357   command = new_command_stream (op_backend, SSH_FXP_LSTAT);
2358   put_string (command, path);
2359 
2360   data = g_slice_new (ErrorFromStatData);
2361   data->original_error = original_error;
2362   data->callback = callback;
2363   data->user_data = user_data;
2364   queue_command_stream_and_free (&op_backend->command_connection, command,
2365                                  error_from_lstat_reply,
2366 				 G_VFS_JOB (job), data);
2367 }
2368 
2369 static void
set_access_attributes_trusted(GFileInfo * info,guint32 perm)2370 set_access_attributes_trusted (GFileInfo *info,
2371 			       guint32 perm)
2372 {
2373   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
2374 				     perm & 0x4);
2375   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
2376 				     perm & 0x2);
2377   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
2378 				     perm & 0x1);
2379 }
2380 
2381 /* For files we don't own we can't trust a negative response to this check, as
2382    something else could allow us to do the operation, for instance an ACL
2383    or some sticky bit thing */
2384 static void
set_access_attributes(GFileInfo * info,guint32 perm)2385 set_access_attributes (GFileInfo *info,
2386                        guint32 perm)
2387 {
2388   if (perm & 0x4)
2389     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
2390 				       TRUE);
2391   if (perm & 0x2)
2392     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
2393 				       TRUE);
2394   if (perm & 0x1)
2395     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
2396 				       TRUE);
2397 }
2398 
2399 
2400 static void
parse_attributes(GVfsBackendSftp * backend,GFileInfo * info,const char * basename,GDataInputStream * reply,GFileAttributeMatcher * matcher)2401 parse_attributes (GVfsBackendSftp *backend,
2402                   GFileInfo *info,
2403                   const char *basename,
2404                   GDataInputStream *reply,
2405                   GFileAttributeMatcher *matcher)
2406 {
2407   guint32 flags;
2408   GFileType type;
2409   guint32 uid, gid;
2410   guint32 mode;
2411   gboolean has_uid, free_mimetype;
2412   char *mimetype;
2413   gboolean uncertain_content_type;
2414 
2415   flags = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2416 
2417   if (basename != NULL && basename[0] == '.')
2418     g_file_info_set_is_hidden (info, TRUE);
2419 
2420   if (basename != NULL)
2421     g_file_info_set_name (info, basename);
2422   else
2423     g_file_info_set_name (info, "/");
2424 
2425   if (basename != NULL && basename[strlen (basename) -1] == '~')
2426     g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP, TRUE);
2427 
2428   if (flags & SSH_FILEXFER_ATTR_SIZE)
2429     {
2430       guint64 size = g_data_input_stream_read_uint64 (reply, NULL, NULL);
2431       g_file_info_set_size (info, size);
2432     }
2433 
2434   has_uid = FALSE;
2435   uid = gid = 0; /* Avoid warnings */
2436   if (flags & SSH_FILEXFER_ATTR_UIDGID)
2437     {
2438       has_uid = TRUE;
2439       uid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2440       g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, uid);
2441       gid = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2442       g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, gid);
2443     }
2444 
2445   type = G_FILE_TYPE_UNKNOWN;
2446 
2447   if (flags & SSH_FILEXFER_ATTR_PERMISSIONS)
2448     {
2449       mode = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2450       g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, mode);
2451 
2452       mimetype = NULL;
2453       uncertain_content_type = FALSE;
2454       if (S_ISREG (mode))
2455         type = G_FILE_TYPE_REGULAR;
2456       else if (S_ISDIR (mode))
2457         {
2458           type = G_FILE_TYPE_DIRECTORY;
2459           mimetype = "inode/directory";
2460         }
2461       else if (S_ISFIFO (mode))
2462         {
2463           type = G_FILE_TYPE_SPECIAL;
2464           mimetype = "inode/fifo";
2465         }
2466       else if (S_ISSOCK (mode))
2467         {
2468           type = G_FILE_TYPE_SPECIAL;
2469           mimetype = "inode/socket";
2470         }
2471       else if (S_ISCHR (mode))
2472         {
2473           type = G_FILE_TYPE_SPECIAL;
2474           mimetype = "inode/chardevice";
2475         }
2476       else if (S_ISBLK (mode))
2477         {
2478           type = G_FILE_TYPE_SPECIAL;
2479           mimetype = "inode/blockdevice";
2480         }
2481       else if (S_ISLNK (mode))
2482         {
2483           type = G_FILE_TYPE_SYMBOLIC_LINK;
2484           g_file_info_set_is_symlink (info, TRUE);
2485           mimetype = "inode/symlink";
2486         }
2487 
2488       free_mimetype = FALSE;
2489       if (mimetype == NULL)
2490         {
2491           if (basename)
2492             {
2493               mimetype = g_content_type_guess (basename, NULL, 0, &uncertain_content_type);
2494               free_mimetype = TRUE;
2495             }
2496           else
2497             mimetype = "application/octet-stream";
2498         }
2499 
2500       if (!uncertain_content_type)
2501         g_file_info_set_content_type (info, mimetype);
2502       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mimetype);
2503 
2504       if (g_file_attribute_matcher_matches (matcher,
2505                                             G_FILE_ATTRIBUTE_STANDARD_ICON)
2506           || g_file_attribute_matcher_matches (matcher,
2507                                                G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON))
2508         {
2509           GIcon *icon = NULL;
2510           GIcon *symbolic_icon = NULL;
2511 
2512           icon = g_content_type_get_icon (mimetype);
2513           symbolic_icon = g_content_type_get_symbolic_icon (mimetype);
2514 
2515           if (icon == NULL)
2516             icon = g_themed_icon_new ("text-x-generic");
2517           if (symbolic_icon == NULL)
2518             symbolic_icon = g_themed_icon_new ("text-x-generic-symbolic");
2519 
2520           g_file_info_set_icon (info, icon);
2521           g_file_info_set_symbolic_icon (info, symbolic_icon);
2522           g_object_unref (icon);
2523           g_object_unref (symbolic_icon);
2524         }
2525 
2526 
2527       if (free_mimetype)
2528         g_free (mimetype);
2529 
2530       if (has_uid && backend->my_uid != (guint32)-1)
2531         {
2532           if (uid == backend->my_uid)
2533             set_access_attributes_trusted (info, (mode >> 6) & 0x7);
2534           else if (gid == backend->my_gid)
2535             set_access_attributes (info, (mode >> 3) & 0x7);
2536           else
2537             set_access_attributes (info, (mode >> 0) & 0x7);
2538         }
2539 
2540     }
2541 
2542   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
2543 
2544   g_file_info_set_file_type (info, type);
2545 
2546   if (flags & SSH_FILEXFER_ATTR_ACMODTIME)
2547     {
2548       guint32 v;
2549       char *etag;
2550 
2551       v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2552       g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, v);
2553       v = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2554       g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, v);
2555 
2556       etag = g_strdup_printf ("%lu", (long unsigned int)v);
2557       g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
2558       g_free (etag);
2559     }
2560 
2561   if (flags & SSH_FILEXFER_ATTR_EXTENDED)
2562     {
2563       guint32 count, i;
2564       char *name, *val;
2565       count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2566       for (i = 0; i < count; i++)
2567         {
2568           name = read_string (reply, NULL);
2569           val = read_string (reply, NULL);
2570 
2571           g_free (name);
2572           g_free (val);
2573         }
2574     }
2575 
2576   /* We use the same setting as for local files. Can't really
2577    * do better, since there is no way in this version of sftp to find out
2578    * the remote charset encoding
2579    */
2580   if (g_file_attribute_matcher_matches (matcher,
2581                                         G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME))
2582     {
2583       if (basename != NULL)
2584         {
2585           char *display_name = g_filename_display_name (basename);
2586 
2587           if (strstr (display_name, "\357\277\275") != NULL)
2588             {
2589               char *p = display_name;
2590               display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
2591               g_free (p);
2592             }
2593           g_file_info_set_display_name (info, display_name);
2594           g_free (display_name);
2595         }
2596       else
2597         {
2598           char *name;
2599 
2600 
2601           /* Translators: This is the name of the root of an SFTP share, like "/ on <hostname>" */
2602           name = g_strdup_printf (_("/ on %s"), G_VFS_BACKEND_SFTP (backend)->host);
2603           g_file_info_set_display_name (info, name);
2604           g_free (name);
2605         }
2606     }
2607 
2608   if (basename != NULL &&
2609       g_file_attribute_matcher_matches (matcher,
2610                                         G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME))
2611     {
2612       char *edit_name = g_filename_display_name (basename);
2613       g_file_info_set_edit_name (info, edit_name);
2614       g_free (edit_name);
2615     }
2616 }
2617 
2618 static SftpHandle *
sftp_handle_new(GDataInputStream * reply)2619 sftp_handle_new (GDataInputStream *reply)
2620 {
2621   SftpHandle *handle;
2622 
2623   handle = g_slice_new0 (SftpHandle);
2624   handle->raw_handle = read_data_buffer (reply);
2625   handle->offset = 0;
2626 
2627   return handle;
2628 }
2629 
2630 static void
sftp_handle_free(SftpHandle * handle)2631 sftp_handle_free (SftpHandle *handle)
2632 {
2633   data_buffer_free (handle->raw_handle);
2634   g_free (handle->filename);
2635   g_free (handle->tempname);
2636   g_slice_free (SftpHandle, handle);
2637 }
2638 
2639 static void
open_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2640 open_stat_reply (GVfsBackendSftp *backend,
2641                  int reply_type,
2642                  GDataInputStream *reply,
2643                  guint32 len,
2644                  GVfsJob *job,
2645                  gpointer user_data)
2646 {
2647   if (g_vfs_job_is_finished (job))
2648     {
2649       /* Handled in stat reply */
2650       return;
2651     }
2652 
2653   if (reply_type == SSH_FXP_ATTRS)
2654     {
2655       GFileType type;
2656       GFileInfo *info = g_file_info_new ();
2657 
2658       parse_attributes (backend, info, NULL,
2659                         reply, NULL);
2660       type = g_file_info_get_file_type (info);
2661       g_object_unref (info);
2662 
2663       if (type == G_FILE_TYPE_DIRECTORY)
2664         {
2665           g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
2666                             _("File is directory"));
2667           return;
2668         }
2669     }
2670 
2671   if (GPOINTER_TO_INT (G_VFS_JOB(job)->backend_data) == 1)
2672     {
2673       /* We ran the read_reply and it was a generic failure */
2674       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2675                         _("Failure"));
2676     }
2677 }
2678 
2679 static void
open_for_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2680 open_for_read_reply (GVfsBackendSftp *backend,
2681                      int reply_type,
2682                      GDataInputStream *reply,
2683                      guint32 len,
2684                      GVfsJob *job,
2685                      gpointer user_data)
2686 {
2687   SftpHandle *handle;
2688 
2689   if (g_vfs_job_is_finished (job))
2690     {
2691       /* Handled in stat reply */
2692 
2693       /* Normally this should not happen as we
2694          sent an is_dir error. But i guess we can
2695          race */
2696       if (reply_type == SSH_FXP_HANDLE)
2697         {
2698           GDataOutputStream *command;
2699           DataBuffer *bhandle;
2700 
2701           bhandle = read_data_buffer (reply);
2702 
2703           command = new_command_stream (backend, SSH_FXP_CLOSE);
2704           put_data_buffer (command, bhandle);
2705           queue_command_stream_and_free (&backend->command_connection, command,
2706                                          NULL,
2707                                          G_VFS_JOB (job), NULL);
2708 
2709           data_buffer_free (bhandle);
2710         }
2711 
2712       return;
2713     }
2714 
2715   if (reply_type == SSH_FXP_STATUS)
2716     {
2717       if (failure_from_status (job,
2718                                reply,
2719                                -1,
2720                                SSH_FX_FAILURE))
2721         {
2722           /* Unknown failure type, mark that we got this and
2723              return result from stat result */
2724           G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (1);
2725         }
2726 
2727       return;
2728     }
2729 
2730   if (reply_type != SSH_FXP_HANDLE)
2731     {
2732       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2733                         _("Invalid reply received"));
2734       return;
2735     }
2736 
2737   handle = sftp_handle_new (reply);
2738 
2739   g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), handle);
2740   g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
2741   g_vfs_job_succeeded (job);
2742 }
2743 
2744 static gboolean
try_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)2745 try_open_for_read (GVfsBackend *backend,
2746                    GVfsJobOpenForRead *job,
2747                    const char *filename)
2748 {
2749   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2750   GDataOutputStream *command;
2751 
2752   G_VFS_JOB(job)->backend_data = GINT_TO_POINTER (0);
2753 
2754   command = new_command_stream (op_backend,
2755                                 SSH_FXP_STAT);
2756   put_string (command, filename);
2757   queue_command_stream_and_free (&op_backend->command_connection, command,
2758                                  open_stat_reply,
2759                                  G_VFS_JOB (job), NULL);
2760 
2761   command = new_command_stream (op_backend,
2762                                 SSH_FXP_OPEN);
2763   put_string (command, filename);
2764   g_data_output_stream_put_uint32 (command, SSH_FXF_READ, NULL, NULL); /* open flags */
2765   g_data_output_stream_put_uint32 (command, 0, NULL, NULL); /* Attr flags */
2766 
2767   queue_command_stream_and_free (&op_backend->command_connection, command,
2768                                  open_for_read_reply,
2769                                  G_VFS_JOB (job), NULL);
2770 
2771   return TRUE;
2772 }
2773 
2774 static void
read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2775 read_reply (GVfsBackendSftp *backend,
2776             int reply_type,
2777             GDataInputStream *reply,
2778             guint32 len,
2779             GVfsJob *job,
2780             gpointer user_data)
2781 {
2782   SftpHandle *handle;
2783   guint32 count;
2784 
2785   handle = user_data;
2786 
2787   if (reply_type == SSH_FXP_STATUS)
2788     {
2789       result_from_status (job, reply, -1, SSH_FX_EOF);
2790       return;
2791     }
2792 
2793   if (reply_type != SSH_FXP_DATA)
2794     {
2795       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2796                         _("Invalid reply received"));
2797       return;
2798     }
2799 
2800   count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
2801 
2802   if (!g_input_stream_read_all (G_INPUT_STREAM (reply),
2803                                 G_VFS_JOB_READ (job)->buffer, count,
2804                                 NULL, NULL, NULL))
2805     {
2806       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2807                         _("Invalid reply received"));
2808       return;
2809     }
2810 
2811   handle->offset += count;
2812 
2813   g_vfs_job_read_set_size (G_VFS_JOB_READ (job), count);
2814   g_vfs_job_succeeded (job);
2815 }
2816 
2817 static gboolean
try_read(GVfsBackend * backend,GVfsJobRead * job,GVfsBackendHandle _handle,char * buffer,gsize bytes_requested)2818 try_read (GVfsBackend *backend,
2819           GVfsJobRead *job,
2820           GVfsBackendHandle _handle,
2821           char *buffer,
2822           gsize bytes_requested)
2823 {
2824   SftpHandle *handle = _handle;
2825   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2826   GDataOutputStream *command;
2827 
2828   command = new_command_stream (op_backend,
2829                                 SSH_FXP_READ);
2830   put_data_buffer (command, handle->raw_handle);
2831   g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
2832   g_data_output_stream_put_uint32 (command, bytes_requested, NULL, NULL);
2833 
2834   queue_command_stream_and_free (&op_backend->command_connection, command,
2835                                  read_reply,
2836                                  G_VFS_JOB (job), handle);
2837 
2838   return TRUE;
2839 }
2840 
2841 static void
seek_read_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2842 seek_read_fstat_reply (GVfsBackendSftp *backend,
2843                        int reply_type,
2844                        GDataInputStream *reply,
2845                        guint32 len,
2846                        GVfsJob *job,
2847                        gpointer user_data)
2848 {
2849   SftpHandle *handle;
2850   GFileInfo *info;
2851   goffset file_size;
2852   GVfsJobSeekRead *op_job;
2853 
2854   handle = user_data;
2855 
2856   if (reply_type == SSH_FXP_STATUS)
2857     {
2858       result_from_status (job, reply, -1, -1);
2859       return;
2860     }
2861 
2862   if (reply_type != SSH_FXP_ATTRS)
2863     {
2864       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2865                         _("Invalid reply received"));
2866       return;
2867     }
2868 
2869   info = g_file_info_new ();
2870   parse_attributes (backend, info, NULL,
2871                     reply, NULL);
2872   file_size = g_file_info_get_size (info);
2873   g_object_unref (info);
2874 
2875   op_job = G_VFS_JOB_SEEK_READ (job);
2876 
2877   handle->offset = file_size + op_job->requested_offset;
2878 
2879   if (handle->offset < 0)
2880     handle->offset = 0;
2881 
2882   g_vfs_job_seek_read_set_offset (op_job, handle->offset);
2883   g_vfs_job_succeeded (job);
2884 }
2885 
2886 static gboolean
try_seek_on_read(GVfsBackend * backend,GVfsJobSeekRead * job,GVfsBackendHandle _handle,goffset offset,GSeekType type)2887 try_seek_on_read (GVfsBackend *backend,
2888                   GVfsJobSeekRead *job,
2889                   GVfsBackendHandle _handle,
2890                   goffset    offset,
2891                   GSeekType  type)
2892 {
2893   SftpHandle *handle = _handle;
2894   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
2895   GDataOutputStream *command;
2896 
2897   switch (job->seek_type)
2898     {
2899     case G_SEEK_CUR:
2900       handle->offset += job->requested_offset;
2901       break;
2902     case G_SEEK_SET:
2903       handle->offset = job->requested_offset;
2904       break;
2905     case G_SEEK_END:
2906       command = new_command_stream (op_backend,
2907                                     SSH_FXP_FSTAT);
2908       put_data_buffer (command, handle->raw_handle);
2909       queue_command_stream_and_free (&op_backend->command_connection, command,
2910                                      seek_read_fstat_reply,
2911                                      G_VFS_JOB (job), handle);
2912       return TRUE;
2913     }
2914 
2915   if (handle->offset < 0)
2916     handle->offset = 0;
2917 
2918   g_vfs_job_seek_read_set_offset (job, handle->offset);
2919   g_vfs_job_succeeded (G_VFS_JOB (job));
2920 
2921   return TRUE;
2922 }
2923 
2924 static void
delete_temp_file(GVfsBackendSftp * backend,SftpHandle * handle,GVfsJob * job)2925 delete_temp_file (GVfsBackendSftp *backend,
2926                   SftpHandle *handle,
2927                   GVfsJob *job)
2928 {
2929   GDataOutputStream *command;
2930 
2931   if (handle->tempname)
2932     {
2933       command = new_command_stream (backend,
2934                                     SSH_FXP_REMOVE);
2935       put_string (command, handle->tempname);
2936       queue_command_stream_and_free (&backend->command_connection, command,
2937                                      NULL,
2938                                      job, NULL);
2939     }
2940 }
2941 
2942 static void
close_moved_tempfile(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2943 close_moved_tempfile (GVfsBackendSftp *backend,
2944                       int reply_type,
2945                       GDataInputStream *reply,
2946                       guint32 len,
2947                       GVfsJob *job,
2948                       gpointer user_data)
2949 {
2950   SftpHandle *handle;
2951 
2952   handle = user_data;
2953 
2954   if (reply_type == SSH_FXP_STATUS)
2955     result_from_status (job, reply, -1, -1);
2956   else
2957     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
2958                       _("Invalid reply received"));
2959 
2960   /* On failure, don't remove tempfile, since we removed the new original file */
2961   sftp_handle_free (handle);
2962 }
2963 
2964 static void
close_restore_permissions(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2965 close_restore_permissions (GVfsBackendSftp *backend,
2966                            int reply_type,
2967                            GDataInputStream *reply,
2968                            guint32 len,
2969                            GVfsJob *job,
2970                            gpointer user_data)
2971 {
2972   GDataOutputStream *command;
2973   SftpHandle *handle;
2974 
2975   handle = user_data;
2976 
2977   /* Here we don't really care whether or not setting the permissions succeeded
2978      or not. We just take the last step and rename the temp file to the
2979      actual file */
2980   command = new_command_stream (backend,
2981                                 SSH_FXP_RENAME);
2982   put_string (command, handle->tempname);
2983   put_string (command, handle->filename);
2984   queue_command_stream_and_free (&backend->command_connection, command,
2985                                  close_moved_tempfile,
2986                                  G_VFS_JOB (job), handle);
2987 }
2988 
2989 static void
close_deleted_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)2990 close_deleted_file (GVfsBackendSftp *backend,
2991                     int reply_type,
2992                     GDataInputStream *reply,
2993                     guint32 len,
2994                     GVfsJob *job,
2995                     gpointer user_data)
2996 {
2997   GDataOutputStream *command;
2998   GError *error;
2999   gboolean res;
3000   SftpHandle *handle;
3001 
3002   handle = user_data;
3003 
3004   error = NULL;
3005   res = FALSE;
3006   if (reply_type == SSH_FXP_STATUS)
3007     res = error_from_status (job, reply, -1, -1, &error);
3008   else
3009     g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3010 	                 _("Invalid reply received"));
3011 
3012   if (res)
3013     {
3014       /* Removed original file, now first try to restore permissions */
3015       if (handle->set_permissions)
3016         {
3017           command = new_command_stream (backend, SSH_FXP_SETSTAT);
3018           put_string (command, handle->tempname);
3019           g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
3020           g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
3021           queue_command_stream_and_free (&backend->command_connection, command,
3022                                          close_restore_permissions,
3023                                          G_VFS_JOB (job), handle);
3024         }
3025       else
3026         {
3027           /* Skip restoring permissions by calling the callback directly */
3028           close_restore_permissions(backend, 0, NULL, 0, job, user_data);
3029         }
3030     }
3031   else
3032     {
3033       /* The delete failed, remove any temporary files */
3034       delete_temp_file (backend,
3035                         handle,
3036                         G_VFS_JOB (job));
3037 
3038       g_vfs_job_failed_from_error (job, error);
3039       g_error_free (error);
3040       sftp_handle_free (handle);
3041     }
3042 }
3043 
3044 static void
close_moved_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3045 close_moved_file (GVfsBackendSftp *backend,
3046                   int reply_type,
3047                   GDataInputStream *reply,
3048                   guint32 len,
3049                   GVfsJob *job,
3050                   gpointer user_data)
3051 {
3052   GDataOutputStream *command;
3053   GError *error;
3054   gboolean res;
3055   SftpHandle *handle;
3056 
3057   handle = user_data;
3058 
3059   error = NULL;
3060   res = FALSE;
3061   if (reply_type == SSH_FXP_STATUS)
3062     res = error_from_status (job, reply, -1, -1, &error);
3063   else
3064     g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3065 	                 _("Invalid reply received"));
3066 
3067   if (res)
3068     {
3069       /* moved original file to backup, now move new file in place */
3070 
3071       command = new_command_stream (backend,
3072                                     SSH_FXP_RENAME);
3073       put_string (command, handle->tempname);
3074       put_string (command, handle->filename);
3075       queue_command_stream_and_free (&backend->command_connection, command,
3076                                      close_moved_tempfile,
3077                                      G_VFS_JOB (job), handle);
3078     }
3079   else
3080     {
3081       /* Move original file to backup name failed, remove any temporary files */
3082       delete_temp_file (backend,
3083                         handle,
3084                         G_VFS_JOB (job));
3085 
3086       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
3087                         _("Error creating backup file: %s"), error->message);
3088       g_error_free (error);
3089       sftp_handle_free (handle);
3090     }
3091 }
3092 
3093 static void
close_deleted_backup(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3094 close_deleted_backup (GVfsBackendSftp *backend,
3095                       int reply_type,
3096                       GDataInputStream *reply,
3097                       guint32 len,
3098                       GVfsJob *job,
3099                       gpointer user_data)
3100 {
3101   SftpHandle *handle;
3102   GDataOutputStream *command;
3103   char *backup_name;
3104 
3105   /* Ignore result here, if it failed we'll just get a new error when moving over it
3106    * This is simpler than ignoring NOEXIST errors
3107    */
3108 
3109   handle = user_data;
3110 
3111   command = new_command_stream (backend,
3112                                 SSH_FXP_RENAME);
3113   backup_name = g_strconcat (handle->filename, "~", NULL);
3114   put_string (command, handle->filename);
3115   put_string (command, backup_name);
3116   g_free (backup_name);
3117   queue_command_stream_and_free (&backend->command_connection, command,
3118                                  close_moved_file,
3119                                  G_VFS_JOB (job), handle);
3120 }
3121 
3122 static void
close_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3123 close_write_reply (GVfsBackendSftp *backend,
3124                    int reply_type,
3125                    GDataInputStream *reply,
3126                    guint32 len,
3127                    GVfsJob *job,
3128                    gpointer user_data)
3129 {
3130   GDataOutputStream *command;
3131   GError *error;
3132   gboolean res;
3133   char *backup_name;
3134   SftpHandle *handle;
3135 
3136   handle = user_data;
3137 
3138   error = NULL;
3139   res = FALSE;
3140   if (reply_type == SSH_FXP_STATUS)
3141     res = error_from_status (job, reply, -1, -1, &error);
3142   else
3143     g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
3144 	                 _("Invalid reply received"));
3145 
3146   if (res)
3147     {
3148       if (handle->tempname)
3149         {
3150           if (handle->make_backup)
3151             {
3152               command = new_command_stream (backend,
3153                                             SSH_FXP_REMOVE);
3154               backup_name = g_strconcat (handle->filename, "~", NULL);
3155               put_string (command, backup_name);
3156               g_free (backup_name);
3157               queue_command_stream_and_free (&backend->command_connection,
3158                                              command,
3159                                              close_deleted_backup,
3160                                              G_VFS_JOB (job), handle);
3161             }
3162           else
3163             {
3164               command = new_command_stream (backend,
3165                                             SSH_FXP_REMOVE);
3166               put_string (command, handle->filename);
3167               queue_command_stream_and_free (&backend->command_connection,
3168                                              command,
3169                                              close_deleted_file,
3170                                              G_VFS_JOB (job), handle);
3171             }
3172         }
3173       else
3174         {
3175           g_vfs_job_succeeded (job);
3176           sftp_handle_free (handle);
3177         }
3178     }
3179   else
3180     {
3181       /* The close failed, remove any temporary files */
3182       delete_temp_file (backend,
3183                         handle,
3184                         G_VFS_JOB (job));
3185 
3186       g_vfs_job_failed_from_error (job, error);
3187       g_error_free (error);
3188 
3189       sftp_handle_free (handle);
3190     }
3191 }
3192 
3193 static void
close_write_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3194 close_write_fstat_reply (GVfsBackendSftp *backend,
3195                         int reply_type,
3196                         GDataInputStream *reply,
3197                         guint32 len,
3198                         GVfsJob *job,
3199                         gpointer user_data)
3200 {
3201   SftpHandle *handle = user_data;
3202   GDataOutputStream *command;
3203   GFileInfo *info;
3204   const char *etag;
3205 
3206   if (reply_type == SSH_FXP_ATTRS)
3207     {
3208       info = g_file_info_new ();
3209       parse_attributes (backend, info, NULL,
3210                         reply, NULL);
3211       etag = g_file_info_get_etag (info);
3212       if (etag)
3213         g_vfs_job_close_write_set_etag (G_VFS_JOB_CLOSE_WRITE (job), etag);
3214       g_object_unref (info);
3215     }
3216 
3217   command = new_command_stream (backend, SSH_FXP_CLOSE);
3218   put_data_buffer (command, handle->raw_handle);
3219 
3220   queue_command_stream_and_free (&backend->command_connection, command,
3221                                  close_write_reply,
3222                                  G_VFS_JOB (job), handle);
3223 }
3224 
3225 static gboolean
try_close_write(GVfsBackend * backend,GVfsJobCloseWrite * job,GVfsBackendHandle _handle)3226 try_close_write (GVfsBackend *backend,
3227                  GVfsJobCloseWrite *job,
3228                  GVfsBackendHandle _handle)
3229 {
3230   SftpHandle *handle = _handle;
3231   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3232   GDataOutputStream *command;
3233 
3234   command = new_command_stream (op_backend, SSH_FXP_FSTAT);
3235   put_data_buffer (command, handle->raw_handle);
3236 
3237   queue_command_stream_and_free (&op_backend->command_connection, command,
3238                                  close_write_fstat_reply,
3239                                  G_VFS_JOB (job), handle);
3240 
3241   return TRUE;
3242 }
3243 
3244 
3245 static void
close_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3246 close_read_reply (GVfsBackendSftp *backend,
3247                   int reply_type,
3248                   GDataInputStream *reply,
3249                   guint32 len,
3250                   GVfsJob *job,
3251                   gpointer user_data)
3252 {
3253   SftpHandle *handle;
3254 
3255   handle = user_data;
3256 
3257   if (reply_type == SSH_FXP_STATUS)
3258     result_from_status (job, reply, -1, -1);
3259   else
3260     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3261                       _("Invalid reply received"));
3262   sftp_handle_free (handle);
3263 }
3264 
3265 static gboolean
try_close_read(GVfsBackend * backend,GVfsJobCloseRead * job,GVfsBackendHandle _handle)3266 try_close_read (GVfsBackend *backend,
3267                 GVfsJobCloseRead *job,
3268                 GVfsBackendHandle _handle)
3269 {
3270   SftpHandle *handle = _handle;
3271   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3272   GDataOutputStream *command;
3273 
3274   command = new_command_stream (op_backend, SSH_FXP_CLOSE);
3275   put_data_buffer (command, handle->raw_handle);
3276 
3277   queue_command_stream_and_free (&op_backend->command_connection, command,
3278                                  close_read_reply,
3279                                  G_VFS_JOB (job), handle);
3280 
3281   return TRUE;
3282 }
3283 
3284 static void
put_mode(GDataOutputStream * command,GFileCreateFlags flags)3285 put_mode (GDataOutputStream *command, GFileCreateFlags flags)
3286 {
3287   if (flags & G_FILE_CREATE_PRIVATE)
3288     {
3289       g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
3290       g_data_output_stream_put_uint32 (command, 0600, NULL, NULL);
3291     }
3292   else
3293     {
3294       g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
3295     }
3296 }
3297 
3298 static void not_dir_or_not_exist_error (GVfsBackendSftp *backend,
3299 					GVfsJob *job,
3300 					char *filename);
3301 
3302 static void
not_dir_or_not_exist_error_cb(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)3303 not_dir_or_not_exist_error_cb (GVfsBackendSftp *backend,
3304 			       GVfsJob *job,
3305 			       gint original_error,
3306 			       gint stat_error,
3307 			       GFileInfo *info,
3308 			       gpointer user_data)
3309 {
3310   char *path;
3311 
3312   path = user_data;
3313   if (info != NULL)
3314     {
3315       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3316 	/* Parent is a directory, so must not have found child */
3317 	g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3318 			  _("No such file or directory"));
3319       else /* Some path element was not a directory */
3320 	g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
3321 			  _("Not a directory"));
3322     }
3323   else if (stat_error == SSH_FX_NO_SUCH_FILE)
3324     {
3325       not_dir_or_not_exist_error (backend, job, path);
3326     }
3327   else
3328     {
3329       /* Some other weird error, lets say "not found" */
3330       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3331 			_("No such file or directory"));
3332     }
3333 
3334   g_free (path);
3335 }
3336 
3337 static void
not_dir_or_not_exist_error(GVfsBackendSftp * backend,GVfsJob * job,char * filename)3338 not_dir_or_not_exist_error (GVfsBackendSftp *backend,
3339 			    GVfsJob *job,
3340 			    char *filename)
3341 {
3342   char *parent;
3343 
3344   parent = g_path_get_dirname (filename);
3345   if (strcmp (parent, ".") == 0)
3346     {
3347       g_free (parent);
3348       /* Root not found? Weird, but at least not
3349 	 NOT_DIRECTORY, so lets report not found */
3350       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
3351 			_("No such file or directory"));
3352       return;
3353     }
3354 
3355   error_from_lstat (backend,
3356 		    job,
3357 		    SSH_FX_NO_SUCH_FILE,
3358 		    filename,
3359 		    not_dir_or_not_exist_error_cb,
3360 		    parent);
3361 }
3362 
3363 static void
create_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3364 create_reply (GVfsBackendSftp *backend,
3365               int reply_type,
3366               GDataInputStream *reply,
3367               guint32 len,
3368               GVfsJob *job,
3369               gpointer user_data)
3370 {
3371   SftpHandle *handle;
3372   guint32 code;
3373 
3374   if (reply_type == SSH_FXP_STATUS)
3375     {
3376       code = read_status_code (reply);
3377 
3378       if (code == SSH_FX_NO_SUCH_FILE)
3379 	{
3380 	  /* openssh sftp returns NO_SUCH_FILE for both ENOTDIR and ENOENT,
3381 	     we need to stat-walk the hierarchy to see what the error was */
3382 	  not_dir_or_not_exist_error (backend, job,
3383 				      G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3384 	  return;
3385 	}
3386 
3387       result_from_status_code (job, code, G_IO_ERROR_EXISTS, -1);
3388       return;
3389     }
3390 
3391   if (reply_type != SSH_FXP_HANDLE)
3392     {
3393       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3394                         _("Invalid reply received"));
3395       return;
3396     }
3397 
3398   handle = sftp_handle_new (reply);
3399 
3400   g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
3401   g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3402   g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3403   g_vfs_job_succeeded (job);
3404 }
3405 
3406 static gboolean
try_create(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)3407 try_create (GVfsBackend *backend,
3408             GVfsJobOpenForWrite *job,
3409             const char *filename,
3410             GFileCreateFlags flags)
3411 {
3412   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3413   GDataOutputStream *command;
3414 
3415   command = new_command_stream (op_backend,
3416                                 SSH_FXP_OPEN);
3417   put_string (command, filename);
3418   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL,  NULL, NULL); /* open flags */
3419   put_mode (command, flags);
3420 
3421   queue_command_stream_and_free (&op_backend->command_connection, command,
3422                                  create_reply,
3423                                  G_VFS_JOB (job), NULL);
3424 
3425   return TRUE;
3426 }
3427 
3428 static void
append_to_error(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)3429 append_to_error (GVfsBackendSftp *backend,
3430 		 GVfsJob *job,
3431 		 gint original_error,
3432 		 gint stat_error,
3433 		 GFileInfo *info,
3434 		 gpointer user_data)
3435 {
3436   if ((original_error == SSH_FX_FAILURE) &&
3437       info != NULL &&
3438       g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3439     {
3440       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
3441 			_("File is directory"));
3442       return;
3443     }
3444 
3445   if (original_error == SSH_FX_NO_SUCH_FILE)
3446     {
3447       not_dir_or_not_exist_error (backend, job,
3448 				  G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3449       return;
3450     }
3451 
3452 
3453   result_from_status_code (job, original_error, -1, -1);
3454 }
3455 
3456 static void
append_to_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3457 append_to_reply (GVfsBackendSftp *backend,
3458                  int reply_type,
3459                  GDataInputStream *reply,
3460                  guint32 len,
3461                  GVfsJob *job,
3462                  gpointer user_data)
3463 {
3464   SftpHandle *handle;
3465 
3466   if (reply_type == SSH_FXP_STATUS)
3467     {
3468       error_from_lstat (backend, job, read_status_code (reply),
3469 			G_VFS_JOB_OPEN_FOR_WRITE (job)->filename,
3470 			append_to_error,
3471 			NULL);
3472       return;
3473     }
3474 
3475   if (reply_type != SSH_FXP_HANDLE)
3476     {
3477       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3478                         _("Invalid reply received"));
3479       return;
3480     }
3481 
3482   handle = sftp_handle_new (reply);
3483 
3484   g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
3485   g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3486   g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
3487   g_vfs_job_succeeded (job);
3488 }
3489 
3490 static gboolean
try_append_to(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,GFileCreateFlags flags)3491 try_append_to (GVfsBackend *backend,
3492                GVfsJobOpenForWrite *job,
3493                const char *filename,
3494                GFileCreateFlags flags)
3495 {
3496   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3497   GDataOutputStream *command;
3498 
3499   command = new_command_stream (op_backend,
3500                                 SSH_FXP_OPEN);
3501   put_string (command, filename);
3502   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_APPEND,  NULL, NULL); /* open flags */
3503   put_mode (command, flags);
3504 
3505   queue_command_stream_and_free (&op_backend->command_connection, command,
3506                                  append_to_reply,
3507                                  G_VFS_JOB (job), NULL);
3508 
3509   return TRUE;
3510 }
3511 
3512 typedef struct {
3513   guint32 permissions;
3514   gboolean set_permissions;
3515   guint32 uid;
3516   guint32 gid;
3517   gboolean set_ownership;
3518   char *tempname;
3519   int temp_count;
3520 } ReplaceData;
3521 
3522 static void
replace_data_free(ReplaceData * data)3523 replace_data_free (ReplaceData *data)
3524 {
3525   g_free (data->tempname);
3526   g_slice_free (ReplaceData, data);
3527 }
3528 
3529 static void replace_create_temp (GVfsBackendSftp *backend,
3530                                  GVfsJobOpenForWrite *job);
3531 
3532 static void
replace_truncate_original_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3533 replace_truncate_original_reply (GVfsBackendSftp *backend,
3534                                  int reply_type,
3535                                  GDataInputStream *reply,
3536                                  guint32 len,
3537                                  GVfsJob *job,
3538                                  gpointer user_data)
3539 {
3540   GVfsJobOpenForWrite *op_job;
3541   SftpHandle *handle;
3542   ReplaceData *data;
3543   GError *error = NULL;
3544 
3545   op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3546   data = G_VFS_JOB (job)->backend_data;
3547 
3548   if (reply_type == SSH_FXP_STATUS)
3549     {
3550       error = NULL;
3551       if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3552         /* Open should not return OK */
3553         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3554                           _("Invalid reply received"));
3555       else
3556         {
3557           g_vfs_job_failed_from_error (job, error);
3558           g_error_free (error);
3559         }
3560       return;
3561     }
3562 
3563   if (reply_type != SSH_FXP_HANDLE)
3564     {
3565       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3566                         _("Invalid reply received"));
3567       return;
3568     }
3569 
3570   handle = sftp_handle_new (reply);
3571   handle->filename = g_strdup (op_job->filename);
3572   handle->tempname = NULL;
3573   handle->permissions = data->permissions;
3574   handle->set_permissions = data->set_permissions;
3575   handle->make_backup = op_job->make_backup;
3576 
3577   g_vfs_job_open_for_write_set_handle (op_job, handle);
3578   g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3579   g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3580 
3581   g_vfs_job_succeeded (job);
3582 }
3583 
3584 static void
replace_truncate_original(GVfsBackendSftp * backend,GVfsJob * job)3585 replace_truncate_original (GVfsBackendSftp *backend,
3586                            GVfsJob *job)
3587 {
3588   GVfsJobOpenForWrite *op_job;
3589   GDataOutputStream *command;
3590 
3591   op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3592 
3593   command = new_command_stream (backend,
3594                                 SSH_FXP_OPEN);
3595   put_string (command, op_job->filename);
3596   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC,  NULL, NULL); /* open flags */
3597   put_mode (command, op_job->flags);
3598 
3599   queue_command_stream_and_free (&backend->command_connection, command,
3600                                  replace_truncate_original_reply,
3601                                  job, NULL);
3602 }
3603 
3604 static void
close_temp_file(GVfsBackendSftp * backend,SftpHandle * handle,GVfsJob * job)3605 close_temp_file (GVfsBackendSftp *backend,
3606                  SftpHandle *handle,
3607                  GVfsJob *job)
3608 {
3609   GDataOutputStream *command;
3610 
3611   command = new_command_stream (backend,
3612                                 SSH_FXP_CLOSE);
3613   put_data_buffer (command, handle->raw_handle);
3614   queue_command_stream_and_free (&backend->command_connection, command,
3615                                  NULL,
3616                                  G_VFS_JOB (job), NULL);
3617 }
3618 
3619 static void
replace_create_temp_fsetstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3620 replace_create_temp_fsetstat_reply (GVfsBackendSftp *backend,
3621                                     int reply_type,
3622                                     GDataInputStream *reply,
3623                                     guint32 len,
3624                                     GVfsJob *job,
3625                                     gpointer user_data)
3626 {
3627   GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3628   ReplaceData *data = job->backend_data;
3629   SftpHandle *handle = user_data;
3630   GError *error = NULL;
3631 
3632   if (reply_type == SSH_FXP_STATUS)
3633     error_from_status (job, reply, -1, -1, &error);
3634   else
3635     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3636                       _("Invalid reply received"));
3637 
3638   if (error != NULL)
3639     {
3640       if (data->set_ownership &&
3641           error->code == G_IO_ERROR_PERMISSION_DENIED)
3642         {
3643           g_error_free (error);
3644 
3645           close_temp_file (backend, handle, job);
3646           delete_temp_file (backend, handle, job);
3647           sftp_handle_free (handle);
3648 
3649           /* This was probably due to the fact that the ownership could not be
3650              set properly. In this case we change our strategy altogether and
3651              simply open/truncate the original file. This is not as secure
3652              as the atomit tempfile/move approach, but at least ownership
3653              doesn't change */
3654           if (!op_job->make_backup)
3655             replace_truncate_original (backend, job);
3656           else
3657             {
3658               /* We only do this if make_backup is FALSE, as this version breaks
3659                  the backup code. Would like to handle the backup case too by
3660                  backing up before truncating, but for now error out... */
3661               g_vfs_job_failed (job, G_IO_ERROR,
3662                                 G_IO_ERROR_CANT_CREATE_BACKUP,
3663                                 _("Backups not supported"));
3664             }
3665         }
3666       else
3667         {
3668           g_vfs_job_failed_from_error (job, error);
3669           g_error_free (error);
3670         }
3671       return;
3672     }
3673 
3674   g_vfs_job_open_for_write_set_handle (op_job, handle);
3675   g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3676   g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3677   g_vfs_job_succeeded (job);
3678 }
3679 
3680 static void
replace_create_temp_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3681 replace_create_temp_fstat_reply (GVfsBackendSftp *backend,
3682                                  int reply_type,
3683                                  GDataInputStream *reply,
3684                                  guint32 len,
3685                                  GVfsJob *job,
3686                                  gpointer user_data)
3687 {
3688   GVfsJobOpenForWrite *op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3689   ReplaceData *data = job->backend_data;
3690   SftpHandle *handle = user_data;
3691   GFileInfo *info;
3692   guint32 uid = -1;
3693   guint32 gid = -1;
3694 
3695   if (reply_type == SSH_FXP_STATUS ||
3696       reply_type != SSH_FXP_ATTRS)
3697     {
3698       close_temp_file (backend, handle, job);
3699       delete_temp_file (backend, handle, job);
3700       sftp_handle_free (handle);
3701 
3702       if (reply_type == SSH_FXP_STATUS)
3703         result_from_status (job, reply, -1, -1);
3704       else
3705         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3706                           _("Invalid reply received"));
3707       return;
3708     }
3709 
3710   info = g_file_info_new ();
3711   parse_attributes (backend, info, NULL, reply, NULL);
3712 
3713   if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID) &&
3714       g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID))
3715     {
3716       uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
3717       gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
3718     }
3719 
3720   g_object_unref (info);
3721 
3722   if (uid != data->uid || gid != data->gid)
3723     {
3724       GDataOutputStream *command;
3725 
3726       command = new_command_stream (backend, SSH_FXP_FSETSTAT);
3727       put_data_buffer (command, handle->raw_handle);
3728       g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_UIDGID, NULL, NULL);
3729       g_data_output_stream_put_uint32 (command, data->uid, NULL, NULL);
3730       g_data_output_stream_put_uint32 (command, data->gid, NULL, NULL);
3731       queue_command_stream_and_free (&backend->command_connection, command,
3732                                      replace_create_temp_fsetstat_reply, job, handle);
3733       return;
3734     }
3735 
3736   g_vfs_job_open_for_write_set_handle (op_job, handle);
3737   g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3738   g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3739   g_vfs_job_succeeded (job);
3740 }
3741 
3742 static void
replace_create_temp_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3743 replace_create_temp_reply (GVfsBackendSftp *backend,
3744                            int reply_type,
3745                            GDataInputStream *reply,
3746                            guint32 len,
3747                            GVfsJob *job,
3748                            gpointer user_data)
3749 {
3750   GVfsJobOpenForWrite *op_job;
3751   SftpHandle *handle;
3752   ReplaceData *data;
3753   GError *error;
3754 
3755   op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3756   data = G_VFS_JOB (job)->backend_data;
3757 
3758   if (reply_type == SSH_FXP_STATUS)
3759     {
3760       error = NULL;
3761       if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3762         /* Open should not return OK */
3763         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3764                           _("Invalid reply received"));
3765       else if (error->code == G_IO_ERROR_EXISTS)
3766         {
3767           /* It was *probably* the EXCL flag failing. I wish we had
3768              an actual real error code for that, grumble */
3769           g_error_free (error);
3770 
3771           replace_create_temp (backend, op_job);
3772         }
3773       else
3774         {
3775           g_vfs_job_failed_from_error (job, error);
3776           g_error_free (error);
3777         }
3778       return;
3779     }
3780 
3781   if (reply_type != SSH_FXP_HANDLE)
3782     {
3783       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3784                         _("Invalid reply received"));
3785       return;
3786     }
3787 
3788   handle = sftp_handle_new (reply);
3789   handle->filename = g_strdup (op_job->filename);
3790   handle->tempname = g_strdup (data->tempname);
3791   handle->permissions = data->permissions;
3792   handle->set_permissions = data->set_permissions;
3793   handle->make_backup = op_job->make_backup;
3794 
3795   if (data->set_ownership)
3796     {
3797       GDataOutputStream *command;
3798 
3799       command = new_command_stream (backend, SSH_FXP_FSTAT);
3800       put_data_buffer (command, handle->raw_handle);
3801       queue_command_stream_and_free (&backend->command_connection, command,
3802                                      replace_create_temp_fstat_reply, job, handle);
3803       return;
3804     }
3805 
3806   g_vfs_job_open_for_write_set_handle (op_job, handle);
3807   g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
3808   g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
3809 
3810   g_vfs_job_succeeded (job);
3811 }
3812 
3813 static void
replace_create_temp(GVfsBackendSftp * backend,GVfsJobOpenForWrite * job)3814 replace_create_temp (GVfsBackendSftp *backend,
3815                      GVfsJobOpenForWrite *job)
3816 {
3817   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
3818   GDataOutputStream *command;
3819   char *dirname;
3820   ReplaceData *data;
3821   char basename[] = ".giosaveXXXXXX";
3822 
3823   data = G_VFS_JOB (job)->backend_data;
3824 
3825   data->temp_count++;
3826 
3827   if (data->temp_count == 100)
3828     {
3829       g_vfs_job_failed (G_VFS_JOB (job),
3830                         G_IO_ERROR, G_IO_ERROR_FAILED,
3831                         _("Unable to create temporary file"));
3832       return;
3833     }
3834 
3835   g_free (data->tempname);
3836 
3837   dirname = g_path_get_dirname (job->filename);
3838   gvfs_randomize_string (basename + 8, 6);
3839   data->tempname = g_build_filename (dirname, basename, NULL);
3840   g_free (dirname);
3841 
3842   command = new_command_stream (op_backend,
3843                                 SSH_FXP_OPEN);
3844   put_string (command, data->tempname);
3845   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL,  NULL, NULL); /* open flags */
3846   g_data_output_stream_put_uint32 (command, (data->set_permissions ? SSH_FILEXFER_ATTR_PERMISSIONS : 0), NULL, NULL);
3847 
3848   if (data->set_permissions)
3849     g_data_output_stream_put_uint32 (command, data->permissions, NULL, NULL);
3850   queue_command_stream_and_free (&op_backend->command_connection, command,
3851                                  replace_create_temp_reply,
3852                                  G_VFS_JOB (job), NULL);
3853 }
3854 
3855 static void
replace_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3856 replace_stat_reply (GVfsBackendSftp *backend,
3857                     int reply_type,
3858                     GDataInputStream *reply,
3859                     guint32 len,
3860                     GVfsJob *job,
3861                     gpointer user_data)
3862 {
3863   GFileInfo *info;
3864   GVfsJobOpenForWrite *op_job;
3865   const char *current_etag;
3866   guint32 permissions;
3867   guint32 uid;
3868   guint32 gid;
3869   gboolean set_permissions;
3870   gboolean set_ownership = FALSE;
3871   gboolean is_regular = FALSE;
3872   ReplaceData *data;
3873 
3874   op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3875 
3876   set_permissions = op_job->flags & G_FILE_CREATE_PRIVATE ? TRUE : FALSE;
3877   permissions = 0600;
3878 
3879   if (reply_type == SSH_FXP_ATTRS)
3880     {
3881       info = g_file_info_new ();
3882       parse_attributes (backend, info, NULL,
3883                         reply, NULL);
3884 
3885       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
3886 	{
3887 	  g_object_unref (info);
3888           g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
3889                             _("File is directory"));
3890           return;
3891 	}
3892       else if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
3893         is_regular = TRUE;
3894 
3895       if (op_job->etag != NULL)
3896         {
3897           current_etag = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE);
3898 
3899           if (current_etag == NULL ||
3900               strcmp (op_job->etag, current_etag) != 0)
3901             {
3902               g_vfs_job_failed (job,
3903                                 G_IO_ERROR, G_IO_ERROR_WRONG_ETAG,
3904                                 _("The file was externally modified"));
3905               g_object_unref (info);
3906               return;
3907             }
3908         }
3909 
3910       if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE) &&
3911           !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION))
3912         {
3913           set_permissions = TRUE;
3914           permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
3915         }
3916 
3917       if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID) &&
3918           g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID) &&
3919           !(op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION))
3920       {
3921         set_ownership = TRUE;
3922         uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID);
3923         gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID);
3924       }
3925     }
3926 
3927   data = g_slice_new0 (ReplaceData);
3928   data->set_permissions = set_permissions;
3929   if (set_permissions)
3930     data->permissions = permissions;
3931 
3932   data->set_ownership = set_ownership;
3933   if (set_ownership)
3934   {
3935     data->uid = uid;
3936     data->gid = gid;
3937   }
3938 
3939   g_vfs_job_set_backend_data (job, data, (GDestroyNotify)replace_data_free);
3940 
3941   /* If G_FILE_CREATE_REPLACE_DESTINATION is specified or the destination
3942    * is a regular file, replace by writing to a temp file and renaming rather
3943    * than truncating. */
3944   if (op_job->flags & G_FILE_CREATE_REPLACE_DESTINATION || is_regular)
3945     replace_create_temp (backend, op_job);
3946   else
3947     {
3948       if (op_job->make_backup)
3949         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANT_CREATE_BACKUP,
3950                           _("Backups not supported"));
3951       else
3952         replace_truncate_original (backend, job);
3953     }
3954 }
3955 
3956 static void
replace_exclusive_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)3957 replace_exclusive_reply (GVfsBackendSftp *backend,
3958                          int reply_type,
3959                          GDataInputStream *reply,
3960                          guint32 len,
3961                          GVfsJob *job,
3962                          gpointer user_data)
3963 {
3964   GVfsJobOpenForWrite *op_job;
3965   GDataOutputStream *command;
3966   SftpHandle *handle;
3967   GError *error;
3968 
3969   op_job = G_VFS_JOB_OPEN_FOR_WRITE (job);
3970   if (reply_type == SSH_FXP_STATUS)
3971     {
3972       error = NULL;
3973       if (error_from_status (job, reply, G_IO_ERROR_EXISTS, -1, &error))
3974         /* Open should not return OK */
3975         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
3976                           _("Invalid reply received"));
3977       else if (error->code == G_IO_ERROR_NOT_FOUND)
3978 	{
3979           g_error_free (error);
3980 	  /* openssh sftp returns NO_SUCH_FILE for ENOTDIR */
3981 	  not_dir_or_not_exist_error (backend, job,
3982 				      G_VFS_JOB_OPEN_FOR_WRITE (job)->filename);
3983 	}
3984       else if (error->code == G_IO_ERROR_EXISTS)
3985         {
3986           /* It was *probably* the EXCL flag failing. I wish we had
3987              an actual real error code for that, grumble */
3988           g_error_free (error);
3989 
3990           /* Replace existing file code: */
3991 
3992           command = new_command_stream (backend,
3993                                         SSH_FXP_LSTAT);
3994           put_string (command, op_job->filename);
3995           queue_command_stream_and_free (&backend->command_connection, command,
3996                                          replace_stat_reply,
3997                                          G_VFS_JOB (job), NULL);
3998         }
3999       else
4000         {
4001           g_vfs_job_failed_from_error (job, error);
4002           g_error_free (error);
4003         }
4004       return;
4005     }
4006 
4007   if (reply_type != SSH_FXP_HANDLE)
4008     {
4009       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4010                         _("Invalid reply received"));
4011       return;
4012     }
4013 
4014   handle = sftp_handle_new (reply);
4015 
4016   g_vfs_job_open_for_write_set_handle (op_job, handle);
4017   g_vfs_job_open_for_write_set_can_seek (op_job, TRUE);
4018   g_vfs_job_open_for_write_set_can_truncate (op_job, TRUE);
4019 
4020   g_vfs_job_succeeded (job);
4021 }
4022 
4023 static gboolean
try_replace(GVfsBackend * backend,GVfsJobOpenForWrite * job,const char * filename,const char * etag,gboolean make_backup,GFileCreateFlags flags)4024 try_replace (GVfsBackend *backend,
4025              GVfsJobOpenForWrite *job,
4026              const char *filename,
4027              const char *etag,
4028              gboolean make_backup,
4029              GFileCreateFlags flags)
4030 {
4031   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4032   GDataOutputStream *command;
4033 
4034   command = new_command_stream (op_backend,
4035                                 SSH_FXP_OPEN);
4036   put_string (command, filename);
4037   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL,  NULL, NULL); /* open flags */
4038   put_mode (command, flags);
4039 
4040   queue_command_stream_and_free (&op_backend->command_connection, command,
4041                                  replace_exclusive_reply,
4042                                  G_VFS_JOB (job), NULL);
4043 
4044   return TRUE;
4045 }
4046 
4047 static void
write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4048 write_reply (GVfsBackendSftp *backend,
4049              int reply_type,
4050              GDataInputStream *reply,
4051              guint32 len,
4052              GVfsJob *job,
4053              gpointer user_data)
4054 {
4055   SftpHandle *handle;
4056 
4057   handle = user_data;
4058 
4059   if (reply_type == SSH_FXP_STATUS)
4060     {
4061       if (result_from_status (job, reply, -1, -1))
4062         {
4063           handle->offset += G_VFS_JOB_WRITE (job)->written_size;
4064         }
4065     }
4066   else
4067     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4068                       _("Invalid reply received"));
4069 }
4070 
4071 static gboolean
try_write(GVfsBackend * backend,GVfsJobWrite * job,GVfsBackendHandle _handle,char * buffer,gsize buffer_size)4072 try_write (GVfsBackend *backend,
4073            GVfsJobWrite *job,
4074            GVfsBackendHandle _handle,
4075            char *buffer,
4076            gsize buffer_size)
4077 {
4078   SftpHandle *handle = _handle;
4079   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4080   GDataOutputStream *command;
4081   gsize size;
4082 
4083   size = MIN (buffer_size, MAX_BUFFER_SIZE);
4084 
4085   command = new_command_stream (op_backend,
4086                                 SSH_FXP_WRITE);
4087   put_data_buffer (command, handle->raw_handle);
4088   g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
4089   g_data_output_stream_put_uint32 (command, size, NULL, NULL);
4090   /* Ideally we shouldn't do this copy, but doing the writes as multiple writes
4091      caused problems on the read side in openssh */
4092   g_output_stream_write_all (G_OUTPUT_STREAM (command),
4093                              buffer, size,
4094                              NULL, NULL, NULL);
4095 
4096   queue_command_stream_and_free (&op_backend->command_connection, command,
4097                                  write_reply,
4098                                  G_VFS_JOB (job), handle);
4099 
4100   /* We always write the full size (on success) */
4101   g_vfs_job_write_set_written_size (job, size);
4102 
4103   return TRUE;
4104 }
4105 
4106 static void
seek_write_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4107 seek_write_fstat_reply (GVfsBackendSftp *backend,
4108                         int reply_type,
4109                         GDataInputStream *reply,
4110                         guint32 len,
4111                         GVfsJob *job,
4112                         gpointer user_data)
4113 {
4114   SftpHandle *handle;
4115   GFileInfo *info;
4116   goffset file_size;
4117   GVfsJobSeekWrite *op_job;
4118 
4119   handle = user_data;
4120 
4121   if (reply_type == SSH_FXP_STATUS)
4122     {
4123       result_from_status (job, reply, -1, -1);
4124       return;
4125     }
4126 
4127   if (reply_type != SSH_FXP_ATTRS)
4128     {
4129       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4130                         _("Invalid reply received"));
4131       return;
4132     }
4133 
4134   info = g_file_info_new ();
4135   parse_attributes (backend, info, NULL,
4136                     reply, NULL);
4137   file_size = g_file_info_get_size (info);
4138   g_object_unref (info);
4139 
4140   op_job = G_VFS_JOB_SEEK_WRITE (job);
4141 
4142   handle->offset = file_size + op_job->requested_offset;
4143 
4144   if (handle->offset < 0)
4145     handle->offset = 0;
4146 
4147   g_vfs_job_seek_write_set_offset (op_job, handle->offset);
4148   g_vfs_job_succeeded (job);
4149 }
4150 
4151 static gboolean
try_seek_on_write(GVfsBackend * backend,GVfsJobSeekWrite * job,GVfsBackendHandle _handle,goffset offset,GSeekType type)4152 try_seek_on_write (GVfsBackend *backend,
4153                    GVfsJobSeekWrite *job,
4154                    GVfsBackendHandle _handle,
4155                    goffset    offset,
4156                    GSeekType  type)
4157 {
4158   SftpHandle *handle = _handle;
4159   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4160   GDataOutputStream *command;
4161 
4162   switch (job->seek_type)
4163     {
4164     case G_SEEK_CUR:
4165       handle->offset += job->requested_offset;
4166       break;
4167     case G_SEEK_SET:
4168       handle->offset = job->requested_offset;
4169       break;
4170     case G_SEEK_END:
4171       command = new_command_stream (op_backend,
4172                                     SSH_FXP_FSTAT);
4173       put_data_buffer (command, handle->raw_handle);
4174       queue_command_stream_and_free (&op_backend->command_connection, command,
4175                                      seek_write_fstat_reply,
4176                                      G_VFS_JOB (job), handle);
4177       return TRUE;
4178     }
4179 
4180   if (handle->offset < 0)
4181     handle->offset = 0;
4182 
4183   g_vfs_job_seek_write_set_offset (job, handle->offset);
4184   g_vfs_job_succeeded (G_VFS_JOB (job));
4185 
4186   return TRUE;
4187 }
4188 
4189 static void
truncate_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4190 truncate_reply (GVfsBackendSftp *backend,
4191                 int reply_type,
4192                 GDataInputStream *reply,
4193                 guint32 len,
4194                 GVfsJob *job,
4195                 gpointer user_data)
4196 {
4197   if (reply_type == SSH_FXP_STATUS)
4198     result_from_status (job, reply, -1, -1);
4199   else
4200     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4201                       _("Invalid reply received"));
4202 }
4203 
4204 static gboolean
try_truncate(GVfsBackend * backend,GVfsJobTruncate * job,GVfsBackendHandle _handle,goffset size)4205 try_truncate (GVfsBackend *backend,
4206               GVfsJobTruncate *job,
4207               GVfsBackendHandle _handle,
4208               goffset size)
4209 {
4210   SftpHandle *handle = _handle;
4211   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4212   GDataOutputStream *command;
4213 
4214   command = new_command_stream (op_backend, SSH_FXP_FSETSTAT);
4215   put_data_buffer (command, handle->raw_handle);
4216   g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_SIZE, NULL, NULL);
4217   g_data_output_stream_put_uint64 (command, size, NULL, NULL);
4218   queue_command_stream_and_free (&op_backend->command_connection, command,
4219                                  truncate_reply,
4220                                  G_VFS_JOB (job), NULL);
4221 
4222   return TRUE;
4223 }
4224 
4225 typedef struct {
4226   DataBuffer *handle;
4227   int outstanding_requests;
4228 } ReadDirData;
4229 
4230 static
4231 void
read_dir_data_free(ReadDirData * data)4232 read_dir_data_free (ReadDirData *data)
4233 {
4234   data_buffer_free (data->handle);
4235   g_slice_free (ReadDirData, data);
4236 }
4237 
4238 static void
read_dir_readlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4239 read_dir_readlink_reply (GVfsBackendSftp *backend,
4240                          int reply_type,
4241                          GDataInputStream *reply,
4242                          guint32 len,
4243                          GVfsJob *job,
4244                          gpointer user_data)
4245 {
4246   ReadDirData *data;
4247   GFileInfo *info = user_data;
4248   char *target;
4249 
4250   data = job->backend_data;
4251 
4252   if (reply_type == SSH_FXP_NAME)
4253     {
4254       /* count = */ (void) g_data_input_stream_read_uint32 (reply, NULL, NULL);
4255 
4256       target = read_string (reply, NULL);
4257       if (target)
4258         {
4259           g_file_info_set_symlink_target (info, target);
4260           g_free (target);
4261         }
4262     }
4263 
4264   g_vfs_job_enumerate_add_info (G_VFS_JOB_ENUMERATE (job), info);
4265   g_object_unref (info);
4266 
4267   if (--data->outstanding_requests == 0)
4268     g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
4269 }
4270 
4271 static void
read_dir_got_stat_info(GVfsBackendSftp * backend,GVfsJob * job,GFileInfo * info)4272 read_dir_got_stat_info (GVfsBackendSftp *backend,
4273                         GVfsJob *job,
4274                         GFileInfo *info)
4275 {
4276   GVfsJobEnumerate *enum_job;
4277   GDataOutputStream *command;
4278   ReadDirData *data;
4279   char *abs_name;
4280 
4281   data = job->backend_data;
4282 
4283   enum_job = G_VFS_JOB_ENUMERATE (job);
4284 
4285   if (g_file_attribute_matcher_matches (enum_job->attribute_matcher,
4286                                         G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4287     {
4288       data->outstanding_requests++;
4289       command = new_command_stream (backend,
4290                                     SSH_FXP_READLINK);
4291       abs_name = g_build_filename (enum_job->filename, g_file_info_get_name (info), NULL);
4292       put_string (command, abs_name);
4293       g_free (abs_name);
4294       queue_command_stream_and_free (&backend->command_connection, command,
4295                                      read_dir_readlink_reply,
4296                                      G_VFS_JOB (job), g_object_ref (info));
4297     }
4298   else
4299     g_vfs_job_enumerate_add_info (enum_job, info);
4300 }
4301 
4302 
4303 static void
read_dir_symlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4304 read_dir_symlink_reply (GVfsBackendSftp *backend,
4305                         int reply_type,
4306                         GDataInputStream *reply,
4307                         guint32 len,
4308                         GVfsJob *job,
4309                         gpointer user_data)
4310 {
4311   const char *name;
4312   GFileInfo *info;
4313   GFileInfo *lstat_info;
4314   ReadDirData *data;
4315 
4316   lstat_info = user_data;
4317   name = g_file_info_get_name (lstat_info);
4318   data = job->backend_data;
4319 
4320   if (reply_type == SSH_FXP_ATTRS)
4321     {
4322       info = g_file_info_new ();
4323       g_file_info_set_name (info, name);
4324       g_file_info_set_is_symlink (info, TRUE);
4325 
4326       parse_attributes (backend, info, name, reply, G_VFS_JOB_ENUMERATE (job)->attribute_matcher);
4327 
4328       read_dir_got_stat_info (backend, job, info);
4329 
4330       g_object_unref (info);
4331     }
4332   else
4333     read_dir_got_stat_info (backend, job, lstat_info);
4334 
4335   g_object_unref (lstat_info);
4336 
4337   if (--data->outstanding_requests == 0)
4338     g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (job));
4339 }
4340 
4341 static void
read_dir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4342 read_dir_reply (GVfsBackendSftp *backend,
4343                 int reply_type,
4344                 GDataInputStream *reply,
4345                 guint32 len,
4346                 GVfsJob *job,
4347                 gpointer user_data)
4348 {
4349   GVfsJobEnumerate *enum_job;
4350   guint32 count;
4351   int i;
4352   GDataOutputStream *command;
4353   ReadDirData *data;
4354 
4355   data = job->backend_data;
4356   enum_job = G_VFS_JOB_ENUMERATE (job);
4357 
4358   if (reply_type != SSH_FXP_NAME)
4359     {
4360       /* Ignore all error, including the expected END OF FILE.
4361        * Real errors are expected in open_dir anyway */
4362 
4363       /* Close handle */
4364 
4365       command = new_command_stream (backend,
4366                                     SSH_FXP_CLOSE);
4367       put_data_buffer (command, data->handle);
4368       queue_command_stream_and_free (&backend->command_connection, command,
4369                                      NULL,
4370                                      G_VFS_JOB (job), NULL);
4371 
4372       if (--data->outstanding_requests == 0)
4373         g_vfs_job_enumerate_done (enum_job);
4374 
4375       return;
4376     }
4377 
4378   count = g_data_input_stream_read_uint32 (reply, NULL, NULL);
4379   for (i = 0; i < count; i++)
4380     {
4381       GFileInfo *info;
4382       char *name;
4383       char *longname;
4384       char *abs_name;
4385 
4386       info = g_file_info_new ();
4387       name = read_string (reply, NULL);
4388       g_file_info_set_name (info, name);
4389 
4390       longname = read_string (reply, NULL);
4391       g_free (longname);
4392 
4393       parse_attributes (backend, info, name, reply, enum_job->attribute_matcher);
4394 
4395       if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK &&
4396           ! (enum_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
4397         {
4398           /* Default (at least for openssh) is for readdir to not follow symlinks.
4399              This was a symlink, and follow links was requested, so we need to manually follow it */
4400           command = new_command_stream (backend,
4401                                         SSH_FXP_STAT);
4402           abs_name = g_build_filename (enum_job->filename, name, NULL);
4403           put_string (command, abs_name);
4404           g_free (abs_name);
4405 
4406           queue_command_stream_and_free (&backend->command_connection, command,
4407                                          read_dir_symlink_reply,
4408                                          G_VFS_JOB (job), g_object_ref (info));
4409           data->outstanding_requests ++;
4410         }
4411       else if (strcmp (".", name) != 0 &&
4412                strcmp ("..", name) != 0)
4413         read_dir_got_stat_info (backend, job, info);
4414 
4415       g_object_unref (info);
4416       g_free (name);
4417     }
4418 
4419   command = new_command_stream (backend,
4420                                 SSH_FXP_READDIR);
4421   put_data_buffer (command, data->handle);
4422   queue_command_stream_and_free (&backend->command_connection, command,
4423                                  read_dir_reply,
4424                                  G_VFS_JOB (job), NULL);
4425 }
4426 
4427 static void
open_dir_error(GVfsBackendSftp * backend,GVfsJob * job,gint original_error,gint stat_error,GFileInfo * info,gpointer user_data)4428 open_dir_error (GVfsBackendSftp *backend,
4429 		GVfsJob *job,
4430 		gint original_error,
4431 		gint stat_error,
4432 		GFileInfo *info,
4433 		gpointer user_data)
4434 {
4435   if ((original_error == SSH_FX_FAILURE ||
4436        original_error == SSH_FX_NO_SUCH_FILE) &&
4437       info != NULL &&
4438       g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
4439     {
4440       g_vfs_job_failed (G_VFS_JOB (job),
4441 		        G_IO_ERROR,
4442 			G_IO_ERROR_NOT_DIRECTORY,
4443 			_("The file is not a directory"));
4444       return;
4445     }
4446 
4447   result_from_status_code (job, original_error, -1, -1);
4448 }
4449 
4450 static void
open_dir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4451 open_dir_reply (GVfsBackendSftp *backend,
4452                 int reply_type,
4453                 GDataInputStream *reply,
4454                 guint32 len,
4455                 GVfsJob *job,
4456                 gpointer user_data)
4457 {
4458   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4459   GDataOutputStream *command;
4460   ReadDirData *data;
4461 
4462   data = job->backend_data;
4463 
4464   if (reply_type == SSH_FXP_STATUS)
4465     {
4466       error_from_lstat (backend, job, read_status_code (reply),
4467 			G_VFS_JOB_ENUMERATE (job)->filename,
4468 			open_dir_error,
4469 			NULL);
4470       return;
4471     }
4472 
4473   if (reply_type != SSH_FXP_HANDLE)
4474     {
4475       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4476                         _("Invalid reply received"));
4477       return;
4478     }
4479 
4480   g_vfs_job_succeeded (G_VFS_JOB (job));
4481 
4482   data->handle = read_data_buffer (reply);
4483 
4484   command = new_command_stream (op_backend,
4485                                 SSH_FXP_READDIR);
4486   put_data_buffer (command, data->handle);
4487 
4488   data->outstanding_requests = 1;
4489 
4490   queue_command_stream_and_free (&op_backend->command_connection, command,
4491                                  read_dir_reply,
4492                                  G_VFS_JOB (job), NULL);
4493 }
4494 
4495 static gboolean
try_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * attribute_matcher,GFileQueryInfoFlags flags)4496 try_enumerate (GVfsBackend *backend,
4497                GVfsJobEnumerate *job,
4498                const char *filename,
4499                GFileAttributeMatcher *attribute_matcher,
4500                GFileQueryInfoFlags flags)
4501 {
4502   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4503   GDataOutputStream *command;
4504   ReadDirData *data;
4505 
4506   data = g_slice_new0 (ReadDirData);
4507 
4508   g_vfs_job_set_backend_data (G_VFS_JOB (job), data, (GDestroyNotify)read_dir_data_free);
4509   command = new_command_stream (op_backend,
4510                                 SSH_FXP_OPENDIR);
4511   put_string (command, filename);
4512 
4513   queue_command_stream_and_free (&op_backend->command_connection, command,
4514                                  open_dir_reply,
4515                                  G_VFS_JOB (job), NULL);
4516 
4517   return TRUE;
4518 }
4519 
4520 static void
query_info_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)4521 query_info_reply (GVfsBackendSftp *backend,
4522                   MultiReply *replies,
4523                   int n_replies,
4524                   GVfsJob *job,
4525                   gpointer user_data)
4526 {
4527   char *basename;
4528   int i;
4529   MultiReply *lstat_reply, *reply;
4530   GFileInfo *lstat_info;
4531   GVfsJobQueryInfo *op_job;
4532 
4533   op_job = G_VFS_JOB_QUERY_INFO (job);
4534 
4535   i = 0;
4536   lstat_reply = &replies[i++];
4537 
4538   if (lstat_reply->type == SSH_FXP_STATUS)
4539     {
4540       result_from_status (job, lstat_reply->data, -1, -1);
4541       return;
4542     }
4543   else if (lstat_reply->type != SSH_FXP_ATTRS)
4544     {
4545       g_vfs_job_failed (job,
4546                         G_IO_ERROR, G_IO_ERROR_FAILED,
4547                         "%s", _("Invalid reply received"));
4548       return;
4549     }
4550 
4551   basename = NULL;
4552   if (strcmp (op_job->filename, "/") != 0)
4553     basename = g_path_get_basename (op_job->filename);
4554 
4555   if (op_job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
4556     {
4557       parse_attributes (backend, op_job->file_info, basename,
4558                         lstat_reply->data, op_job->attribute_matcher);
4559     }
4560   else
4561     {
4562       /* Look at stat results */
4563       reply = &replies[i++];
4564 
4565       if (reply->type == SSH_FXP_ATTRS)
4566         {
4567           parse_attributes (backend, op_job->file_info, basename,
4568                             reply->data, op_job->attribute_matcher);
4569 
4570 
4571           lstat_info = g_file_info_new ();
4572           parse_attributes (backend, lstat_info, basename,
4573                             lstat_reply->data, op_job->attribute_matcher);
4574           if (g_file_info_get_is_symlink (lstat_info))
4575             g_file_info_set_is_symlink (op_job->file_info, TRUE);
4576           g_object_unref (lstat_info);
4577         }
4578       else
4579         {
4580           /* Broken symlink, use lstat data */
4581           parse_attributes (backend, op_job->file_info, basename,
4582                             lstat_reply->data, op_job->attribute_matcher);
4583         }
4584 
4585     }
4586 
4587   g_free (basename);
4588 
4589   if (g_file_attribute_matcher_matches (op_job->attribute_matcher,
4590                                         G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4591     {
4592       /* Look at readlink results */
4593       reply = &replies[i++];
4594 
4595       if (reply->type == SSH_FXP_NAME)
4596         {
4597           char *symlink_target;
4598 
4599           /* Skip count (always 1 for replies to SSH_FXP_READLINK) */
4600           g_data_input_stream_read_uint32 (reply->data, NULL, NULL);
4601           symlink_target = read_string (reply->data, NULL);
4602           g_file_info_set_symlink_target (op_job->file_info, symlink_target);
4603           g_free (symlink_target);
4604         }
4605     }
4606 
4607   g_vfs_job_succeeded (G_VFS_JOB (job));
4608 }
4609 
4610 static gboolean
try_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)4611 try_query_info (GVfsBackend *backend,
4612                 GVfsJobQueryInfo *job,
4613                 const char *filename,
4614                 GFileQueryInfoFlags flags,
4615                 GFileInfo *info,
4616                 GFileAttributeMatcher *matcher)
4617 {
4618   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4619   Command commands[3];
4620   GDataOutputStream *command;
4621   int n_commands;
4622 
4623   n_commands = 0;
4624 
4625   commands[n_commands].connection = &op_backend->command_connection;
4626   command = commands[n_commands++].cmd =
4627     new_command_stream (op_backend,
4628                         SSH_FXP_LSTAT);
4629   put_string (command, filename);
4630 
4631   if (! (job->flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
4632     {
4633       commands[n_commands].connection = &op_backend->command_connection;
4634       command = commands[n_commands++].cmd =
4635         new_command_stream (op_backend,
4636                             SSH_FXP_STAT);
4637       put_string (command, filename);
4638     }
4639 
4640   if (g_file_attribute_matcher_matches (job->attribute_matcher,
4641                                         G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET))
4642     {
4643       commands[n_commands].connection = &op_backend->command_connection;
4644       command = commands[n_commands++].cmd =
4645         new_command_stream (op_backend,
4646                             SSH_FXP_READLINK);
4647       put_string (command, filename);
4648     }
4649 
4650   queue_command_streams_and_free (commands, n_commands,
4651                                   query_info_reply,
4652                                   G_VFS_JOB (job), NULL);
4653 
4654   return TRUE;
4655 }
4656 
4657 static void
query_fs_info_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4658 query_fs_info_reply (GVfsBackendSftp *backend,
4659                      int reply_type,
4660                      GDataInputStream *reply,
4661                      guint32 len,
4662                      GVfsJob *job,
4663                      gpointer user_data)
4664 {
4665   GFileInfo *info = user_data;
4666   guint64 frsize, blocks, bfree, bavail, flags;
4667 
4668   if (reply_type == SSH_FXP_STATUS)
4669     {
4670       guint32 code = read_status_code (reply);
4671 
4672       if (code == SSH_FX_NO_SUCH_FILE)
4673         not_dir_or_not_exist_error (backend, job, G_VFS_JOB_QUERY_FS_INFO (job)->filename);
4674       else
4675         result_from_status_code (job, code, -1, -1);
4676       return;
4677     }
4678   else if (reply_type != SSH_FXP_EXTENDED_REPLY)
4679     {
4680       g_vfs_job_failed (job,
4681                         G_IO_ERROR, G_IO_ERROR_FAILED,
4682                         "%s", _("Invalid reply received"));
4683       return;
4684     }
4685 
4686   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* bsize */
4687   frsize = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4688   blocks = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4689   bfree = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4690   bavail = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4691   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* files */
4692   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* ffree */
4693   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* favail */
4694   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* fsid */
4695   flags = g_data_input_stream_read_uint64 (reply, NULL, NULL);
4696   g_data_input_stream_read_uint64 (reply, NULL, NULL); /* namemax */
4697 
4698   /* If free and available are both 0, treat it like the size information is
4699    * missing.
4700    * */
4701   if (bfree || bavail)
4702     {
4703       g_file_info_set_attribute_uint64 (info,
4704                                         G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
4705                                         frsize * bavail);
4706 
4707       g_file_info_set_attribute_uint64 (info,
4708                                         G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
4709                                         frsize * blocks);
4710 
4711       g_file_info_set_attribute_uint64 (info,
4712                                         G_FILE_ATTRIBUTE_FILESYSTEM_USED,
4713                                         frsize * (blocks - bfree));
4714     }
4715 
4716   g_file_info_set_attribute_boolean (info,
4717                                      G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
4718                                      flags & SSH_FXE_STATVFS_ST_RDONLY);
4719 
4720   g_vfs_job_succeeded (job);
4721 }
4722 
4723 static gboolean
try_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * matcher)4724 try_query_fs_info (GVfsBackend *backend,
4725                    GVfsJobQueryFsInfo *job,
4726                    const char *filename,
4727                    GFileInfo *info,
4728                    GFileAttributeMatcher *matcher)
4729 {
4730   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4731   GDataOutputStream *command;
4732 
4733   g_file_info_set_attribute_string (info,
4734                                     G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "sftp");
4735   g_file_info_set_attribute_boolean (info,
4736                                      G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE);
4737 
4738   if (has_extension (op_backend, SFTP_EXT_OPENSSH_STATVFS) &&
4739       (g_file_attribute_matcher_matches (matcher,
4740                                          G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
4741        g_file_attribute_matcher_matches (matcher,
4742                                          G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
4743        g_file_attribute_matcher_matches (matcher,
4744                                          G_FILE_ATTRIBUTE_FILESYSTEM_USED) ||
4745        g_file_attribute_matcher_matches (matcher,
4746                                          G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)))
4747     {
4748       command = new_command_stream (op_backend, SSH_FXP_EXTENDED);
4749       put_string (command, "statvfs@openssh.com");
4750       put_string (command, filename);
4751 
4752       queue_command_stream_and_free (&op_backend->command_connection, command,
4753                                      query_fs_info_reply,
4754                                      G_VFS_JOB (job), info);
4755     }
4756   else
4757     g_vfs_job_succeeded (G_VFS_JOB (job));
4758 
4759   return TRUE;
4760 }
4761 
4762 typedef struct {
4763    GFileInfo *info;
4764    GFileAttributeMatcher *attribute_matcher;
4765 } QueryInfoFStatData;
4766 
4767 static void
query_info_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4768 query_info_fstat_reply (GVfsBackendSftp *backend,
4769                         int reply_type,
4770                         GDataInputStream *reply,
4771                         guint32 len,
4772                         GVfsJob *job,
4773                         gpointer user_data)
4774 {
4775   QueryInfoFStatData *data = user_data;
4776   GFileInfo *file_info;
4777   GFileAttributeMatcher *attribute_matcher;
4778 
4779   file_info = data->info;
4780   attribute_matcher = data->attribute_matcher;
4781   g_slice_free (QueryInfoFStatData, data);
4782 
4783   if (reply_type == SSH_FXP_STATUS)
4784     {
4785       result_from_status (job, reply, -1, -1);
4786       return;
4787     }
4788 
4789   if (reply_type != SSH_FXP_ATTRS)
4790     {
4791       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4792                         _("Invalid reply received"));
4793       return;
4794     }
4795 
4796   parse_attributes (backend,
4797                     file_info,
4798                     NULL,
4799                     reply,
4800                     attribute_matcher);
4801 
4802   g_vfs_job_succeeded (G_VFS_JOB (job));
4803 }
4804 
4805 static gboolean
try_query_info_fstat(GVfsBackend * backend,GVfsJob * job,GVfsBackendHandle _handle,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)4806 try_query_info_fstat (GVfsBackend *backend,
4807                       GVfsJob *job,
4808                       GVfsBackendHandle _handle,
4809                       GFileInfo *info,
4810                       GFileAttributeMatcher *attribute_matcher)
4811 {
4812   SftpHandle *handle = _handle;
4813   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4814   GDataOutputStream *command;
4815   QueryInfoFStatData *data;
4816 
4817   command = new_command_stream (op_backend, SSH_FXP_FSTAT);
4818   put_data_buffer (command, handle->raw_handle);
4819 
4820   data = g_slice_new (QueryInfoFStatData);
4821   data->info = info;
4822   data->attribute_matcher = attribute_matcher;
4823   queue_command_stream_and_free (&op_backend->command_connection, command,
4824                                  query_info_fstat_reply,
4825                                  G_VFS_JOB (job), data);
4826 
4827   return TRUE;
4828 }
4829 
4830 static void
move_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4831 move_reply (GVfsBackendSftp *backend,
4832             int reply_type,
4833             GDataInputStream *reply,
4834             guint32 len,
4835             GVfsJob *job,
4836             gpointer user_data)
4837 {
4838   goffset *file_size;
4839 
4840   /* on any unknown error, return NOT_SUPPORTED to get the fallback implementation */
4841   if (reply_type == SSH_FXP_STATUS)
4842     {
4843       if (failure_from_status (job, reply, G_IO_ERROR_NOT_SUPPORTED, -1))
4844         {
4845           /* Succeeded, report file size */
4846           file_size = job->backend_data;
4847           if (file_size != NULL)
4848             g_vfs_job_progress_callback (*file_size, *file_size, job);
4849           g_vfs_job_succeeded (job);
4850         }
4851     }
4852   else
4853     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4854                       _("Invalid reply received"));
4855 }
4856 
4857 static void
move_do_rename(GVfsBackendSftp * backend,GVfsJob * job)4858 move_do_rename (GVfsBackendSftp *backend,
4859                 GVfsJob *job)
4860 {
4861   GVfsJobMove *op_job;
4862   GDataOutputStream *command;
4863 
4864   op_job = G_VFS_JOB_MOVE (job);
4865 
4866   command = new_command_stream (backend,
4867                                 SSH_FXP_RENAME);
4868   put_string (command, op_job->source);
4869   put_string (command, op_job->destination);
4870 
4871   queue_command_stream_and_free (&backend->command_connection, command,
4872                                  move_reply,
4873                                  G_VFS_JOB (job), NULL);
4874 }
4875 
4876 static void
move_delete_target_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)4877 move_delete_target_reply (GVfsBackendSftp *backend,
4878                           int reply_type,
4879                           GDataInputStream *reply,
4880                           guint32 len,
4881                           GVfsJob *job,
4882                           gpointer user_data)
4883 {
4884   if (reply_type == SSH_FXP_STATUS)
4885     {
4886       if (failure_from_status (job, reply, -1, -1))
4887         move_do_rename (backend, job);
4888     }
4889   else
4890     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
4891                       _("Invalid reply received"));
4892 }
4893 
4894 
4895 static void
move_lstat_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)4896 move_lstat_reply (GVfsBackendSftp *backend,
4897                   MultiReply *replies,
4898                   int n_replies,
4899                   GVfsJob *job,
4900                   gpointer user_data)
4901 {
4902   GVfsJobMove *op_job;
4903   gboolean destination_exist, source_is_dir, dest_is_dir;
4904   GDataOutputStream *command;
4905   GFileInfo *info;
4906   goffset *file_size;
4907 
4908   op_job = G_VFS_JOB_MOVE (job);
4909 
4910   if (replies[0].type == SSH_FXP_STATUS)
4911     {
4912       result_from_status (job, replies[0].data, -1, -1);
4913       return;
4914     }
4915   else if (replies[0].type != SSH_FXP_ATTRS)
4916     {
4917       g_vfs_job_failed (job,
4918                         G_IO_ERROR, G_IO_ERROR_FAILED,
4919                         "%s", _("Invalid reply received"));
4920       return;
4921     }
4922 
4923   info = g_file_info_new ();
4924   parse_attributes (backend, info, NULL,
4925                     replies[0].data, NULL);
4926   source_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
4927   file_size = g_new (goffset, 1);
4928   *file_size = g_file_info_get_size (info);
4929   g_vfs_job_set_backend_data (G_VFS_JOB (job), file_size, g_free);
4930   g_object_unref (info);
4931 
4932   destination_exist = FALSE;
4933   if (replies[1].type == SSH_FXP_ATTRS)
4934     {
4935       destination_exist = TRUE; /* Target file exists */
4936 
4937       info = g_file_info_new ();
4938       parse_attributes (backend, info, NULL,
4939                         replies[1].data, NULL);
4940       dest_is_dir = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
4941       g_object_unref (info);
4942 
4943       if (op_job->flags & G_FILE_COPY_OVERWRITE)
4944 	{
4945 
4946 	  /* Always fail on dirs, even with overwrite */
4947 	  if (dest_is_dir)
4948 	    {
4949               if (source_is_dir)
4950                 g_vfs_job_failed (job,
4951                                   G_IO_ERROR,
4952                                   G_IO_ERROR_WOULD_MERGE,
4953                                   _("Can’t move directory over directory"));
4954               else
4955                 g_vfs_job_failed (job,
4956                                   G_IO_ERROR,
4957                                   G_IO_ERROR_IS_DIRECTORY,
4958                                   _("File is directory"));
4959 	      return;
4960 	    }
4961 	}
4962       else
4963 	{
4964 	  g_vfs_job_failed (G_VFS_JOB (job),
4965 			    G_IO_ERROR,
4966 			    G_IO_ERROR_EXISTS,
4967 			    _("Target file already exists"));
4968 	  return;
4969 	}
4970     }
4971 
4972   /* TODO: Check flags & G_FILE_COPY_BACKUP */
4973 
4974   if (destination_exist && (op_job->flags & G_FILE_COPY_OVERWRITE))
4975     {
4976       command = new_command_stream (backend,
4977                                     SSH_FXP_REMOVE);
4978       put_string (command, op_job->destination);
4979       queue_command_stream_and_free (&backend->command_connection, command,
4980                                      move_delete_target_reply,
4981                                      G_VFS_JOB (job), NULL);
4982       return;
4983     }
4984 
4985   move_do_rename (backend, job);
4986 }
4987 
4988 
4989 static gboolean
try_move(GVfsBackend * backend,GVfsJobMove * job,const char * source,const char * destination,GFileCopyFlags flags,GFileProgressCallback progress_callback,gpointer progress_callback_data)4990 try_move (GVfsBackend *backend,
4991           GVfsJobMove *job,
4992           const char *source,
4993           const char *destination,
4994           GFileCopyFlags flags,
4995           GFileProgressCallback progress_callback,
4996           gpointer progress_callback_data)
4997 {
4998   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
4999   GDataOutputStream *command;
5000   Command commands[2];
5001 
5002   commands[0].connection = &op_backend->command_connection;
5003   command = commands[0].cmd =
5004     new_command_stream (op_backend,
5005                         SSH_FXP_LSTAT);
5006   put_string (command, source);
5007 
5008   commands[1].connection = &op_backend->command_connection;
5009   command = commands[1].cmd =
5010     new_command_stream (op_backend,
5011                         SSH_FXP_LSTAT);
5012   put_string (command, destination);
5013 
5014   queue_command_streams_and_free (commands, 2,
5015                                   move_lstat_reply,
5016                                   G_VFS_JOB (job), NULL);
5017 
5018   return TRUE;
5019 }
5020 
5021 static void
set_display_name_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5022 set_display_name_reply (GVfsBackendSftp *backend,
5023                         int reply_type,
5024                         GDataInputStream *reply,
5025                         guint32 len,
5026                         GVfsJob *job,
5027                         gpointer user_data)
5028 {
5029   if (reply_type == SSH_FXP_STATUS)
5030     result_from_status (job, reply, -1, -1);
5031   else
5032     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5033                       _("Invalid reply received"));
5034 }
5035 
5036 static gboolean
try_set_display_name(GVfsBackend * backend,GVfsJobSetDisplayName * job,const char * filename,const char * display_name)5037 try_set_display_name (GVfsBackend *backend,
5038                       GVfsJobSetDisplayName *job,
5039                       const char *filename,
5040                       const char *display_name)
5041 {
5042   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5043   GDataOutputStream *command;
5044   char *dirname, *basename, *new_name;
5045 
5046   /* We use the same setting as for local files. Can't really
5047    * do better, since there is no way in this version of sftp to find out
5048    * the remote charset encoding
5049    */
5050 
5051   dirname = g_path_get_dirname (filename);
5052   basename = g_filename_from_utf8 (display_name, -1, NULL, NULL, NULL);
5053   if (basename == NULL)
5054     basename = g_strdup (display_name);
5055   new_name = g_build_filename (dirname, basename, NULL);
5056   g_free (dirname);
5057   g_free (basename);
5058 
5059   g_vfs_job_set_display_name_set_new_path (job,
5060                                            new_name);
5061 
5062   command = new_command_stream (op_backend,
5063                                 SSH_FXP_RENAME);
5064   put_string (command, filename);
5065   put_string (command, new_name);
5066 
5067   queue_command_stream_and_free (&op_backend->command_connection, command,
5068                                  set_display_name_reply,
5069                                  G_VFS_JOB (job), NULL);
5070 
5071   g_free (new_name);
5072 
5073   return TRUE;
5074 }
5075 
5076 
5077 static void
make_symlink_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5078 make_symlink_reply (GVfsBackendSftp *backend,
5079                     int reply_type,
5080                     GDataInputStream *reply,
5081                     guint32 len,
5082                     GVfsJob *job,
5083                     gpointer user_data)
5084 {
5085   if (reply_type == SSH_FXP_STATUS)
5086     result_from_status (job, reply, G_IO_ERROR_EXISTS, -1);
5087   else
5088     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5089                       _("Invalid reply received"));
5090 }
5091 
5092 static gboolean
try_make_symlink(GVfsBackend * backend,GVfsJobMakeSymlink * job,const char * filename,const char * symlink_value)5093 try_make_symlink (GVfsBackend *backend,
5094                   GVfsJobMakeSymlink *job,
5095                   const char *filename,
5096                   const char *symlink_value)
5097 {
5098   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5099   GDataOutputStream *command;
5100 
5101   command = new_command_stream (op_backend,
5102                                 SSH_FXP_SYMLINK);
5103   /* Note: This is the reverse order of how this is documented in
5104      draft-ietf-secsh-filexfer-02.txt, but its how openssh does it. */
5105   put_string (command, symlink_value);
5106   put_string (command, filename);
5107 
5108   queue_command_stream_and_free (&op_backend->command_connection, command,
5109                                  make_symlink_reply,
5110                                  G_VFS_JOB (job), NULL);
5111 
5112   return TRUE;
5113 }
5114 
5115 static void
mkdir_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5116 mkdir_stat_reply (GVfsBackendSftp *backend,
5117                   int reply_type,
5118                   GDataInputStream *reply,
5119                   guint32 len,
5120                   GVfsJob *job,
5121                   gpointer user_data)
5122 {
5123   if (reply_type == SSH_FXP_STATUS)
5124     /* We got some error, but lets report the original FAILURE, as
5125        these extra errors are not really mkdir errors, we just wanted
5126        to implement EEXISTS */
5127     result_from_status_code (job, SSH_FX_FAILURE, -1, -1);
5128   else if (reply_type != SSH_FXP_ATTRS)
5129     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5130                       _("Invalid reply received"));
5131   else
5132     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_EXISTS,
5133                       _("Target file exists"));
5134 }
5135 
5136 static void
make_directory_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5137 make_directory_reply (GVfsBackendSftp *backend,
5138                       int reply_type,
5139                       GDataInputStream *reply,
5140                       guint32 len,
5141                       GVfsJob *job,
5142                       gpointer user_data)
5143 {
5144   if (reply_type == SSH_FXP_STATUS)
5145     {
5146       gint stat_error;
5147 
5148       stat_error = read_status_code (reply);
5149       if (stat_error == SSH_FX_FAILURE)
5150         {
5151           /* Generic SFTP error, let's stat the target */
5152           GDataOutputStream *command;
5153 
5154           command = new_command_stream (backend,
5155                                         SSH_FXP_LSTAT);
5156           put_string (command, G_VFS_JOB_MAKE_DIRECTORY (job)->filename);
5157           queue_command_stream_and_free (&backend->command_connection, command,
5158                                          mkdir_stat_reply,
5159                                          G_VFS_JOB (job), NULL);
5160         }
5161       else
5162         result_from_status_code (job, stat_error, -1, -1);
5163     }
5164   else
5165     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5166                       _("Invalid reply received"));
5167 }
5168 
5169 static gboolean
try_make_directory(GVfsBackend * backend,GVfsJobMakeDirectory * job,const char * filename)5170 try_make_directory (GVfsBackend *backend,
5171                     GVfsJobMakeDirectory *job,
5172                     const char *filename)
5173 {
5174   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5175   GDataOutputStream *command;
5176 
5177   command = new_command_stream (op_backend,
5178                                 SSH_FXP_MKDIR);
5179   put_string (command, filename);
5180   /* No file info - flag 0 */
5181   g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
5182 
5183   queue_command_stream_and_free (&op_backend->command_connection, command,
5184                                  make_directory_reply,
5185                                  G_VFS_JOB (job), NULL);
5186 
5187   return TRUE;
5188 }
5189 
5190 static void
delete_remove_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5191 delete_remove_reply (GVfsBackendSftp *backend,
5192                      int reply_type,
5193                      GDataInputStream *reply,
5194                      guint32 len,
5195                      GVfsJob *job,
5196                      gpointer user_data)
5197 {
5198   if (reply_type == SSH_FXP_STATUS)
5199     result_from_status (job, reply, -1, -1);
5200   else
5201     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5202                       _("Invalid reply received"));
5203 }
5204 
5205 static void
delete_rmdir_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5206 delete_rmdir_reply (GVfsBackendSftp *backend,
5207                     int reply_type,
5208                     GDataInputStream *reply,
5209                     guint32 len,
5210                     GVfsJob *job,
5211                     gpointer user_data)
5212 {
5213   if (reply_type == SSH_FXP_STATUS)
5214     result_from_status (job, reply, G_IO_ERROR_NOT_EMPTY, -1);
5215   else
5216     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5217                       _("Invalid reply received"));
5218 }
5219 
5220 static void
delete_lstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5221 delete_lstat_reply (GVfsBackendSftp *backend,
5222                     int reply_type,
5223                     GDataInputStream *reply,
5224                     guint32 len,
5225                     GVfsJob *job,
5226                     gpointer user_data)
5227 {
5228   if (reply_type == SSH_FXP_STATUS)
5229     result_from_status (job, reply, -1, -1);
5230   else if (reply_type != SSH_FXP_ATTRS)
5231     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5232                       _("Invalid reply received"));
5233   else
5234     {
5235       GFileInfo *info;
5236       GDataOutputStream *command;
5237 
5238       info = g_file_info_new ();
5239       parse_attributes (backend, info, NULL, reply, NULL);
5240 
5241       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
5242         {
5243           command = new_command_stream (backend,
5244                                         SSH_FXP_RMDIR);
5245           put_string (command, G_VFS_JOB_DELETE (job)->filename);
5246           queue_command_stream_and_free (&backend->command_connection, command,
5247                                          delete_rmdir_reply,
5248                                          G_VFS_JOB (job), NULL);
5249         }
5250       else
5251         {
5252           command = new_command_stream (backend,
5253                                         SSH_FXP_REMOVE);
5254           put_string (command, G_VFS_JOB_DELETE (job)->filename);
5255           queue_command_stream_and_free (&backend->command_connection, command,
5256                                          delete_remove_reply,
5257                                          G_VFS_JOB (job), NULL);
5258         }
5259 
5260       g_object_unref (info);
5261     }
5262 }
5263 
5264 static gboolean
try_delete(GVfsBackend * backend,GVfsJobDelete * job,const char * filename)5265 try_delete (GVfsBackend *backend,
5266             GVfsJobDelete *job,
5267             const char *filename)
5268 {
5269   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5270   GDataOutputStream *command;
5271 
5272   command = new_command_stream (op_backend,
5273                                 SSH_FXP_LSTAT);
5274   put_string (command, filename);
5275   queue_command_stream_and_free (&op_backend->command_connection, command,
5276                                  delete_lstat_reply,
5277                                  G_VFS_JOB (job), NULL);
5278 
5279   return TRUE;
5280 }
5281 
5282 static gboolean
try_query_settable_attributes(GVfsBackend * backend,GVfsJobQueryAttributes * job,const char * filename)5283 try_query_settable_attributes (GVfsBackend *backend,
5284 			       GVfsJobQueryAttributes *job,
5285 			       const char *filename)
5286 {
5287   GFileAttributeInfoList *list;
5288 
5289   list = g_file_attribute_info_list_new ();
5290 
5291   g_file_attribute_info_list_add (list,
5292 				  G_FILE_ATTRIBUTE_UNIX_MODE,
5293 				  G_FILE_ATTRIBUTE_TYPE_UINT32,
5294 				  G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
5295 				  G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5296 
5297   g_file_attribute_info_list_add (list,
5298                                   G_FILE_ATTRIBUTE_TIME_MODIFIED,
5299                                   G_FILE_ATTRIBUTE_TYPE_UINT64,
5300                                   G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE |
5301                                   G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5302 
5303   g_file_attribute_info_list_add (list,
5304                                   G_FILE_ATTRIBUTE_TIME_ACCESS,
5305                                   G_FILE_ATTRIBUTE_TYPE_UINT64,
5306                                   G_FILE_ATTRIBUTE_INFO_COPY_WHEN_MOVED);
5307 
5308   g_vfs_job_query_attributes_set_list (job, list);
5309   g_vfs_job_succeeded (G_VFS_JOB (job));
5310   g_file_attribute_info_list_unref (list);
5311 
5312   return TRUE;
5313 }
5314 
5315 static void
set_attribute_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5316 set_attribute_reply (GVfsBackendSftp *backend,
5317 		     int reply_type,
5318 		     GDataInputStream *reply,
5319 		     guint32 len,
5320 		     GVfsJob *job,
5321 		     gpointer user_data)
5322 {
5323   if (reply_type == SSH_FXP_STATUS)
5324     result_from_status (job, reply, -1, -1);
5325   else
5326     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5327 		      _("Invalid reply received"));
5328 }
5329 
5330 static void
set_attribute_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5331 set_attribute_stat_reply (GVfsBackendSftp *backend,
5332                           int reply_type,
5333                           GDataInputStream *reply,
5334                           guint32 len,
5335                           GVfsJob *job,
5336                           gpointer user_data)
5337 {
5338   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5339   GDataOutputStream *command;
5340   GVfsJobSetAttribute *op_job = G_VFS_JOB_SET_ATTRIBUTE (job);
5341 
5342   if (reply_type == SSH_FXP_ATTRS)
5343     {
5344       guint32 mtime;
5345       guint32 atime;
5346       GFileInfo *info = g_file_info_new ();
5347 
5348       parse_attributes (backend, info, NULL, reply, NULL);
5349 
5350       /* parse_attributes sets either both timestamps or none
5351        * so checking one of them is enough. */
5352       if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
5353         {
5354           g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
5355                             _("Operation not supported"));
5356           g_object_unref (info);
5357           return;
5358         }
5359       /* Timestamps must be read as uint64 but sftp only supports uint32 */
5360       if (op_job->value.uint64 > G_MAXUINT32)
5361         {
5362           g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5363                             _("Value out of range, sftp only supports 32bit timestamps"));
5364           g_object_unref (info);
5365           return;
5366         }
5367 
5368       if (g_strcmp0 (op_job->attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0)
5369         {
5370           mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
5371           atime = op_job->value.uint64;
5372         }
5373       else
5374         {
5375           atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
5376           mtime = op_job->value.uint64;
5377         }
5378 
5379       g_object_unref (info);
5380 
5381       command = new_command_stream (op_backend, SSH_FXP_SETSTAT);
5382       put_string (command, op_job->filename);
5383 
5384       g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_ACMODTIME, NULL, NULL);
5385       g_data_output_stream_put_uint32 (command, atime, NULL, NULL);
5386       g_data_output_stream_put_uint32 (command, mtime, NULL, NULL);
5387       queue_command_stream_and_free (&op_backend->command_connection, command,
5388                                      set_attribute_reply,
5389                                      G_VFS_JOB (job), NULL);
5390     }
5391   else if (reply_type == SSH_FXP_STATUS)
5392     result_from_status (job, reply, -1, -1);
5393   else
5394     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5395                       _("Invalid reply received"));
5396 }
5397 
5398 static gboolean
try_set_attribute(GVfsBackend * backend,GVfsJobSetAttribute * job,const char * filename,const char * attribute,GFileAttributeType type,gpointer value_p,GFileQueryInfoFlags flags)5399 try_set_attribute (GVfsBackend *backend,
5400 		   GVfsJobSetAttribute *job,
5401 		   const char *filename,
5402 		   const char *attribute,
5403 		   GFileAttributeType type,
5404 		   gpointer value_p,
5405 		   GFileQueryInfoFlags flags)
5406 {
5407   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
5408   GDataOutputStream *command;
5409 
5410   if (g_strcmp0 (attribute, G_FILE_ATTRIBUTE_UNIX_MODE) == 0)
5411     {
5412       if (type != G_FILE_ATTRIBUTE_TYPE_UINT32)
5413         {
5414           g_vfs_job_failed (G_VFS_JOB (job),
5415                             G_IO_ERROR,
5416                             G_IO_ERROR_INVALID_ARGUMENT,
5417                             _("Invalid attribute type (uint32 expected)"));
5418           return TRUE;
5419         }
5420 
5421       command = new_command_stream (op_backend,
5422                                     SSH_FXP_SETSTAT);
5423       put_string (command, filename);
5424       g_data_output_stream_put_uint32 (command, SSH_FILEXFER_ATTR_PERMISSIONS, NULL, NULL);
5425       g_data_output_stream_put_uint32 (command, (*(guint32 *)value_p) & 0777, NULL, NULL);
5426       queue_command_stream_and_free (&op_backend->command_connection, command,
5427                                      set_attribute_reply,
5428                                      G_VFS_JOB (job), NULL);
5429     }
5430   else if (g_strcmp0 (attribute, G_FILE_ATTRIBUTE_TIME_MODIFIED) == 0 ||
5431            g_strcmp0 (attribute, G_FILE_ATTRIBUTE_TIME_ACCESS) == 0)
5432     {
5433       if (type != G_FILE_ATTRIBUTE_TYPE_UINT64)
5434         {
5435           g_vfs_job_failed (G_VFS_JOB (job),
5436                             G_IO_ERROR,
5437                             G_IO_ERROR_INVALID_ARGUMENT,
5438                             _("Invalid attribute type (uint64 expected)"));
5439           return TRUE;
5440         }
5441 
5442       command = new_command_stream (op_backend, SSH_FXP_LSTAT);
5443       put_string (command, filename);
5444 
5445       queue_command_stream_and_free (&op_backend->command_connection, command,
5446                                      set_attribute_stat_reply,
5447                                      G_VFS_JOB (job), NULL);
5448     }
5449   else
5450     {
5451       g_vfs_job_failed (G_VFS_JOB (job),
5452                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
5453                         _("Operation not supported"));
5454     }
5455 
5456   return TRUE;
5457 }
5458 
5459 /* Return true if the job is finished or cancelled, failing it if needed. */
5460 static gboolean
check_finished_or_cancelled_job(GVfsJob * job)5461 check_finished_or_cancelled_job (GVfsJob *job)
5462 {
5463   if (g_vfs_job_is_finished (job))
5464     return TRUE;
5465 
5466   if (g_vfs_job_is_cancelled (job))
5467     {
5468       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_CANCELLED,
5469                         _("Operation was cancelled"));
5470       return TRUE;
5471     }
5472 
5473   return FALSE;
5474 }
5475 
5476 /* The push sliding window mechanism is based on the one in the OpenSSH sftp
5477  * client. */
5478 
5479 #define PUSH_MAX_REQUESTS 64
5480 
5481 typedef struct {
5482   /* Job context */
5483   GVfsBackendSftp *backend;
5484   GVfsJobPush *op_job;
5485   GVfsJob *job;
5486 
5487   /* Open files */
5488   DataBuffer *raw_handle;
5489   GInputStream *in;
5490 
5491   /* fstat information */
5492   goffset size;
5493   guint32 permissions;
5494   guint64 mtime;
5495   guint64 atime;
5496 
5497   /* state */
5498   goffset offset;
5499   goffset n_written;
5500   int num_req;
5501 
5502   /* replace data */
5503   char *tempname;
5504   int temp_count;
5505 
5506   char buffer[MAX_BUFFER_SIZE];
5507 } SftpPushHandle;
5508 
5509 typedef struct {
5510   SftpPushHandle *handle;
5511   gssize count;
5512 } PushWriteRequest;
5513 
5514 static void
sftp_push_handle_free(SftpPushHandle * handle)5515 sftp_push_handle_free (SftpPushHandle *handle)
5516 {
5517   GDataOutputStream *command;
5518 
5519   /* Only free the handle if there are no write requests outstanding and no
5520    * asynchronous reads pending. */
5521   if (handle->num_req == 0 && (!handle->in || !g_input_stream_has_pending (handle->in)))
5522     {
5523       if (handle->in)
5524         {
5525           g_input_stream_close_async (handle->in, 0, NULL, NULL, NULL);
5526           g_object_unref (handle->in);
5527         }
5528 
5529       /* If raw_handle is non-NULL, it means destination is still open. Close
5530        * it. */
5531       if (handle->raw_handle)
5532         {
5533           command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
5534           put_data_buffer (command, handle->raw_handle);
5535           queue_command_stream_and_free (&handle->backend->data_connection,
5536                                          command,
5537                                          NULL,
5538                                          handle->job, NULL);
5539           data_buffer_free (handle->raw_handle);
5540         }
5541 
5542       /* If tempname is non-NULL, it means we failed and should delete the temp
5543        * file. */
5544       if (handle->tempname)
5545         {
5546           command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
5547           put_string (command, handle->tempname);
5548           queue_command_stream_and_free (&handle->backend->command_connection,
5549                                          command,
5550                                          NULL,
5551                                          handle->job, NULL);
5552 
5553           g_free (handle->tempname);
5554         }
5555 
5556       g_object_unref (handle->backend);
5557       g_object_unref (handle->job);
5558       g_slice_free (SftpPushHandle, handle);
5559     }
5560 }
5561 
5562 static void
5563 push_read_cb (GObject *source, GAsyncResult *res, gpointer user_data);
5564 
5565 static void
push_enqueue_request(SftpPushHandle * handle)5566 push_enqueue_request (SftpPushHandle *handle)
5567 {
5568   g_input_stream_read_async (handle->in,
5569                              handle->buffer,
5570                              MAX_BUFFER_SIZE,
5571                              G_PRIORITY_DEFAULT,
5572                              NULL,
5573                              push_read_cb, handle);
5574   handle->num_req++;
5575 }
5576 
5577 static void
push_close_moved_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5578 push_close_moved_file (GVfsBackendSftp *backend,
5579                        int reply_type,
5580                        GDataInputStream *reply,
5581                        guint32 len,
5582                        GVfsJob *job,
5583                        gpointer user_data)
5584 {
5585   SftpPushHandle *handle = user_data;
5586 
5587   if (reply_type == SSH_FXP_STATUS)
5588     {
5589       guint32 code = read_status_code (reply);
5590       if (code == SSH_FX_OK)
5591         {
5592           if (handle->op_job->remove_source)
5593             g_unlink (handle->op_job->local_path);
5594 
5595           g_vfs_job_succeeded (job);
5596         }
5597       else
5598         result_from_status_code (job, code, -1, -1);
5599     }
5600   else
5601     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5602                       _("Invalid reply received"));
5603 
5604   sftp_push_handle_free (handle);
5605 }
5606 
5607 static void
push_close_deleted_file(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5608 push_close_deleted_file (GVfsBackendSftp *backend,
5609                         int reply_type,
5610                         GDataInputStream *reply,
5611                         guint32 len,
5612                         GVfsJob *job,
5613                         gpointer user_data)
5614 {
5615   SftpPushHandle *handle = user_data;
5616 
5617   if (reply_type == SSH_FXP_STATUS)
5618     {
5619       guint32 code = read_status_code (reply);
5620       if (code == SSH_FX_OK)
5621         {
5622           /* The delete completed successfully, now rename. */
5623           GDataOutputStream *command = new_command_stream (backend, SSH_FXP_RENAME);
5624           put_string (command, handle->tempname);
5625           put_string (command, handle->op_job->destination);
5626           queue_command_stream_and_free (&backend->command_connection, command,
5627                                          push_close_moved_file,
5628                                          job, handle);
5629 
5630           g_free (handle->tempname);
5631           handle->tempname = NULL;
5632           return;
5633         }
5634       else
5635         result_from_status_code (job, code, -1, -1);
5636     }
5637   else
5638     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5639                       _("Invalid reply received"));
5640 
5641   sftp_push_handle_free (handle);
5642 }
5643 
5644 static void
push_close_delete_or_succeed(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5645 push_close_delete_or_succeed (GVfsBackendSftp *backend,
5646                               int reply_type,
5647                               GDataInputStream *reply,
5648                               guint32 len,
5649                               GVfsJob *job,
5650                               gpointer user_data)
5651 {
5652   SftpPushHandle *handle = user_data;
5653 
5654   if (handle->tempname)
5655     {
5656       /* If we wrote to a temp file, do delete then rename. */
5657       GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
5658       put_string (command, handle->op_job->destination);
5659       queue_command_stream_and_free (&handle->backend->command_connection, command,
5660                                      push_close_deleted_file,
5661                                      handle->job, handle);
5662     }
5663   else
5664     {
5665       if (handle->op_job->remove_source)
5666         g_unlink (handle->op_job->local_path);
5667 
5668       g_vfs_job_succeeded (handle->job);
5669       sftp_push_handle_free (handle);
5670     }
5671 }
5672 
5673 static void
push_close_restore_permissions(SftpPushHandle * handle)5674 push_close_restore_permissions (SftpPushHandle *handle)
5675 {
5676   gboolean default_perms = (handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS);
5677   guint32 flags = SSH_FILEXFER_ATTR_ACMODTIME;
5678 
5679   if (!default_perms)
5680     flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
5681 
5682   /* Restore the source file's permissions and timestamps. */
5683   GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_SETSTAT);
5684   put_string (command, handle->tempname ? handle->tempname : handle->op_job->destination);
5685   g_data_output_stream_put_uint32 (command, flags, NULL, NULL);
5686   if (!default_perms)
5687     g_data_output_stream_put_uint32 (command, handle->permissions, NULL, NULL);
5688   g_data_output_stream_put_uint32 (command, handle->atime, NULL, NULL);
5689   g_data_output_stream_put_uint32 (command, handle->mtime, NULL, NULL);
5690   queue_command_stream_and_free (&handle->backend->command_connection, command,
5691                                  push_close_delete_or_succeed,
5692                                  handle->job, handle);
5693 }
5694 
5695 static void
push_close_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5696 push_close_stat_reply (GVfsBackendSftp *backend,
5697                        int reply_type,
5698                        GDataInputStream *reply,
5699                        guint32 len,
5700                        GVfsJob *job,
5701                        gpointer user_data)
5702 {
5703   SftpPushHandle *handle = user_data;
5704 
5705   if (reply_type == SSH_FXP_ATTRS)
5706     {
5707       GFileInfo *info = g_file_info_new ();
5708 
5709       parse_attributes (backend, info, NULL, reply, NULL);
5710 
5711       /* Don't fail on error, but fall back to the local atime
5712        * (assigned in push_source_fstat_cb). */
5713       if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_ACCESS))
5714         handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
5715 
5716       g_object_unref (info);
5717     }
5718 
5719   push_close_restore_permissions (handle);
5720 }
5721 
5722 static void
push_close_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5723 push_close_write_reply (GVfsBackendSftp *backend,
5724                         int reply_type,
5725                         GDataInputStream *reply,
5726                         guint32 len,
5727                         GVfsJob *job,
5728                         gpointer user_data)
5729 {
5730   SftpPushHandle *handle = user_data;
5731 
5732   if (reply_type == SSH_FXP_STATUS)
5733     {
5734       guint32 code = read_status_code (reply);
5735       if (code == SSH_FX_OK)
5736         {
5737           /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
5738           if (!handle->op_job->remove_source &&
5739               !(handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
5740             {
5741               GDataOutputStream *command = new_command_stream (backend, SSH_FXP_LSTAT);
5742               put_string (command, handle->op_job->destination);
5743               queue_command_stream_and_free (&backend->command_connection, command,
5744                                              push_close_stat_reply,
5745                                              job, handle);
5746               return;
5747             }
5748 
5749           push_close_restore_permissions (handle);
5750           return;
5751         }
5752       else
5753         result_from_status_code (job, code, -1, -1);
5754     }
5755   else
5756     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5757                       _("Invalid reply received"));
5758 
5759   sftp_push_handle_free (handle);
5760 }
5761 
5762 static void
push_finish(SftpPushHandle * handle)5763 push_finish (SftpPushHandle *handle)
5764 {
5765   GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
5766   put_data_buffer (command, handle->raw_handle);
5767   queue_command_stream_and_free (&handle->backend->data_connection, command,
5768                                  push_close_write_reply,
5769                                  handle->job, handle);
5770 
5771   data_buffer_free (handle->raw_handle);
5772   handle->raw_handle = NULL;
5773 }
5774 
5775 static void
push_source_close_cb(GObject * source,GAsyncResult * res,gpointer user_data)5776 push_source_close_cb (GObject *source, GAsyncResult *res, gpointer user_data)
5777 {
5778   SftpPushHandle *handle = user_data;
5779 
5780   g_input_stream_close_finish (handle->in, res, NULL);
5781   g_clear_object (&handle->in);
5782 
5783   if (check_finished_or_cancelled_job (handle->job))
5784     {
5785       sftp_push_handle_free (handle);
5786       return;
5787     }
5788 
5789   /* If there are no write requests outstanding, we are done. */
5790   if (handle->num_req == 0)
5791     push_finish(handle);
5792 }
5793 
5794 static void
push_write_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5795 push_write_reply (GVfsBackendSftp *backend,
5796                   int reply_type,
5797                   GDataInputStream *reply,
5798                   guint32 len,
5799                   GVfsJob *job,
5800                   gpointer user_data)
5801 {
5802   PushWriteRequest *request = user_data;
5803   SftpPushHandle *handle = request->handle;
5804   gssize count = request->count;
5805 
5806   g_slice_free (PushWriteRequest, request);
5807 
5808   handle->num_req--;
5809 
5810   if (check_finished_or_cancelled_job (job))
5811     {
5812       sftp_push_handle_free (handle);
5813       return;
5814     }
5815 
5816   if (reply_type == SSH_FXP_STATUS)
5817     {
5818       guint32 code = read_status_code (reply);
5819 
5820       if (code == SSH_FX_OK)
5821         {
5822           handle->n_written += count;
5823           g_vfs_job_progress_callback (handle->n_written, handle->size, job);
5824 
5825           /* Enqueue a read op if the file is still open, and there isn't
5826            * already one pending. */
5827           if (handle->in && !g_input_stream_has_pending (handle->in))
5828             push_enqueue_request (handle);
5829 
5830           /* We are done if the file is closed and there are no write requests
5831            * oustanding. */
5832           if (!handle->in && handle->num_req == 0)
5833             {
5834               push_finish (handle);
5835               return;
5836             }
5837         }
5838       else
5839         result_from_status_code (handle->job, code, -1, -1);
5840     }
5841   else
5842     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5843                       _("Invalid reply received"));
5844 
5845   sftp_push_handle_free (handle);
5846 }
5847 
5848 static void
push_read_cb(GObject * source,GAsyncResult * res,gpointer user_data)5849 push_read_cb (GObject *source, GAsyncResult *res, gpointer user_data)
5850 {
5851   PushWriteRequest *request;
5852   GDataOutputStream *command;
5853   gssize count;
5854 
5855   SftpPushHandle *handle = user_data;
5856   GError *error = NULL;
5857 
5858   count = g_input_stream_read_finish (handle->in, res, &error);
5859 
5860   if (check_finished_or_cancelled_job (handle->job))
5861     {
5862       g_clear_error (&error);
5863       sftp_push_handle_free (handle);
5864       return;
5865     }
5866 
5867   if (error)
5868     {
5869       g_vfs_job_failed_from_error (handle->job, error);
5870       g_error_free (error);
5871 
5872       sftp_push_handle_free (handle);
5873       return;
5874     }
5875 
5876   if (count == 0)
5877     {
5878       handle->num_req--;
5879 
5880       g_input_stream_close_async (handle->in,
5881                                   0, NULL,
5882                                   push_source_close_cb, handle);
5883       return;
5884     }
5885 
5886   request = g_slice_new (PushWriteRequest);
5887   request->handle = handle;
5888   request->count = count;
5889 
5890   command = new_command_stream (handle->backend, SSH_FXP_WRITE);
5891   put_data_buffer (command, handle->raw_handle);
5892   g_data_output_stream_put_uint64 (command, handle->offset, NULL, NULL);
5893   g_data_output_stream_put_uint32 (command, count, NULL, NULL);
5894   g_output_stream_write_all (G_OUTPUT_STREAM (command),
5895                              handle->buffer, count,
5896                              NULL, NULL, NULL);
5897   queue_command_stream_and_free (&handle->backend->data_connection, command,
5898                                  push_write_reply,
5899                                  handle->job, request);
5900   handle->offset += count;
5901 
5902   if (handle->num_req < PUSH_MAX_REQUESTS)
5903     push_enqueue_request (handle);
5904 }
5905 
5906 static void push_create_temp (SftpPushHandle *handle);
5907 
5908 static void
push_truncate_original_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5909 push_truncate_original_reply (GVfsBackendSftp *backend,
5910                               int reply_type,
5911                               GDataInputStream *reply,
5912                               guint32 len,
5913                               GVfsJob *job,
5914                               gpointer user_data)
5915 {
5916   SftpPushHandle *handle = user_data;
5917 
5918   if (reply_type == SSH_FXP_HANDLE)
5919     {
5920       handle->raw_handle = read_data_buffer (reply);
5921       push_enqueue_request (handle);
5922     }
5923   else if (reply_type == SSH_FXP_STATUS)
5924     result_from_status (job, reply, -1, -1);
5925   else
5926     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5927                       _("Invalid reply received"));
5928 
5929   sftp_push_handle_free (handle);
5930 }
5931 
5932 static void
push_create_temp_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)5933 push_create_temp_reply (GVfsBackendSftp *backend,
5934                         int reply_type,
5935                         GDataInputStream *reply,
5936                         guint32 len,
5937                         GVfsJob *job,
5938                         gpointer user_data)
5939 {
5940   SftpPushHandle *handle = user_data;
5941 
5942   if (reply_type == SSH_FXP_STATUS)
5943     {
5944       guint32 code = read_status_code (reply);
5945       if (code == SSH_FX_PERMISSION_DENIED)
5946         {
5947           /* The temp file creation failed. Try truncating the existing file. */
5948           GDataOutputStream *command;
5949 
5950           g_free (handle->tempname);
5951           handle->tempname = NULL;
5952 
5953           command = new_command_stream (backend, SSH_FXP_OPEN);
5954           put_string (command, handle->op_job->destination);
5955           g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_TRUNC,  NULL, NULL);
5956           g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
5957           queue_command_stream_and_free (&backend->data_connection, command,
5958                                          push_truncate_original_reply,
5959                                          job, handle);
5960 
5961           return;
5962         }
5963       else if (code == SSH_FX_FAILURE)
5964         {
5965           push_create_temp (handle);
5966           return;
5967         }
5968       else
5969         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5970                           _("Invalid reply received"));
5971     }
5972   else if (reply_type != SSH_FXP_HANDLE)
5973     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
5974                       _("Invalid reply received"));
5975   else
5976     {
5977       handle->raw_handle = read_data_buffer (reply);
5978       push_enqueue_request (handle);
5979     }
5980 
5981   sftp_push_handle_free (handle);
5982 }
5983 
5984 static void
push_create_temp(SftpPushHandle * handle)5985 push_create_temp (SftpPushHandle *handle)
5986 {
5987   GDataOutputStream *command;
5988   char *dirname;
5989   char basename[] = ".giosaveXXXXXX";
5990 
5991   /* Write to a temp file and then rename to replace. */
5992 
5993   handle->temp_count++;
5994 
5995   if (handle->temp_count == 100)
5996     {
5997       g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_FAILED,
5998                         _("Unable to create temporary file"));
5999       sftp_push_handle_free (handle);
6000       return;
6001     }
6002 
6003   g_free (handle->tempname);
6004   dirname = g_path_get_dirname (handle->op_job->destination);
6005   gvfs_randomize_string (basename + 8, 6);
6006   handle->tempname = g_build_filename (dirname, basename, NULL);
6007   g_free (dirname);
6008 
6009   command = new_command_stream (handle->backend, SSH_FXP_OPEN);
6010   put_string (command, handle->tempname);
6011   g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL,  NULL, NULL);
6012   g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
6013   queue_command_stream_and_free (&handle->backend->data_connection, command,
6014                                  push_create_temp_reply,
6015                                  handle->job, handle);
6016 }
6017 
6018 static void
push_open_stat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6019 push_open_stat_reply (GVfsBackendSftp *backend,
6020                       int reply_type,
6021                       GDataInputStream *reply,
6022                       guint32 len,
6023                       GVfsJob *job,
6024                       gpointer user_data)
6025 {
6026   SftpPushHandle *handle = user_data;
6027 
6028   if (reply_type == SSH_FXP_ATTRS)
6029     {
6030       GFileInfo *info;
6031       GFileType type;
6032 
6033       info = g_file_info_new ();
6034       parse_attributes (backend, info, NULL, reply, NULL);
6035       type = g_file_info_get_file_type (info);
6036       g_object_unref (info);
6037 
6038       if (type == G_FILE_TYPE_DIRECTORY)
6039         {
6040           /* We cannot overwrite a directory. */
6041           if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6042             g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
6043                               _("File is directory"));
6044 
6045           sftp_push_handle_free (handle);
6046           return;
6047         }
6048 
6049       push_create_temp (handle);
6050     }
6051   else
6052     {
6053       result_from_status (job, reply, -1, -1);
6054       sftp_push_handle_free (handle);
6055     }
6056 }
6057 
6058 static void
push_open_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6059 push_open_reply (GVfsBackendSftp *backend,
6060                  int reply_type,
6061                  GDataInputStream *reply,
6062                  guint32 len,
6063                  GVfsJob *job,
6064                  gpointer user_data)
6065 {
6066   SftpPushHandle *handle = user_data;
6067 
6068   if (reply_type == SSH_FXP_STATUS)
6069     {
6070       guint32 code = read_status_code (reply);
6071       if (code == SSH_FX_NO_SUCH_FILE)
6072         not_dir_or_not_exist_error (backend, job, handle->op_job->destination);
6073       else if (code == SSH_FX_FAILURE)
6074         {
6075           if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6076             {
6077               /* The destination probably exists. Let's see if we can overwrite
6078                * it. */
6079               GDataOutputStream *command = new_command_stream (backend, SSH_FXP_LSTAT);
6080               put_string (command, handle->op_job->destination);
6081               queue_command_stream_and_free (&backend->command_connection, command,
6082                                              push_open_stat_reply,
6083                                              job, handle);
6084               return;
6085             }
6086           else
6087             result_from_status_code (job, code, G_IO_ERROR_EXISTS, -1);
6088         }
6089       else
6090         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6091                           _("Invalid reply received"));
6092     }
6093   else if (reply_type != SSH_FXP_HANDLE)
6094     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6095                       _("Invalid reply received"));
6096   else
6097     {
6098       handle->raw_handle = read_data_buffer (reply);
6099       push_enqueue_request (handle);
6100     }
6101 
6102   sftp_push_handle_free (handle);
6103 }
6104 
6105 static void
push_source_fstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)6106 push_source_fstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6107 {
6108   GFileInputStream *fin = G_FILE_INPUT_STREAM (source);
6109   SftpPushHandle *handle = user_data;
6110   GError *error = NULL;
6111   GFileInfo *info;
6112   GDataOutputStream *command;
6113 
6114   info = g_file_input_stream_query_info_finish (fin, res, &error);
6115   if (info)
6116     {
6117       handle->permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777;
6118       handle->size = g_file_info_get_size (info);
6119       handle->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
6120       handle->atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS);
6121 
6122       command = new_command_stream (handle->backend, SSH_FXP_OPEN);
6123       put_string (command, handle->op_job->destination);
6124       g_data_output_stream_put_uint32 (command, SSH_FXF_WRITE|SSH_FXF_CREAT|SSH_FXF_EXCL, NULL, NULL);
6125       g_data_output_stream_put_uint32 (command, 0, NULL, NULL);
6126       queue_command_stream_and_free (&handle->backend->data_connection, command,
6127                                      push_open_reply,
6128                                      handle->job, handle);
6129     }
6130   else
6131     {
6132       g_vfs_job_failed_from_error (handle->job, error);
6133       g_error_free (error);
6134       sftp_push_handle_free (handle);
6135     }
6136 }
6137 
6138 static void
push_source_open_cb(GObject * source,GAsyncResult * res,gpointer user_data)6139 push_source_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6140 {
6141   GFile *source_file = G_FILE (source);
6142   SftpPushHandle *handle = user_data;
6143   GError *error = NULL;
6144   GFileInputStream *fin;
6145 
6146   fin = g_file_read_finish (source_file, res, &error);
6147   if (fin)
6148     {
6149       handle->in = G_INPUT_STREAM (fin);
6150 
6151       g_file_input_stream_query_info_async (fin,
6152                                             G_FILE_ATTRIBUTE_STANDARD_SIZE ","
6153                                             G_FILE_ATTRIBUTE_UNIX_MODE ","
6154                                             G_FILE_ATTRIBUTE_TIME_MODIFIED ","
6155                                             G_FILE_ATTRIBUTE_TIME_ACCESS,
6156                                             0, NULL,
6157                                             push_source_fstat_cb, handle);
6158     }
6159   else
6160     {
6161       if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_IS_DIRECTORY)
6162         {
6163           /* Fall back to default implementation to improve the error message */
6164           g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6165                             _("Operation not supported"));
6166         }
6167       else
6168         g_vfs_job_failed_from_error (handle->job, error);
6169 
6170       g_error_free (error);
6171       sftp_push_handle_free (handle);
6172     }
6173 }
6174 
6175 static void
push_source_lstat_cb(GObject * source,GAsyncResult * res,gpointer user_data)6176 push_source_lstat_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6177 {
6178   GFile *source_file = G_FILE (source);
6179   SftpPushHandle *handle = user_data;
6180   GError *error = NULL;
6181   GFileInfo *info;
6182 
6183   info = g_file_query_info_finish (source_file, res, &error);
6184   if (!info)
6185     {
6186       g_vfs_job_failed_from_error (handle->job, error);
6187       g_error_free (error);
6188       sftp_push_handle_free (handle);
6189       return;
6190     }
6191 
6192   if ((handle->op_job->flags & G_FILE_COPY_NOFOLLOW_SYMLINKS) &&
6193       g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK)
6194     {
6195       /* Fall back to default implementation to copy symlink */
6196       g_vfs_job_failed (handle->job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6197                         _("Operation not supported"));
6198       sftp_push_handle_free (handle);
6199       return;
6200     }
6201 
6202   g_file_read_async (source_file, 0, NULL, push_source_open_cb, handle);
6203 }
6204 
6205 static gboolean
try_push(GVfsBackend * backend,GVfsJobPush * op_job,const char * destination,const char * local_path,GFileCopyFlags flags,gboolean remove_source,GFileProgressCallback progress_callback,gpointer progress_callback_data)6206 try_push (GVfsBackend *backend,
6207           GVfsJobPush *op_job,
6208           const char *destination,
6209           const char *local_path,
6210           GFileCopyFlags flags,
6211           gboolean remove_source,
6212           GFileProgressCallback progress_callback,
6213           gpointer progress_callback_data)
6214 {
6215   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
6216   GFile *source;
6217   SftpPushHandle *handle;
6218 
6219   if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
6220     {
6221       g_vfs_job_failed (G_VFS_JOB (op_job),
6222                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6223                         _("Operation not supported"));
6224       return TRUE;
6225     }
6226 
6227   if (!connection_is_usable (&op_backend->data_connection))
6228     {
6229       g_vfs_job_failed (G_VFS_JOB (op_job),
6230                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6231                         _("Operation not supported"));
6232       return TRUE;
6233     }
6234 
6235   handle = g_slice_new0 (SftpPushHandle);
6236   handle->backend = g_object_ref (op_backend);
6237   handle->job = g_object_ref (G_VFS_JOB (op_job));
6238   handle->op_job = op_job;
6239 
6240   source = g_file_new_for_path (local_path);
6241   g_file_query_info_async (source,
6242                            G_FILE_ATTRIBUTE_STANDARD_TYPE,
6243                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
6244                            0, NULL,
6245                            push_source_lstat_cb, handle);
6246   g_object_unref (source);
6247 
6248   return TRUE;
6249 }
6250 
6251 /* The pull sliding window mechanism is based on the one from the OpenSSH sftp
6252  * client. It is complicated because requests can be returned out of order. */
6253 
6254 #define PULL_MAX_REQUESTS 64  /* Never have more than this many requests outstanding */
6255 #define PULL_SIZE_INCOMPLETE -1  /* Indicates an incomplete fstat() request */
6256 #define PULL_SIZE_INVALID -2  /* Indicates that no fstat() request is in progress */
6257 
6258 typedef struct {
6259   /* initial job information */
6260   GVfsBackendSftp *backend;
6261   GVfsJob *job;
6262   GVfsJobPull *op_job;
6263   GFile *dest;
6264 
6265   /* Open files */
6266   DataBuffer *raw_handle;
6267   GOutputStream *output;
6268 
6269   /* fstat information */
6270   goffset size;
6271   guint32 mode;
6272   guint64 mtime;
6273   guint64 atime;
6274 
6275   /* state */
6276   goffset offset;
6277   goffset n_written;
6278   int num_req; /* Number of outstanding read requests */
6279   int max_req; /* Current maximum number of outstanding read requests */
6280   GList *queued_writes;
6281 } SftpPullHandle;
6282 
6283 typedef struct {
6284   SftpPullHandle *handle;
6285   guint32 request_len;    /* number of bytes requested */
6286   guint64 request_offset; /* offset of requested bytes */
6287   gssize response_len;     /* number of bytes returned */
6288   gssize write_offset;     /* offset in buffer of bytes written so far */
6289   char *buffer;
6290 } PullRequest;
6291 
6292 static void
6293 pull_enqueue_next_request (SftpPullHandle *handle);
6294 
6295 static void
6296 pull_enqueue_request (SftpPullHandle *handle, guint64 offset, guint32 len);
6297 
6298 static void
6299 pull_try_start_write (SftpPullHandle *handle);
6300 
6301 static void
pull_request_free(PullRequest * request)6302 pull_request_free (PullRequest *request)
6303 {
6304   if (request->buffer)
6305       g_slice_free1 (request->response_len, request->buffer);
6306   g_slice_free (PullRequest, request);
6307 }
6308 
6309 static void
sftp_pull_handle_free(SftpPullHandle * handle)6310 sftp_pull_handle_free (SftpPullHandle *handle)
6311 {
6312   if (handle->size != PULL_SIZE_INCOMPLETE && /* fstat complete */
6313       (!handle->output || !g_output_stream_has_pending (handle->output)) && /* no writes outstanding */
6314       handle->num_req == 0) /* no reads oustanding */
6315     {
6316       if (handle->raw_handle)
6317         {
6318           GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_CLOSE);
6319           put_data_buffer (command, handle->raw_handle);
6320           queue_command_stream_and_free (&handle->backend->data_connection, command,
6321                                          NULL,
6322                                          handle->job, NULL);
6323           data_buffer_free (handle->raw_handle);
6324         }
6325       g_clear_object (&handle->output);
6326       g_object_unref(handle->backend);
6327       g_object_unref(handle->op_job);
6328       g_object_unref(handle->dest);
6329       g_list_free_full (handle->queued_writes, (GDestroyNotify)pull_request_free);
6330       g_slice_free (SftpPullHandle, handle);
6331     }
6332 }
6333 
6334 static void
pull_remove_source_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6335 pull_remove_source_reply (GVfsBackendSftp *backend,
6336                           int reply_type,
6337                           GDataInputStream *reply,
6338                           guint32 len,
6339                           GVfsJob *job,
6340                           gpointer user_data)
6341 {
6342   if (reply_type == SSH_FXP_STATUS)
6343     result_from_status (job, reply, -1, -1);
6344   else
6345     g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6346                       _("Invalid reply received"));
6347 }
6348 
6349 static void
pull_set_perms_cb(GObject * source,GAsyncResult * res,gpointer user_data)6350 pull_set_perms_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6351 {
6352   SftpPullHandle *handle = user_data;
6353 
6354   if (handle->op_job->remove_source)
6355     {
6356       GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
6357       put_string (command, handle->op_job->source);
6358       queue_command_stream_and_free (&handle->backend->command_connection,
6359                                      command,
6360                                      pull_remove_source_reply,
6361                                      handle->job,
6362                                      NULL);
6363     }
6364   else
6365     g_vfs_job_succeeded (handle->job);
6366 
6367   sftp_pull_handle_free (handle);
6368 }
6369 
6370 static void
pull_close_cb(GObject * source,GAsyncResult * res,gpointer user_data)6371 pull_close_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6372 {
6373   SftpPullHandle *handle = user_data;
6374   GError *error = NULL;
6375 
6376   if (g_output_stream_close_finish(handle->output, res, &error))
6377     {
6378       g_vfs_job_progress_callback (handle->n_written, handle->n_written, handle->job);
6379 
6380       if (handle->size >= 0)
6381         {
6382           GFileInfo *info = g_file_info_new ();
6383           if (!(handle->op_job->flags & G_FILE_COPY_TARGET_DEFAULT_PERMS))
6384             g_file_info_set_attribute_uint32 (info,
6385                                               G_FILE_ATTRIBUTE_UNIX_MODE,
6386                                               handle->mode);
6387           g_file_info_set_attribute_uint64 (info,
6388                                             G_FILE_ATTRIBUTE_TIME_MODIFIED,
6389                                             handle->mtime);
6390           /* Atime is COPY_WHEN_MOVED, but not COPY_WITH_FILE. */
6391           if (handle->op_job->remove_source ||
6392               (handle->op_job->flags & G_FILE_COPY_ALL_METADATA))
6393             g_file_info_set_attribute_uint64 (info,
6394                                               G_FILE_ATTRIBUTE_TIME_ACCESS,
6395                                               handle->atime);
6396           g_file_set_attributes_async (handle->dest,
6397                                        info,
6398                                        G_FILE_QUERY_INFO_NONE,
6399                                        G_PRIORITY_DEFAULT,
6400                                        NULL,
6401                                        pull_set_perms_cb, handle);
6402           g_object_unref (info);
6403           return;
6404         }
6405 
6406       if (handle->op_job->remove_source)
6407         {
6408           GDataOutputStream *command = new_command_stream (handle->backend, SSH_FXP_REMOVE);
6409           put_string (command, handle->op_job->source);
6410           queue_command_stream_and_free (&handle->backend->command_connection,
6411                                          command,
6412                                          pull_remove_source_reply,
6413                                          handle->job,
6414                                          NULL);
6415         }
6416       else
6417         g_vfs_job_succeeded (handle->job);
6418     }
6419   else
6420     {
6421       g_vfs_job_failed_from_error (handle->job, error);
6422       g_error_free (error);
6423     }
6424 
6425   sftp_pull_handle_free (handle);
6426 }
6427 
6428 static void
pull_try_finish(SftpPullHandle * handle)6429 pull_try_finish (SftpPullHandle *handle)
6430 {
6431   if (handle->max_req == 0 && /* received EOF */
6432       handle->size != PULL_SIZE_INCOMPLETE && /* fstat complete */
6433       !g_output_stream_has_pending (handle->output) && /* no writes outstanding */
6434       handle->num_req == 0) /* no reads oustanding */
6435     {
6436       g_output_stream_close_async (handle->output,
6437                                    G_PRIORITY_DEFAULT,
6438                                    NULL,
6439                                    pull_close_cb, handle);
6440     }
6441 }
6442 
6443 static void
pull_write_cb(GObject * source,GAsyncResult * res,gpointer user_data)6444 pull_write_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6445 {
6446   PullRequest *request = user_data;
6447   SftpPullHandle *handle = request->handle;
6448   GError *error = NULL;
6449   gssize n_written;
6450 
6451   n_written = g_output_stream_write_finish (handle->output, res, &error);
6452   if (n_written == -1)
6453     {
6454       g_vfs_job_failed_from_error (handle->job, error);
6455       g_error_free (error);
6456       pull_request_free (request);
6457       sftp_pull_handle_free (handle);
6458       return;
6459     }
6460 
6461   handle->n_written += n_written;
6462   request->write_offset += n_written;
6463 
6464   /* If we didn't write everything, do another write */
6465   if (request->write_offset < request->response_len)
6466     {
6467       g_output_stream_write_async (handle->output,
6468                                    request->buffer + request->write_offset,
6469                                    request->response_len - request->write_offset,
6470                                    G_PRIORITY_DEFAULT,
6471                                    NULL,
6472                                    pull_write_cb, request);
6473       return;
6474     }
6475 
6476   if (handle->size >= 0)
6477     g_vfs_job_progress_callback (handle->n_written, handle->size, handle->job);
6478 
6479   pull_try_start_write (handle);
6480 
6481   /* If we read short, issue another request for the remaining data. */
6482   if (request->response_len < request->request_len)
6483     pull_enqueue_request (handle,
6484                           request->request_offset + request->response_len,
6485                           request->request_len - request->response_len);
6486   else if (handle->max_req == 0)
6487     pull_try_finish (handle);
6488   else
6489     {
6490       /* Once we have requested past the estimated EOF, request one at a
6491        * time.  Otherwise try increase the number of concurrent requests. */
6492       if (handle->offset > handle->size)
6493         handle->max_req = 1;
6494       else if (handle->max_req < PULL_MAX_REQUESTS)
6495         handle->max_req++;
6496 
6497       while (handle->num_req < handle->max_req)
6498         pull_enqueue_next_request (handle);
6499     }
6500 
6501   pull_request_free (request);
6502 }
6503 
6504 static void
pull_try_start_write(SftpPullHandle * handle)6505 pull_try_start_write (SftpPullHandle *handle)
6506 {
6507   GError *error = NULL;
6508   PullRequest *request;
6509 
6510   if (g_output_stream_has_pending (handle->output))
6511     return;
6512 
6513   if (!handle->queued_writes)
6514     return;
6515 
6516   request = handle->queued_writes->data;
6517   handle->queued_writes = g_list_delete_link (handle->queued_writes, handle->queued_writes);
6518 
6519   if (!g_seekable_seek (G_SEEKABLE (handle->output),
6520                         request->request_offset, G_SEEK_SET,
6521                         NULL, &error))
6522     {
6523       g_vfs_job_failed_from_error (handle->job, error);
6524       g_error_free (error);
6525       pull_request_free (request);
6526       sftp_pull_handle_free (handle);
6527       return;
6528     }
6529 
6530   g_output_stream_write_async (handle->output,
6531                                request->buffer, request->response_len,
6532                                G_PRIORITY_DEFAULT,
6533                                NULL,
6534                                pull_write_cb, request);
6535 }
6536 
6537 static void
pull_read_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6538 pull_read_reply (GVfsBackendSftp *backend,
6539                  int reply_type,
6540                  GDataInputStream *reply,
6541                  guint32 len,
6542                  GVfsJob *job,
6543                  gpointer user_data)
6544 {
6545   PullRequest *request = user_data;
6546   SftpPullHandle *handle = request->handle;
6547 
6548   handle->num_req--;
6549 
6550   if (check_finished_or_cancelled_job (job))
6551     {
6552     }
6553   else if (reply_type == SSH_FXP_STATUS)
6554     {
6555       guint32 code = read_status_code (reply);
6556       if (code == SSH_FX_EOF)
6557         {
6558           pull_request_free (request);
6559           handle->max_req = 0;
6560           pull_try_finish (handle);
6561           return;
6562         }
6563       else
6564         result_from_status_code (job, code, -1, -1);
6565     }
6566   else if (reply_type != SSH_FXP_DATA)
6567     {
6568       g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6569                         _("Invalid reply received"));
6570     }
6571   else
6572     {
6573       request->response_len = g_data_input_stream_read_uint32 (reply, NULL, NULL);
6574       request->buffer = g_slice_alloc (request->response_len);
6575 
6576       if (g_input_stream_read_all (G_INPUT_STREAM (reply),
6577                                    request->buffer, request->response_len,
6578                                    NULL, NULL, NULL))
6579         {
6580           handle->queued_writes = g_list_append (handle->queued_writes, request);
6581           pull_try_start_write (handle);
6582           return;
6583         }
6584       else
6585         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6586                           _("Invalid reply received"));
6587     }
6588 
6589   pull_request_free (request);
6590   sftp_pull_handle_free (handle);
6591 }
6592 
6593 static void
pull_enqueue_request(SftpPullHandle * handle,guint64 offset,guint32 len)6594 pull_enqueue_request (SftpPullHandle *handle, guint64 offset, guint32 len)
6595 {
6596   PullRequest *request;
6597   GDataOutputStream *command;
6598 
6599   request = g_slice_new0 (PullRequest);
6600   request->handle = handle;
6601   request->request_len = len;
6602   request->request_offset = offset;
6603 
6604   command = new_command_stream (handle->backend, SSH_FXP_READ);
6605   put_data_buffer (command, handle->raw_handle);
6606   g_data_output_stream_put_uint64 (command, offset, NULL, NULL);
6607   g_data_output_stream_put_uint32 (command, len, NULL, NULL);
6608   queue_command_stream_and_free (&handle->backend->data_connection, command,
6609                                  pull_read_reply,
6610                                  handle->job, request);
6611 
6612   handle->num_req++;
6613 }
6614 
6615 static void
pull_enqueue_next_request(SftpPullHandle * handle)6616 pull_enqueue_next_request (SftpPullHandle *handle)
6617 {
6618   pull_enqueue_request (handle, handle->offset, MAX_BUFFER_SIZE);
6619   handle->offset += MAX_BUFFER_SIZE;
6620 }
6621 
6622 static void
pull_fstat_reply(GVfsBackendSftp * backend,int reply_type,GDataInputStream * reply,guint32 len,GVfsJob * job,gpointer user_data)6623 pull_fstat_reply (GVfsBackendSftp *backend,
6624                   int reply_type,
6625                   GDataInputStream *reply,
6626                   guint32 len,
6627                   GVfsJob *job,
6628                   gpointer user_data)
6629 {
6630   SftpPullHandle *handle = user_data;
6631 
6632   if (check_finished_or_cancelled_job (job))
6633     {
6634       handle->size = PULL_SIZE_INVALID;
6635       sftp_pull_handle_free (handle);
6636       return;
6637     }
6638 
6639   if (reply_type == SSH_FXP_ATTRS)
6640     {
6641       GFileInfo *info = g_file_info_new ();
6642       parse_attributes (backend, info, NULL, reply, NULL);
6643       handle->size = g_file_info_get_size (info);
6644       handle->mode = g_file_info_get_attribute_uint32 (info,
6645                                                        G_FILE_ATTRIBUTE_UNIX_MODE);
6646       handle->mtime = g_file_info_get_attribute_uint64 (info,
6647                                                         G_FILE_ATTRIBUTE_TIME_MODIFIED);
6648       handle->atime = g_file_info_get_attribute_uint64 (info,
6649                                                         G_FILE_ATTRIBUTE_TIME_ACCESS);
6650       g_object_unref (info);
6651     }
6652   else
6653     handle->size = PULL_SIZE_INVALID;
6654 
6655   pull_try_finish (handle);
6656 }
6657 
6658 static void
pull_dest_open_cb(GObject * source,GAsyncResult * res,gpointer user_data)6659 pull_dest_open_cb (GObject *source, GAsyncResult *res, gpointer user_data)
6660 {
6661   SftpPullHandle *handle = user_data;
6662   GError *error = NULL;
6663 
6664   if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6665     handle->output = G_OUTPUT_STREAM (g_file_replace_finish (handle->dest,
6666                                                              res,
6667                                                              &error));
6668   else
6669     handle->output = G_OUTPUT_STREAM (g_file_create_finish (handle->dest,
6670                                                             res,
6671                                                             &error));
6672   if (handle->output)
6673     {
6674       /* Do an fstat() to find out the size and mode of the file. */
6675       GDataOutputStream *command = new_command_stream (handle->backend,
6676                                                        SSH_FXP_FSTAT);
6677       put_data_buffer (command, handle->raw_handle);
6678       queue_command_stream_and_free (&handle->backend->data_connection,
6679                                      command,
6680                                      pull_fstat_reply,
6681                                      handle->job,
6682                                      handle);
6683       handle->size = PULL_SIZE_INCOMPLETE;
6684 
6685       while (handle->num_req < handle->max_req)
6686         pull_enqueue_next_request (handle);
6687     }
6688   else
6689     {
6690       g_vfs_job_failed_from_error (handle->job, error);
6691       g_error_free (error);
6692       sftp_pull_handle_free (handle);
6693     }
6694 }
6695 
6696 static void
pull_open_reply(GVfsBackendSftp * backend,MultiReply * replies,int n_replies,GVfsJob * job,gpointer user_data)6697 pull_open_reply (GVfsBackendSftp *backend,
6698                  MultiReply *replies,
6699                  int n_replies,
6700                  GVfsJob *job,
6701                  gpointer user_data)
6702 {
6703   SftpPullHandle *handle = user_data;
6704 
6705   if (replies[0].type == SSH_FXP_ATTRS)
6706     {
6707       GFileType type;
6708       GFileInfo *info = g_file_info_new ();
6709 
6710       parse_attributes (backend, info, NULL, replies[0].data, NULL);
6711       type = g_file_info_get_file_type (info);
6712       g_object_unref (info);
6713 
6714       if (type != G_FILE_TYPE_REGULAR)
6715         {
6716           /* Fall back to default implementation to copy non-regular files */
6717           g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6718                             _("Operation not supported"));
6719         }
6720       else if (replies[1].type == SSH_FXP_STATUS)
6721         result_from_status (job, replies[1].data, -1, -1);
6722       else if (replies[1].type != SSH_FXP_HANDLE)
6723         g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_FAILED,
6724                           _("Invalid reply received"));
6725       else
6726         {
6727           /* We got a valid file handle. */
6728           handle->raw_handle = read_data_buffer (replies[1].data);
6729 
6730           if (handle->op_job->flags & G_FILE_COPY_OVERWRITE)
6731             g_file_replace_async (handle->dest,
6732                                   NULL,
6733                                   handle->op_job->flags & G_FILE_COPY_BACKUP ? TRUE : FALSE,
6734                                   G_FILE_CREATE_REPLACE_DESTINATION,
6735                                   G_PRIORITY_DEFAULT,
6736                                   NULL,
6737                                   pull_dest_open_cb, handle);
6738           else
6739             g_file_create_async (handle->dest,
6740                                  G_FILE_CREATE_NONE,
6741                                  G_PRIORITY_DEFAULT,
6742                                  NULL,
6743                                  pull_dest_open_cb, handle);
6744           return;
6745         }
6746     }
6747   else if (replies[0].type == SSH_FXP_STATUS)
6748     result_from_status (job, replies[0].data, -1, -1);
6749   else
6750     g_vfs_job_failed (job,
6751                       G_IO_ERROR, G_IO_ERROR_FAILED,
6752                       "%s", _("Invalid reply received"));
6753 
6754   /* If we got a file handle, store it.  It will be closed when the
6755    * SftpPushHandle is freed. */
6756   if (replies[1].type == SSH_FXP_HANDLE && !handle->raw_handle)
6757     handle->raw_handle = read_data_buffer (replies[1].data);
6758 
6759   sftp_pull_handle_free (handle);
6760 }
6761 
6762 static gboolean
try_pull(GVfsBackend * backend,GVfsJobPull * job,const char * source,const char * local_path,GFileCopyFlags flags,gboolean remove_source,GFileProgressCallback progress_callback,gpointer progress_callback_data)6763 try_pull (GVfsBackend *backend,
6764           GVfsJobPull *job,
6765           const char *source,
6766           const char *local_path,
6767           GFileCopyFlags flags,
6768           gboolean remove_source,
6769           GFileProgressCallback progress_callback,
6770           gpointer progress_callback_data)
6771 {
6772   GVfsBackendSftp *op_backend = G_VFS_BACKEND_SFTP (backend);
6773   SftpPullHandle *handle;
6774   Command commands[2];
6775 
6776   if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
6777     {
6778       g_vfs_job_failed (G_VFS_JOB (job),
6779                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6780                         _("Operation not supported"));
6781       return TRUE;
6782     }
6783 
6784   if (!connection_is_usable (&op_backend->data_connection))
6785     {
6786       g_vfs_job_failed (G_VFS_JOB (job),
6787                         G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
6788                         _("Operation not supported"));
6789       return TRUE;
6790     }
6791 
6792   handle = g_slice_new0 (SftpPullHandle);
6793   handle->backend = g_object_ref (op_backend);
6794   handle->op_job = g_object_ref (job);
6795   handle->job = G_VFS_JOB (job);
6796   handle->dest = g_file_new_for_path (local_path);
6797   handle->size = PULL_SIZE_INVALID;
6798   handle->max_req = 1;
6799 
6800   commands[0].connection = &op_backend->command_connection;
6801   commands[0].cmd = new_command_stream (op_backend,
6802                                         flags & G_FILE_COPY_NOFOLLOW_SYMLINKS ? SSH_FXP_LSTAT : SSH_FXP_STAT);
6803   put_string (commands[0].cmd, source);
6804 
6805   commands[1].connection = &op_backend->data_connection;
6806   commands[1].cmd = new_command_stream (op_backend, SSH_FXP_OPEN);
6807   put_string (commands[1].cmd, source);
6808   g_data_output_stream_put_uint32 (commands[1].cmd, SSH_FXF_READ, NULL, NULL);
6809   g_data_output_stream_put_uint32 (commands[1].cmd, 0, NULL, NULL);
6810 
6811   queue_command_streams_and_free (commands, 2,
6812                                   pull_open_reply,
6813                                   G_VFS_JOB(job),
6814                                   handle);
6815 
6816   return TRUE;
6817 }
6818 
6819 static void
g_vfs_backend_sftp_class_init(GVfsBackendSftpClass * klass)6820 g_vfs_backend_sftp_class_init (GVfsBackendSftpClass *klass)
6821 {
6822   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
6823   GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
6824 
6825   id_q = g_quark_from_static_string ("command-id");
6826 
6827   gobject_class->finalize = g_vfs_backend_sftp_finalize;
6828 
6829   backend_class->mount = real_do_mount;
6830   backend_class->try_mount = try_mount;
6831   backend_class->try_unmount = try_unmount;
6832   backend_class->try_open_for_read = try_open_for_read;
6833   backend_class->try_read = try_read;
6834   backend_class->try_seek_on_read = try_seek_on_read;
6835   backend_class->try_close_read = try_close_read;
6836   backend_class->try_close_write = try_close_write;
6837   backend_class->try_query_info = try_query_info;
6838   backend_class->try_query_fs_info = try_query_fs_info;
6839   backend_class->try_query_info_on_read = (gpointer) try_query_info_fstat;
6840   backend_class->try_query_info_on_write = (gpointer) try_query_info_fstat;
6841   backend_class->try_enumerate = try_enumerate;
6842   backend_class->try_create = try_create;
6843   backend_class->try_append_to = try_append_to;
6844   backend_class->try_replace = try_replace;
6845   backend_class->try_write = try_write;
6846   backend_class->try_seek_on_write = try_seek_on_write;
6847   backend_class->try_truncate = try_truncate;
6848   backend_class->try_move = try_move;
6849   backend_class->try_make_symlink = try_make_symlink;
6850   backend_class->try_make_directory = try_make_directory;
6851   backend_class->try_delete = try_delete;
6852   backend_class->try_set_display_name = try_set_display_name;
6853   backend_class->try_query_settable_attributes = try_query_settable_attributes;
6854   backend_class->try_set_attribute = try_set_attribute;
6855   backend_class->try_push = try_push;
6856   backend_class->try_pull = try_pull;
6857 }
6858