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