1 /* Virtual File System: SFTP file system.
2    The internal functions
3 
4    Copyright (C) 2011-2021
5    Free Software Foundation, Inc.
6 
7    Written by:
8    Ilia Maslakov <il.smind@gmail.com>, 2011
9    Slava Zanko <slavazanko@gmail.com>, 2011, 2012
10 
11    This file is part of the Midnight Commander.
12 
13    The Midnight Commander is free software: you can redistribute it
14    and/or modify it under the terms of the GNU General Public License as
15    published by the Free Software Foundation, either version 3 of the License,
16    or (at your option) any later version.
17 
18    The Midnight Commander is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21    GNU General Public License for more details.
22 
23    You should have received a copy of the GNU General Public License
24    along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include <config.h>
28 #include <errno.h>
29 
30 #ifdef HAVE_SYS_SELECT_H
31 #include <sys/select.h>
32 #else
33 #include <sys/time.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #endif
37 
38 #include "lib/global.h"
39 #include "lib/util.h"
40 
41 #include "internal.h"
42 
43 /*** global variables ****************************************************************************/
44 
45 GString *sftpfs_filename_buffer = NULL;
46 
47 /*** file scope macro definitions ****************************************************************/
48 
49 /*** file scope type declarations ****************************************************************/
50 
51 /*** file scope variables ************************************************************************/
52 
53 /* --------------------------------------------------------------------------------------------- */
54 /*** file scope functions ************************************************************************/
55 /* --------------------------------------------------------------------------------------------- */
56 
57 /* Adjust block size and number of blocks */
58 
59 static void
sftpfs_blksize(struct stat * s)60 sftpfs_blksize (struct stat *s)
61 {
62 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
63     s->st_blksize = LIBSSH2_CHANNEL_WINDOW_DEFAULT;     /* FIXME */
64 #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
65     vfs_adjust_stat (s);
66 }
67 
68 /* --------------------------------------------------------------------------------------------- */
69 /**
70  * Awaiting for any activity on socket.
71  *
72  * @param super extra data for SFTP connection
73  * @param mcerror    pointer to the error object
74  * @return 0 if success, negative value otherwise
75  */
76 
77 static int
sftpfs_internal_waitsocket(sftpfs_super_t * super,GError ** mcerror)78 sftpfs_internal_waitsocket (sftpfs_super_t * super, GError ** mcerror)
79 {
80     struct timeval timeout = { 10, 0 };
81     fd_set fd;
82     fd_set *writefd = NULL;
83     fd_set *readfd = NULL;
84     int dir, ret;
85 
86     mc_return_val_if_error (mcerror, -1);
87 
88     FD_ZERO (&fd);
89     FD_SET (super->socket_handle, &fd);
90 
91     /* now make sure we wait in the correct direction */
92     dir = libssh2_session_block_directions (super->session);
93 
94     if ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) != 0)
95         readfd = &fd;
96 
97     if ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) != 0)
98         writefd = &fd;
99 
100     ret = select (super->socket_handle + 1, readfd, writefd, NULL, &timeout);
101     if (ret < 0)
102     {
103         int my_errno = errno;
104 
105         mc_propagate_error (mcerror, my_errno, _("sftp: socket error: %s"),
106                             unix_error_string (my_errno));
107     }
108 
109     return ret;
110 }
111 
112 /* --------------------------------------------------------------------------------------------- */
113 
114 static gboolean
sftpfs_op_init(sftpfs_super_t ** super,const vfs_path_element_t ** path_element,const vfs_path_t * vpath,GError ** mcerror)115 sftpfs_op_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
116                 const vfs_path_t * vpath, GError ** mcerror)
117 {
118     struct vfs_s_super *lc_super = NULL;
119 
120     mc_return_val_if_error (mcerror, FALSE);
121 
122     if (vfs_s_get_path (vpath, &lc_super, 0) == NULL)
123         return FALSE;
124 
125     if (lc_super == NULL)
126         return FALSE;
127 
128     *super = SFTP_SUPER (lc_super);
129 
130     if ((*super)->sftp_session == NULL)
131         return FALSE;
132 
133     *path_element = vfs_path_get_by_index (vpath, -1);
134 
135     return TRUE;
136 }
137 
138 /* --------------------------------------------------------------------------------------------- */
139 
140 static int
sftpfs_stat_init(sftpfs_super_t ** super,const vfs_path_element_t ** path_element,const vfs_path_t * vpath,GError ** mcerror,int stat_type,LIBSSH2_SFTP_ATTRIBUTES * attrs)141 sftpfs_stat_init (sftpfs_super_t ** super, const vfs_path_element_t ** path_element,
142                   const vfs_path_t * vpath, GError ** mcerror, int stat_type,
143                   LIBSSH2_SFTP_ATTRIBUTES * attrs)
144 {
145     int res;
146 
147     if (!sftpfs_op_init (super, path_element, vpath, mcerror))
148         return -1;
149 
150     do
151     {
152         const GString *fixfname;
153 
154         fixfname = sftpfs_fix_filename ((*path_element)->path);
155 
156         res = libssh2_sftp_stat_ex ((*super)->sftp_session, fixfname->str, fixfname->len,
157                                     stat_type, attrs);
158         if (res >= 0)
159             break;
160 
161         if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_PERMISSION_DENIED))
162             return -EACCES;
163 
164         if (sftpfs_is_sftp_error ((*super)->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
165             return -ENOENT;
166 
167         if (!sftpfs_waitsocket (*super, res, mcerror))
168             return -1;
169     }
170     while (res == LIBSSH2_ERROR_EAGAIN);
171 
172     return res;
173 }
174 
175 /* --------------------------------------------------------------------------------------------- */
176 /*** public functions ****************************************************************************/
177 /* --------------------------------------------------------------------------------------------- */
178 
179 gboolean
sftpfs_waitsocket(sftpfs_super_t * super,int sftp_res,GError ** mcerror)180 sftpfs_waitsocket (sftpfs_super_t * super, int sftp_res, GError ** mcerror)
181 {
182     if (sftp_res != LIBSSH2_ERROR_EAGAIN)
183     {
184         sftpfs_ssherror_to_gliberror (super, sftp_res, mcerror);
185         return FALSE;
186     }
187 
188     sftpfs_internal_waitsocket (super, mcerror);
189 
190     return (mcerror == NULL || *mcerror == NULL);
191 }
192 
193 /* --------------------------------------------------------------------------------------------- */
194 
195 gboolean
sftpfs_is_sftp_error(LIBSSH2_SFTP * sftp_session,int sftp_res,int sftp_error)196 sftpfs_is_sftp_error (LIBSSH2_SFTP * sftp_session, int sftp_res, int sftp_error)
197 {
198     return (sftp_res == LIBSSH2_ERROR_SFTP_PROTOCOL &&
199             libssh2_sftp_last_error (sftp_session) == (unsigned long) sftp_error);
200 }
201 
202 /* --------------------------------------------------------------------------------------------- */
203 /**
204  * Convert libssh error to GError object.
205  *
206  * @param super   extra data for SFTP connection
207  * @param libssh_errno errno from libssh
208  * @param mcerror      pointer to the error object
209  */
210 
211 void
sftpfs_ssherror_to_gliberror(sftpfs_super_t * super,int libssh_errno,GError ** mcerror)212 sftpfs_ssherror_to_gliberror (sftpfs_super_t * super, int libssh_errno, GError ** mcerror)
213 {
214     char *err = NULL;
215     int err_len;
216 
217     mc_return_if_error (mcerror);
218 
219     libssh2_session_last_error (super->session, &err, &err_len, 1);
220     if (libssh_errno == LIBSSH2_ERROR_SFTP_PROTOCOL && super->sftp_session != NULL)
221         mc_propagate_error (mcerror, libssh_errno, "%s %lu", err,
222                             libssh2_sftp_last_error (super->sftp_session));
223     else
224         mc_propagate_error (mcerror, libssh_errno, "%s", err);
225     g_free (err);
226 }
227 
228 /* --------------------------------------------------------------------------------------------- */
229 /**
230  * Fix filename for SFTP operations: add leading slash to file name.
231  *
232  * @param file_name file name
233  * @param length length of returned string
234  *
235  * @return pointer to string that contains the file name with leading slash
236  */
237 
238 const GString *
sftpfs_fix_filename(const char * file_name)239 sftpfs_fix_filename (const char *file_name)
240 {
241     g_string_printf (sftpfs_filename_buffer, "%c%s", PATH_SEP, file_name);
242     return sftpfs_filename_buffer;
243 }
244 
245 /* --------------------------------------------------------------------------------------------- */
246 
247 void
sftpfs_attr_to_stat(const LIBSSH2_SFTP_ATTRIBUTES * attrs,struct stat * s)248 sftpfs_attr_to_stat (const LIBSSH2_SFTP_ATTRIBUTES * attrs, struct stat *s)
249 {
250     if ((attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0)
251     {
252         s->st_uid = attrs->uid;
253         s->st_gid = attrs->gid;
254     }
255 
256     if ((attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0)
257     {
258         s->st_atime = attrs->atime;
259         s->st_mtime = attrs->mtime;
260         s->st_ctime = attrs->mtime;
261 #ifdef HAVE_STRUCT_STAT_ST_MTIM
262         s->st_atim.tv_nsec = s->st_mtim.tv_nsec = s->st_ctim.tv_nsec = 0;
263 #endif
264     }
265 
266     if ((attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) != 0)
267     {
268         s->st_size = attrs->filesize;
269         sftpfs_blksize (s);
270     }
271 
272     if ((attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0)
273         s->st_mode = attrs->permissions;
274 }
275 
276 /* --------------------------------------------------------------------------------------------- */
277 /**
278  * Getting information about a symbolic link.
279  *
280  * @param vpath   path to file, directory or symbolic link
281  * @param buf     buffer for store stat-info
282  * @param mcerror pointer to error object
283  * @return 0 if success, negative value otherwise
284  */
285 
286 int
sftpfs_lstat(const vfs_path_t * vpath,struct stat * buf,GError ** mcerror)287 sftpfs_lstat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
288 {
289     sftpfs_super_t *super = NULL;
290     const vfs_path_element_t *path_element = NULL;
291     LIBSSH2_SFTP_ATTRIBUTES attrs;
292     int res;
293 
294     res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
295     if (res >= 0)
296     {
297         sftpfs_attr_to_stat (&attrs, buf);
298         res = 0;
299     }
300 
301     return res;
302 }
303 
304 /* --------------------------------------------------------------------------------------------- */
305 /**
306  * Getting information about a file or directory.
307  *
308  * @param vpath   path to file or directory
309  * @param buf     buffer for store stat-info
310  * @param mcerror pointer to error object
311  * @return 0 if success, negative value otherwise
312  */
313 
314 int
sftpfs_stat(const vfs_path_t * vpath,struct stat * buf,GError ** mcerror)315 sftpfs_stat (const vfs_path_t * vpath, struct stat *buf, GError ** mcerror)
316 {
317     sftpfs_super_t *super = NULL;
318     const vfs_path_element_t *path_element = NULL;
319     LIBSSH2_SFTP_ATTRIBUTES attrs;
320     int res;
321 
322     res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_STAT, &attrs);
323     if (res >= 0)
324     {
325         buf->st_nlink = 1;
326         sftpfs_attr_to_stat (&attrs, buf);
327         res = 0;
328     }
329 
330     return res;
331 }
332 
333 /* --------------------------------------------------------------------------------------------- */
334 /**
335  * Read value of a symbolic link.
336  *
337  * @param vpath   path to file or directory
338  * @param buf     buffer for store stat-info
339  * @param size    buffer size
340  * @param mcerror pointer to error object
341  * @return 0 if success, negative value otherwise
342  */
343 
344 int
sftpfs_readlink(const vfs_path_t * vpath,char * buf,size_t size,GError ** mcerror)345 sftpfs_readlink (const vfs_path_t * vpath, char *buf, size_t size, GError ** mcerror)
346 {
347     sftpfs_super_t *super = NULL;
348     const vfs_path_element_t *path_element = NULL;
349     int res;
350 
351     if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
352         return -1;
353 
354     do
355     {
356         const GString *fixfname;
357 
358         fixfname = sftpfs_fix_filename (path_element->path);
359 
360         res =
361             libssh2_sftp_symlink_ex (super->sftp_session, fixfname->str, fixfname->len, buf, size,
362                                      LIBSSH2_SFTP_READLINK);
363         if (res >= 0)
364             break;
365 
366         if (!sftpfs_waitsocket (super, res, mcerror))
367             return -1;
368     }
369     while (res == LIBSSH2_ERROR_EAGAIN);
370 
371     return res;
372 }
373 
374 /* --------------------------------------------------------------------------------------------- */
375 /**
376  * Create symlink to file or directory
377  *
378  * @param vpath1  path to file or directory
379  * @param vpath2  path to symlink
380  * @param mcerror pointer to error object
381  * @return 0 if success, negative value otherwise
382  */
383 
384 int
sftpfs_symlink(const vfs_path_t * vpath1,const vfs_path_t * vpath2,GError ** mcerror)385 sftpfs_symlink (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
386 {
387     sftpfs_super_t *super = NULL;
388     const vfs_path_element_t *path_element1;
389     const vfs_path_element_t *path_element2 = NULL;
390     const GString *ctmp_path;
391     char *tmp_path;
392     unsigned int tmp_path_len;
393     int res;
394 
395     if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
396         return -1;
397 
398     ctmp_path = sftpfs_fix_filename (path_element2->path);
399     tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
400     tmp_path_len = ctmp_path->len;
401 
402     path_element1 = vfs_path_get_by_index (vpath1, -1);
403 
404     do
405     {
406         const GString *fixfname;
407 
408         fixfname = sftpfs_fix_filename (path_element1->path);
409 
410         res =
411             libssh2_sftp_symlink_ex (super->sftp_session, fixfname->str, fixfname->len, tmp_path,
412                                      tmp_path_len, LIBSSH2_SFTP_SYMLINK);
413         if (res >= 0)
414             break;
415 
416         if (!sftpfs_waitsocket (super, res, mcerror))
417         {
418             g_free (tmp_path);
419             return -1;
420         }
421     }
422     while (res == LIBSSH2_ERROR_EAGAIN);
423     g_free (tmp_path);
424 
425     return 0;
426 }
427 
428 /* --------------------------------------------------------------------------------------------- */
429 /**
430  * Changes the times of the file.
431  *
432  * @param vpath   path to file or directory
433  * @param atime   access time
434  * @param mtime   modification time
435  * @param mcerror pointer to error object
436  * @return 0 if success, negative value otherwise
437  */
438 
439 int
sftpfs_utime(const vfs_path_t * vpath,time_t atime,time_t mtime,GError ** mcerror)440 sftpfs_utime (const vfs_path_t * vpath, time_t atime, time_t mtime, GError ** mcerror)
441 {
442     sftpfs_super_t *super = NULL;
443     const vfs_path_element_t *path_element = NULL;
444     LIBSSH2_SFTP_ATTRIBUTES attrs;
445     int res;
446 
447     res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
448     if (res < 0)
449         return res;
450 
451     attrs.atime = atime;
452     attrs.mtime = mtime;
453 
454     do
455     {
456         const GString *fixfname;
457 
458         fixfname = sftpfs_fix_filename (path_element->path);
459 
460         res =
461             libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
462                                   LIBSSH2_SFTP_SETSTAT, &attrs);
463         if (res >= 0)
464             break;
465 
466         if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
467             return -ENOENT;
468 
469         if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
470         {
471             res = 0;            /* need something like ftpfs_ignore_chattr_errors */
472             break;
473         }
474 
475         if (!sftpfs_waitsocket (super, res, mcerror))
476             return -1;
477     }
478     while (res == LIBSSH2_ERROR_EAGAIN);
479 
480     return res;
481 }
482 
483 /* --------------------------------------------------------------------------------------------- */
484 /**
485  * Changes the permissions of the file.
486  *
487  * @param vpath   path to file or directory
488  * @param mode    mode (see man 2 open)
489  * @param mcerror pointer to error object
490  * @return 0 if success, negative value otherwise
491  */
492 
493 int
sftpfs_chmod(const vfs_path_t * vpath,mode_t mode,GError ** mcerror)494 sftpfs_chmod (const vfs_path_t * vpath, mode_t mode, GError ** mcerror)
495 {
496     sftpfs_super_t *super = NULL;
497     const vfs_path_element_t *path_element = NULL;
498     LIBSSH2_SFTP_ATTRIBUTES attrs;
499     int res;
500 
501     res = sftpfs_stat_init (&super, &path_element, vpath, mcerror, LIBSSH2_SFTP_LSTAT, &attrs);
502     if (res < 0)
503         return res;
504 
505     attrs.permissions = mode;
506 
507     do
508     {
509         const GString *fixfname;
510 
511         fixfname = sftpfs_fix_filename (path_element->path);
512 
513         res =
514             libssh2_sftp_stat_ex (super->sftp_session, fixfname->str, fixfname->len,
515                                   LIBSSH2_SFTP_SETSTAT, &attrs);
516         if (res >= 0)
517             break;
518 
519         if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_NO_SUCH_FILE))
520             return -ENOENT;
521 
522         if (sftpfs_is_sftp_error (super->sftp_session, res, LIBSSH2_FX_FAILURE))
523         {
524             res = 0;            /* need something like ftpfs_ignore_chattr_errors */
525             break;
526         }
527 
528         if (!sftpfs_waitsocket (super, res, mcerror))
529             return -1;
530     }
531     while (res == LIBSSH2_ERROR_EAGAIN);
532 
533     return res;
534 }
535 
536 /* --------------------------------------------------------------------------------------------- */
537 /**
538  * Delete a name from the file system.
539  *
540  * @param vpath   path to file or directory
541  * @param mcerror pointer to error object
542  * @return 0 if success, negative value otherwise
543  */
544 
545 int
sftpfs_unlink(const vfs_path_t * vpath,GError ** mcerror)546 sftpfs_unlink (const vfs_path_t * vpath, GError ** mcerror)
547 {
548     sftpfs_super_t *super = NULL;
549     const vfs_path_element_t *path_element = NULL;
550     int res;
551 
552     if (!sftpfs_op_init (&super, &path_element, vpath, mcerror))
553         return -1;
554 
555     do
556     {
557         const GString *fixfname;
558 
559         fixfname = sftpfs_fix_filename (path_element->path);
560 
561         res = libssh2_sftp_unlink_ex (super->sftp_session, fixfname->str, fixfname->len);
562         if (res >= 0)
563             break;
564 
565         if (!sftpfs_waitsocket (super, res, mcerror))
566             return -1;
567     }
568     while (res == LIBSSH2_ERROR_EAGAIN);
569 
570     return res;
571 }
572 
573 /* --------------------------------------------------------------------------------------------- */
574 /**
575  * Rename a file, moving it between directories if required.
576  *
577  * @param vpath1   path to source file or directory
578  * @param vpath2   path to destination file or directory
579  * @param mcerror  pointer to error object
580  * @return 0 if success, negative value otherwise
581  */
582 
583 int
sftpfs_rename(const vfs_path_t * vpath1,const vfs_path_t * vpath2,GError ** mcerror)584 sftpfs_rename (const vfs_path_t * vpath1, const vfs_path_t * vpath2, GError ** mcerror)
585 {
586     sftpfs_super_t *super = NULL;
587     const vfs_path_element_t *path_element1;
588     const vfs_path_element_t *path_element2 = NULL;
589     const GString *ctmp_path;
590     char *tmp_path;
591     unsigned int tmp_path_len;
592     int res;
593 
594     if (!sftpfs_op_init (&super, &path_element2, vpath2, mcerror))
595         return -1;
596 
597     ctmp_path = sftpfs_fix_filename (path_element2->path);
598     tmp_path = g_strndup (ctmp_path->str, ctmp_path->len);
599     tmp_path_len = ctmp_path->len;
600 
601     path_element1 = vfs_path_get_by_index (vpath1, -1);
602 
603     do
604     {
605         const GString *fixfname;
606 
607         fixfname = sftpfs_fix_filename (path_element1->path);
608 
609         res =
610             libssh2_sftp_rename_ex (super->sftp_session, fixfname->str, fixfname->len, tmp_path,
611                                     tmp_path_len, LIBSSH2_SFTP_SYMLINK);
612         if (res >= 0)
613             break;
614 
615         if (!sftpfs_waitsocket (super, res, mcerror))
616         {
617             g_free (tmp_path);
618             return -1;
619         }
620     }
621     while (res == LIBSSH2_ERROR_EAGAIN);
622     g_free (tmp_path);
623 
624     return 0;
625 }
626 
627 /* --------------------------------------------------------------------------------------------- */
628