1 /*
2  * Copyright (C) 2000-2020 the xine project
3  * Copyright (C) 2018 Petri Hintukainen <phintuka@users.sourceforge.net>
4  *
5  * This file is part of xine, a free video player.
6  *
7  * xine is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * xine is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
20  *
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <errno.h>
30 
31 #include <libssh2.h>
32 #include <libssh2_sftp.h>
33 
34 #define LOG_MODULE "input_ssh"
35 #define LOG_VERBOSE
36 /*
37 #define LOG
38 */
39 
40 #include <xine/xine_internal.h>
41 #include <xine/xineutils.h>
42 #include <xine/input_plugin.h>
43 
44 #include "net_buf_ctrl.h"
45 #include "http_helper.h"
46 #include "input_helper.h"
47 
48 #define DEFAULT_SSH_PORT 22
49 
50 typedef struct {
51   input_plugin_t   input_plugin;
52 
53   xine_t          *xine;
54   xine_stream_t   *stream;
55   char            *mrl;         /* mrl without credentials */
56   char            *mrl_private;
57   off_t            curpos;
58   off_t            file_size;
59 
60   nbc_t           *nbc;
61 
62   /* ssh */
63   int                  fd;
64   LIBSSH2_SESSION     *session;
65 
66   /* sftp */
67   LIBSSH2_SFTP        *sftp_session;
68   LIBSSH2_SFTP_HANDLE *sftp_handle;
69 
70   /* scp */
71   LIBSSH2_CHANNEL      *scp_channel;
72   size_t                preview_size;
73   char                  preview[MAX_PREVIEW_SIZE];
74 
75 } ssh_input_plugin_t;
76 
77 /*
78  * helper functions
79  */
80 
_wait_socket(ssh_input_plugin_t * this)81 static int _wait_socket(ssh_input_plugin_t *this)
82 {
83   int flags = 0;
84   int dir;
85 
86   dir = libssh2_session_block_directions(this->session);
87 
88   if (dir & LIBSSH2_SESSION_BLOCK_INBOUND)
89     flags |= XIO_READ_READY;
90   if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
91     flags |= XIO_WRITE_READY;
92 
93   return _x_io_select(this->stream, this->fd, flags, 500);
94 }
95 
_emit_authentication_request(ssh_input_plugin_t * this)96 static void _emit_authentication_request(ssh_input_plugin_t *this)
97 {
98   xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
99           "Authentication required for '%s'\n", this->mrl);
100   if (this->stream)
101     _x_message(this->stream, XINE_MSG_AUTHENTICATION_NEEDED,
102                this->mrl, "Authentication required", NULL);
103 }
104 
_ssh_connect(ssh_input_plugin_t * this,const xine_url_t * url)105 static int _ssh_connect(ssh_input_plugin_t *this,
106                         const xine_url_t *url)
107 {
108   int port = url->port;
109   int rc;
110 
111   /* check parameters */
112 
113   if (!port)
114     port = DEFAULT_SSH_PORT;
115 
116   if (!url->user) {
117     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
118             "No username in mrl '%s'\n", this->mrl);
119     _emit_authentication_request(this);
120     return -1;
121   }
122 
123   /* connect to remote host */
124 
125   this->fd = _x_io_tcp_connect (this->stream, url->host, port);
126   if (this->fd < 0) {
127     return -1;
128   }
129 
130   do {
131     rc = _x_io_tcp_connect_finish(this->stream, this->fd, 1000);
132     if (rc == XIO_READY)
133       break;
134     if (rc != XIO_TIMEOUT)
135       return -1;
136   } while (1);
137 
138   /* init ssh session */
139 
140   this->session = libssh2_session_init();
141   if (!this->session) {
142     return -1;
143   }
144 
145   /* enable non-blocking mode (allow stopping if network is stuck) */
146   libssh2_session_set_blocking(this->session, 0);
147 
148   do {
149     rc = libssh2_session_handshake(this->session, this->fd);
150     if (this->stream && _x_action_pending(this->stream))
151       return -1;
152   } while (rc == LIBSSH2_ERROR_EAGAIN);
153 
154   if (rc) {
155     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
156             "Failed to establish SSH session: %d\n", rc);
157     return -1;
158   }
159 
160   /* authenticate */
161 
162   if (url->password && url->password[0]) {
163 
164     /* password */
165 
166     do {
167       rc = libssh2_userauth_password(this->session, url->user, url->password);
168       if (this->stream && _x_action_pending(this->stream))
169         return -1;
170     } while (rc == LIBSSH2_ERROR_EAGAIN);
171 
172     if (rc) {
173       xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
174               "Authentication by password failed.\n");
175       _emit_authentication_request(this);
176       return -1;
177     }
178   } else {
179 
180     /* public key */
181 
182     const char *home = xine_get_homedir();
183     char *pub  = _x_asprintf("%s/.ssh/id_rsa.pub", home);
184     char *priv = _x_asprintf("%s/.ssh/id_rsa", home);
185 
186     if (pub && priv)
187     do {
188       rc = libssh2_userauth_publickey_fromfile(this->session, url->user,
189                                                pub, priv, url->password);
190       if (this->stream && _x_action_pending(this->stream)) {
191         free(pub);
192         free(priv);
193         return -1;
194       }
195     } while (rc == LIBSSH2_ERROR_EAGAIN);
196 
197     free(pub);
198     free(priv);
199 
200     if (rc) {
201       xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
202               "Authentication by public key failed\n");
203       _emit_authentication_request(this);
204       return -1;
205     }
206   }
207 
208   return 0;
209 }
210 
_sftp_session_init(ssh_input_plugin_t * this)211 static int _sftp_session_init(ssh_input_plugin_t *this)
212 {
213   int rc;
214 
215   do {
216     this->sftp_session = libssh2_sftp_init(this->session);
217 
218     if (!this->sftp_session) {
219       rc = libssh2_session_last_errno(this->session);
220       if (rc != LIBSSH2_ERROR_EAGAIN) {
221         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
222                 "Unable to init SFTP session\n");
223         return -1;
224       }
225       _wait_socket(this);
226       if (this->stream && _x_action_pending(this->stream))
227         return -1;
228     }
229   } while (!this->sftp_session);
230 
231   return 0;
232 }
233 
_scp_channel_init(ssh_input_plugin_t * this,const char * uri)234 static int _scp_channel_init(ssh_input_plugin_t *this, const char *uri)
235 {
236 #if LIBSSH2_VERSION_NUM < 0x010800
237   struct stat sb;
238 #else
239   libssh2_struct_stat sb;
240 #endif
241   int rc;
242 
243   /* Request a file via SCP */
244   do {
245 #if LIBSSH2_VERSION_NUM < 0x010800
246     this->scp_channel = libssh2_scp_recv(this->session, uri, &sb);
247 #else
248     this->scp_channel = libssh2_scp_recv2(this->session, uri, &sb);
249 #endif
250     if (!this->scp_channel) {
251       rc = libssh2_session_last_errno(this->session);
252       if (rc != LIBSSH2_ERROR_EAGAIN) {
253         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
254                 "Unable to init SCP channel for '%s'\n", uri);
255         return -1;
256       }
257       _wait_socket(this);
258       if (_x_action_pending(this->stream))
259         return -1;
260     }
261   } while (!this->scp_channel);
262 
263   this->file_size = sb.st_size;
264 
265   return 0;
266 }
267 
_sftp_open(ssh_input_plugin_t * this,const char * uri)268 static int _sftp_open(ssh_input_plugin_t *this, const char *uri)
269 {
270   int rc;
271 
272   /* Request a file via SFTP */
273   do {
274     this->sftp_handle = libssh2_sftp_open(this->sftp_session, uri, LIBSSH2_FXF_READ, 0);
275     if (!this->sftp_handle) {
276       rc = libssh2_session_last_errno(this->session);
277       if (rc != LIBSSH2_ERROR_EAGAIN) {
278         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
279                 "Unable to open SFTP file '%s'\n", uri);
280         return -1;
281       }
282       _wait_socket(this);
283       if (_x_action_pending(this->stream))
284         return -1;
285     }
286   } while (!this->sftp_handle);
287 
288   return 0;
289 }
290 
291 /*
292  * plugin interface
293  */
294 
_scp_read(input_plugin_t * this_gen,void * buf_gen,off_t len)295 static off_t _scp_read (input_plugin_t *this_gen, void *buf_gen, off_t len)
296 {
297   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
298   uint8_t *buf = buf_gen;
299   off_t got = 0;
300   int rc;
301 
302   /* handle preview chunk */
303   if (this->curpos < (off_t)this->preview_size) {
304     size_t n = this->preview_size - this->curpos;
305     if ((off_t)n > len)
306       n = len;
307     memcpy (buf, this->preview + this->curpos, n);
308     this->curpos += n;
309     got += n;
310   }
311 
312   /* handle actual read */
313   while (got < len) {
314 
315     /* check for EOF */
316     if (this->curpos + got >= this->file_size) {
317       goto out;
318     }
319 
320     while ((rc = libssh2_channel_read(this->scp_channel, buf + got, len - got))
321            == LIBSSH2_ERROR_EAGAIN) {
322       if (libssh2_channel_eof(this->scp_channel)) {
323         goto out;
324       }
325       _wait_socket(this);
326       if (_x_action_pending(this->stream)) {
327         errno = EINTR;
328         if (got)
329           goto out;
330         return -1;
331       }
332     }
333 
334     if (rc <= 0) {
335       if (rc < 0) {
336         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
337                 "SCP read failed: %d\n", rc);
338         if (got)
339           goto out;
340         return -1;
341       }
342       if (libssh2_channel_eof(this->scp_channel)) {
343         goto out;
344       }
345     }
346 
347     got += rc;
348   }
349 
350  out:
351   this->curpos += got;
352   return got;
353 }
354 
_sftp_read(input_plugin_t * this_gen,void * buf_gen,off_t len)355 static off_t _sftp_read (input_plugin_t *this_gen, void *buf_gen, off_t len)
356 {
357   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
358   uint8_t *buf = buf_gen;
359   off_t got = 0;
360   int rc;
361 
362   if (this->curpos + len >= this->file_size) {
363     /* check if file growed */
364     this->file_size = 0;
365     this_gen->get_length(this_gen);
366     if (this->curpos >= this->file_size) {
367       return 0;
368     }
369   }
370 
371   while (got < len) {
372 
373     while ((rc = libssh2_sftp_read(this->sftp_handle, buf + got, len - got))
374            == LIBSSH2_ERROR_EAGAIN) {
375       _wait_socket(this);
376       if (_x_action_pending(this->stream)) {
377         errno = EINTR;
378         if (got)
379           goto out;
380         return -1;
381       }
382     }
383 
384     if (rc <= 0) {
385       if (rc < 0) {
386         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
387                 "SCP read failed: %d\n", rc);
388         if (got)
389           goto out;
390         return -1;
391       }
392       goto out;
393     }
394     got += rc;
395   }
396 
397  out:
398   this->curpos += got;
399   return got;
400 }
401 
_scp_get_length(input_plugin_t * this_gen)402 static off_t _scp_get_length (input_plugin_t *this_gen)
403 {
404   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
405 
406   return this->file_size;
407 }
408 
_sftp_get_length(input_plugin_t * this_gen)409 static off_t _sftp_get_length (input_plugin_t *this_gen)
410 {
411   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
412   LIBSSH2_SFTP_ATTRIBUTES attrs;
413   int rc;
414 
415   if (this->file_size)
416     return this->file_size;
417 
418   memset(&attrs, 0, sizeof(attrs));
419 
420   while ((rc = libssh2_sftp_fstat_ex(this->sftp_handle, &attrs, 0)) ==
421          LIBSSH2_ERROR_EAGAIN) {
422     if (_x_action_pending(this->stream))
423       return 0;
424   }
425   if (rc) {
426     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
427             "SFTP stat failed: %d\n", rc);
428     return 0;
429   }
430 
431   this->file_size = attrs.filesize;
432   return this->file_size;
433 }
434 
_get_current_pos(input_plugin_t * this_gen)435 static off_t _get_current_pos (input_plugin_t *this_gen)
436 {
437   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
438 
439   return this->curpos;
440 }
441 
_scp_seek(input_plugin_t * this_gen,off_t offset,int origin)442 static off_t _scp_seek (input_plugin_t *this_gen, off_t offset, int origin)
443 {
444   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
445 
446   return _x_input_seek_preview(this_gen, offset, origin,
447                                &this->curpos, this->file_size, this->preview_size);
448 }
449 
_sftp_seek(input_plugin_t * this_gen,off_t offset,int origin)450 static off_t _sftp_seek (input_plugin_t *this_gen, off_t offset, int origin)
451 {
452   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
453 
454   switch (origin) {
455   case SEEK_CUR:
456     offset = this->curpos + offset;
457     break;
458   case SEEK_END:
459     offset = this->file_size + offset;
460     break;
461   case SEEK_SET:
462     break;
463   default:
464     return -1;
465   }
466 
467   if (offset < 0) {
468     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
469             "SFTP seek failed: position %" PRId64 " outside of file.\n", (int64_t)offset);
470     return -1;
471   }
472 
473   this->curpos = offset;
474   libssh2_sftp_seek64(this->sftp_handle, offset);
475 
476   return this->curpos;
477 }
478 
479 
_get_mrl(input_plugin_t * this_gen)480 static const char *_get_mrl (input_plugin_t *this_gen)
481 {
482   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
483 
484   return this->mrl;
485 }
486 
_get_optional_data(input_plugin_t * this_gen,void * data,int data_type)487 static int _get_optional_data (input_plugin_t *this_gen, void *data, int data_type)
488 {
489   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
490 
491   switch (data_type) {
492     case INPUT_OPTIONAL_DATA_PREVIEW:
493       if (this->preview_size > 0) {
494         memcpy (data, this->preview, this->preview_size);
495         return this->preview_size;
496       }
497   }
498 
499   return INPUT_OPTIONAL_UNSUPPORTED;
500 }
501 
_dispose(input_plugin_t * this_gen)502 static void _dispose (input_plugin_t *this_gen)
503 {
504   ssh_input_plugin_t *this = (ssh_input_plugin_t *) this_gen;
505 
506   if (this->nbc) {
507     nbc_close (this->nbc);
508     this->nbc = NULL;
509   }
510 
511   if (this->sftp_handle) {
512     while (libssh2_sftp_close(this->sftp_handle) == LIBSSH2_ERROR_EAGAIN);
513     this->sftp_handle = NULL;
514   }
515 
516   if (this->scp_channel) {
517     while (libssh2_channel_free(this->scp_channel) == LIBSSH2_ERROR_EAGAIN);
518     this->scp_channel = NULL;
519   }
520 
521   if (this->sftp_session) {
522     while (libssh2_sftp_shutdown(this->sftp_session) == LIBSSH2_ERROR_EAGAIN);
523     this->sftp_session = NULL;
524   }
525 
526   if (this->session) {
527     while (libssh2_session_disconnect(this->session, "close") == LIBSSH2_ERROR_EAGAIN);
528     while (libssh2_session_free(this->session) == LIBSSH2_ERROR_EAGAIN);
529     this->session = NULL;
530   }
531 
532   if (this->fd != -1) {
533     _x_io_tcp_close(this->stream, this->fd);
534     this->fd = -1;
535   }
536 
537   _x_freep (&this->mrl);
538   _x_freep_wipe_string(&this->mrl_private);
539 
540   free (this_gen);
541 
542   libssh2_exit();
543 }
544 
_scp_fill_preview(ssh_input_plugin_t * this)545 static int _scp_fill_preview(ssh_input_plugin_t *this)
546 {
547   off_t got;
548 
549   got = _scp_read (&this->input_plugin, this->preview, sizeof(this->preview));
550   if (got < 1 || got > (off_t)sizeof(this->preview)) {
551     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
552             "Unable to read preview data\n");
553     return -1;
554   }
555 
556   this->preview_size = got;
557   return 0;
558 }
559 
_open_plugin(input_plugin_t * this_gen)560 static int _open_plugin (input_plugin_t *this_gen)
561 {
562   ssh_input_plugin_t *this = (ssh_input_plugin_t *)this_gen;
563   xine_url_t url;
564   int   result = 0, rc;
565   int   is_scp;
566 
567   this->curpos = 0;
568 
569   /* parse mrl */
570   rc = _x_url_parse2(this->mrl_private, &url);
571   _x_freep_wipe_string(&this->mrl_private);
572   if (!rc) {
573     _x_message(this->stream, XINE_MSG_GENERAL_WARNING, "malformed url", NULL);
574     return 0;
575   }
576 
577   /* set up ssh connection */
578   result = _ssh_connect(this, &url);
579   if (result < 0)
580     goto out;
581 
582   is_scp = !strncasecmp (url.proto, "scp", 3);
583   if (is_scp) {
584 
585     /* Request a file via SCP */
586 
587     if (_scp_channel_init(this, url.uri) < 0)
588       goto out;
589 
590     if (_scp_fill_preview(this) < 0)
591       goto out;
592 
593   } else {
594 
595     /* Request a file via SFTP */
596 
597     if (_sftp_session_init(this) < 0)
598       goto out;
599 
600     if (_sftp_open(this, url.uri) < 0)
601       goto out;
602 
603     _sftp_get_length(this_gen);
604   }
605 
606   /* succeed */
607   result = 1;
608 
609  out:
610 
611   _x_url_cleanup(&url);
612   return result;
613 }
614 
615 
616 /*
617  * plugin class
618  */
619 
_get_instance(input_class_t * cls_gen,xine_stream_t * stream,const char * mrl)620 static input_plugin_t *_get_instance (input_class_t *cls_gen, xine_stream_t *stream, const char *mrl)
621 {
622   ssh_input_plugin_t *this;
623   int sftp, scp, rc;
624 
625   /* check mrl type */
626   sftp = !strncasecmp (mrl, "sftp://", 7);
627   scp  = !strncasecmp (mrl, "scp://", 6);
628   if (!sftp && !scp)
629     return NULL;
630 
631   /* initialize libssh2 */
632   rc = libssh2_init(0);
633   if (rc) {
634     xprintf(stream ? stream->xine : NULL, XINE_VERBOSITY_LOG, LOG_MODULE ": "
635             "libssh2 initialization failed (%d)\n", rc);
636     return NULL;
637   }
638 
639   /* initialize plugin */
640 
641   this = calloc(1, sizeof(*this));
642   if (!this)
643     return NULL;
644 
645   this->mrl_private = strdup(mrl);
646   this->mrl         = _x_mrl_remove_auth(mrl);
647 
648   if (!this->mrl || !this->mrl_private) {
649     _dispose(&this->input_plugin);
650     return NULL;
651   }
652 
653   this->stream = stream;
654   this->fd     = -1;
655   this->xine   = stream ? stream->xine : NULL;
656 
657   if (stream) {
658     /* not needed for directory browsing */
659     this->nbc = nbc_init (stream);
660   }
661 
662   this->input_plugin.open              = _open_plugin;
663   this->input_plugin.read_block        = _x_input_default_read_block;
664   this->input_plugin.get_blocksize     = _x_input_default_get_blocksize;
665   this->input_plugin.get_optional_data = _get_optional_data;
666   this->input_plugin.get_current_pos   = _get_current_pos;
667   this->input_plugin.get_mrl           = _get_mrl;
668   this->input_plugin.dispose           = _dispose;
669   if (scp) {
670     this->input_plugin.get_capabilities  = _x_input_get_capabilities_preview;
671     this->input_plugin.read              = _scp_read;
672     this->input_plugin.seek              = _scp_seek;
673     this->input_plugin.get_length        = _scp_get_length;
674   } else {
675     this->input_plugin.get_capabilities  = _x_input_get_capabilities_seekable;
676     this->input_plugin.read              = _sftp_read;
677     this->input_plugin.seek              = _sftp_seek;
678     this->input_plugin.get_length        = _sftp_get_length;
679   }
680   this->input_plugin.input_class = cls_gen;
681 
682   return &this->input_plugin;
683 }
684 
685 /*
686  * SCP class
687  */
688 
scp_init_class(xine_t * xine,const void * data)689 static void *scp_init_class(xine_t *xine, const void *data)
690 {
691   static const input_class_t input_scp_class = {
692     .get_instance      = _get_instance,
693     .description       = N_("SCP input plugin"),
694     .identifier        = "SCP",
695     .dispose           = NULL,
696   };
697 
698   (void)xine;
699   (void)data;
700 
701   return (void *)&input_scp_class;
702 }
703 
704 /*
705  * SFTP class
706  */
707 
708 typedef struct {
709 
710   input_class_t     input_class;
711   xine_t           *xine;
712 
713   /* browser */
714   xine_mrl_t        **mrls;
715 
716 } sftp_input_class_t;
717 
_open_input(sftp_input_class_t * this,xine_url_t * url,const char * mrl)718 static ssh_input_plugin_t *_open_input(sftp_input_class_t *this,
719                                        xine_url_t *url, const char *mrl)
720 {
721   ssh_input_plugin_t *input;
722 
723   input = (ssh_input_plugin_t *)this->input_class.get_instance(&this->input_class, NULL, mrl);
724   if (!input)
725     return NULL;
726 
727   input->xine = this->xine;
728 
729   if (_ssh_connect(input, url))
730     goto fail;
731   if (_sftp_session_init(input))
732     goto fail;
733 
734   libssh2_session_set_blocking(input->session, 1);
735 
736   return input;
737 
738  fail:
739   input->input_plugin.dispose(&input->input_plugin);
740   return NULL;
741 }
742 
_read_dir(sftp_input_class_t * this,ssh_input_plugin_t * input,const char * mrl,const char * uri,int * nFiles)743 static int _read_dir(sftp_input_class_t *this,
744                      ssh_input_plugin_t *input,
745                      const char *mrl, const char *uri, int *nFiles)
746 {
747   LIBSSH2_SFTP_ATTRIBUTES attr;
748   LIBSSH2_SFTP_HANDLE *dir;
749   xine_mrl_t **mrls = NULL;
750   size_t mrls_size = 0;
751   size_t n = 0;
752   char file[1024];
753   int show_hidden_files;
754   int rc;
755 
756   rc = libssh2_sftp_stat(input->sftp_session, uri, &attr);
757   if (rc) {
758     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
759             "remote stat failed for '%s': %d\n", uri, rc);
760     return -1;
761   }
762 
763   if (!LIBSSH2_SFTP_S_ISDIR(attr.permissions)) {
764     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
765             "'%s' is not a directory\n", uri);
766 
767     this->mrls = _x_input_alloc_mrls(1);
768     if (this->mrls) {
769       this->mrls[0]->type   = mrl_net | mrl_file | mrl_file_normal;
770       this->mrls[0]->mrl    = strdup(mrl);
771       *nFiles = 1;
772     }
773     return 0;
774   }
775 
776   dir = libssh2_sftp_opendir(input->sftp_session, uri);
777   if (!dir) {
778     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
779             "error opening directory '%s': %d\n", uri, rc);
780     return -1;
781   }
782 
783   show_hidden_files = _x_input_get_show_hidden_files(this->xine->config);
784 
785   /* add link to back */
786 
787   mrls_size += 64;
788   mrls = _x_input_alloc_mrls(mrls_size);
789   if (!mrls)
790     goto fail;
791 
792   mrls[n]->type   = mrl_net | mrl_file | mrl_file_directory;
793   mrls[n]->origin = strdup(mrl);
794   mrls[n]->mrl    = _x_asprintf("%s/..", mrl);
795   n++;
796 
797   /* read directory */
798 
799   while ( 0 != (rc = libssh2_sftp_readdir(dir, file, sizeof(file), &attr))) {
800 
801     if (rc < 0 ) {
802       if (rc == LIBSSH2_ERROR_BUFFER_TOO_SMALL) {
803         xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
804                 "ignoring too long file name");
805         continue;
806       }
807       if (rc == LIBSSH2_ERROR_EAGAIN)
808         continue;
809       xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
810               "directory '%s' read failed: %d", uri, rc);
811       break;
812     }
813 
814     if (!show_hidden_files && file[0] == '.')
815       continue;
816     if (!strcmp(file, ".") || !strcmp(file, ".."))
817       continue;
818 
819     if (n >= mrls_size) {
820       mrls_size += 64;
821       if (!_x_input_realloc_mrls(&mrls, mrls_size))
822         break;
823     }
824 
825     int type = LIBSSH2_SFTP_S_ISDIR( attr.permissions ) ? mrl_file_directory : mrl_file_normal;
826 
827     mrls[n]->type   = type | mrl_net | mrl_file;
828     mrls[n]->origin = strdup(mrl);
829     mrls[n]->mrl    = _x_asprintf("%s/%s", mrl, file);
830     mrls[n]->size   = attr.filesize;
831     n++;
832   }
833 
834  fail:
835 
836   if (n > 2)
837     _x_input_sort_mrls(mrls + 1, n - 1);
838 
839   if (dir)
840     libssh2_sftp_close(dir);
841 
842   *nFiles = n;
843   this->mrls = mrls;
844 
845   return 0;
846 }
847 
_get_dir(input_class_t * this_gen,const char * filename,int * nFiles)848 static xine_mrl_t **_get_dir (input_class_t *this_gen, const char *filename, int *nFiles)
849 {
850   sftp_input_class_t *this = (sftp_input_class_t *) this_gen;
851   ssh_input_plugin_t *input = NULL;
852   xine_url_t url;
853 
854   _x_input_free_mrls(&this->mrls);
855   *nFiles = 0;
856 
857   if (!filename || !strcmp(filename, "sftp:/") || !strcmp(filename, "sftp://")) {
858     this->mrls = _x_input_get_default_server_mrls(this->xine->config, "sftp://", nFiles);
859     if (!this->mrls)
860       xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
861               "missing sftp mrl\n");
862     return this->mrls;
863   }
864 
865   if (!_x_url_parse2(filename, &url)) {
866     xprintf(this->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": "
867             "malformed url '%s'", filename);
868     return NULL;
869   }
870 
871   input = _open_input(this, &url, filename);
872   if (!input)
873     goto out;
874 
875   _read_dir(this, input, filename, url.uri, nFiles);
876 
877  out:
878   _x_url_cleanup(&url);
879   if (input) {
880     input->input_plugin.dispose(&input->input_plugin);
881   }
882 
883   return this->mrls;
884 }
885 
_dispose_class_sftp(input_class_t * this_gen)886 static void _dispose_class_sftp(input_class_t *this_gen)
887 {
888   sftp_input_class_t *this = (sftp_input_class_t *) this_gen;
889 
890   _x_input_free_mrls(&this->mrls);
891   free(this_gen);
892 }
893 
sftp_init_class(xine_t * xine,const void * data)894 static void *sftp_init_class(xine_t *xine, const void *data)
895 {
896   sftp_input_class_t *this;
897 
898   (void)data;
899 
900   this = calloc(1, sizeof(*this));
901   if (!this)
902     return NULL;
903 
904   this->input_class.get_instance      = _get_instance;
905   this->input_class.description       = N_("SFTP input plugin");
906   this->input_class.identifier        = "SFTP";
907   this->input_class.get_dir           = _get_dir;
908   this->input_class.dispose           = _dispose_class_sftp;
909 
910   this->xine = xine;
911 
912   _x_input_register_show_hidden_files(xine->config);
913   _x_input_register_default_servers(xine->config);
914 
915   return this;
916 }
917 
918 /*
919  * exported plugin catalog entry
920  */
921 
922 const input_info_t input_info_sftp = {
923   .priority = 100,
924 };
925 
926 const input_info_t input_info_scp = {
927   .priority = 100,
928 };
929 
930 const plugin_info_t xine_plugin_info[] EXPORTED = {
931   /* type, API, "name", version, special_info, init_function */
932   { PLUGIN_INPUT, 18, "SFTP", XINE_VERSION_CODE, &input_info_sftp, sftp_init_class },
933   { PLUGIN_INPUT, 18, "SCP",  XINE_VERSION_CODE, &input_info_scp,  scp_init_class },
934   { PLUGIN_NONE, 0, NULL, 0, NULL, NULL }
935 };
936