sftp.c 18.4 KB
Newer Older
ivoire's avatar
ivoire committed
1
2
3
/*****************************************************************************
 * sftp.c: SFTP input module
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2009 VLC authors and VideoLAN
ivoire's avatar
ivoire committed
5
6
7
 * $Id$
 *
 * Authors: Rémi Duraffort <ivoire@videolan.org>
8
 *          Petri Hintukainen <phintuka@gmail.com>
ivoire's avatar
ivoire committed
9
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
10
11
12
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
ivoire's avatar
ivoire committed
13
14
15
16
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
17
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
ivoire's avatar
ivoire committed
19
 *
Jean-Baptiste Kempf's avatar
LGPL    
Jean-Baptiste Kempf committed
20
21
22
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
ivoire's avatar
ivoire committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_plugin.h>

#include <assert.h>

#include <vlc_access.h>
38
#include <vlc_input_item.h>
ivoire's avatar
ivoire committed
39
40
#include <vlc_network.h>
#include <vlc_url.h>
Thomas Guillem's avatar
Thomas Guillem committed
41
#include <vlc_keystore.h>
ivoire's avatar
ivoire committed
42
43
44
45
46
47
48
49
50
51
52
53
54

#include <libssh2.h>
#include <libssh2_sftp.h>


/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open ( vlc_object_t* );
static void Close( vlc_object_t* );

#define PORT_TEXT N_("SFTP port")
#define PORT_LONGTEXT N_("SFTP port number to use on the server")
55
56
57
58
59
60
#define USER_TEXT N_("Username")
#define USER_LONGTEXT N_("Username that will be used for the connection, " \
        "if no username is set in the URL.")
#define PASS_TEXT N_("Password")
#define PASS_LONGTEXT N_("Password that will be used for the connection, " \
        "if no username or password are set in URL.")
ivoire's avatar
ivoire committed
61
62
63
64
65
66
67

vlc_module_begin ()
    set_shortname( "SFTP" )
    set_description( N_("SFTP input") )
    set_capability( "access", 0 )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_ACCESS )
68
    add_integer( "sftp-port", 22, PORT_TEXT, PORT_LONGTEXT, true )
69
70
    add_string( "sftp-user", NULL, USER_TEXT, USER_LONGTEXT, false )
    add_password( "sftp-pwd", NULL, PASS_TEXT, PASS_LONGTEXT, false )
ivoire's avatar
ivoire committed
71
72
73
74
75
76
77
78
    add_shortcut( "sftp" )
    set_callbacks( Open, Close )
vlc_module_end ()


/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
79
static ssize_t  Read( access_t *, void *, size_t );
ivoire's avatar
ivoire committed
80
static int      Seek( access_t *, uint64_t );
ivoire's avatar
ivoire committed
81
82
static int      Control( access_t *, int, va_list );

83
static int DirRead( access_t *, input_item_node_t * );
84
static int DirControl( access_t *, int, va_list );
ivoire's avatar
ivoire committed
85
86
87
88
89
90
91

struct access_sys_t
{
    int i_socket;
    LIBSSH2_SESSION* ssh_session;
    LIBSSH2_SFTP* sftp_session;
    LIBSSH2_SFTP_HANDLE* file;
92
    uint64_t filesize;
93
    char *psz_base_url;
ivoire's avatar
ivoire committed
94
95
};

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
static int AuthKeyAgent( access_t *p_access, const char *psz_username )
{
    access_sys_t* p_sys = p_access->p_sys;
    int i_result = VLC_EGENERIC;
    LIBSSH2_AGENT *p_sshagent = NULL;
    struct libssh2_agent_publickey *p_identity = NULL,
                                   *p_prev_identity = NULL;

    if( !psz_username || !psz_username[0] )
        return i_result;

    p_sshagent = libssh2_agent_init( p_sys->ssh_session );

    if( !p_sshagent )
    {
        msg_Dbg( p_access, "Failed to initialize key agent" );
        goto bailout;
    }
    if( libssh2_agent_connect( p_sshagent ) )
    {
        msg_Dbg( p_access, "Failed to connect key agent" );
        goto bailout;
    }
    if( libssh2_agent_list_identities( p_sshagent ) )
    {
        msg_Dbg( p_access, "Failed to request identities" );
        goto bailout;
    }

    while( libssh2_agent_get_identity( p_sshagent, &p_identity, p_prev_identity ) == 0 )
    {
        msg_Dbg( p_access, "Using key %s", p_identity->comment );
        if( libssh2_agent_userauth( p_sshagent, psz_username, p_identity ) == 0 )
        {
            msg_Info( p_access, "Public key agent authentication succeeded" );
            i_result = VLC_SUCCESS;
            goto bailout;
        }
        msg_Dbg( p_access, "Public key agent authentication failed" );
        p_prev_identity = p_identity;
    }

bailout:
    if( p_sshagent )
    {
        libssh2_agent_disconnect( p_sshagent );
        libssh2_agent_free( p_sshagent );
    }
    return i_result;
}

ivoire's avatar
ivoire committed
147

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
static int AuthPublicKey( access_t *p_access, const char *psz_home, const char *psz_username )
{
    access_sys_t* p_sys = p_access->p_sys;
    int i_result = VLC_EGENERIC;
    char *psz_keyfile1 = NULL;
    char *psz_keyfile2 = NULL;

    if( !psz_username || !psz_username[0] )
        return i_result;

    if( asprintf( &psz_keyfile1, "%s/.ssh/id_rsa.pub", psz_home ) == -1 ||
        asprintf( &psz_keyfile2, "%s/.ssh/id_rsa",     psz_home ) == -1 )
        goto bailout;

    if( libssh2_userauth_publickey_fromfile( p_sys->ssh_session, psz_username, psz_keyfile1, psz_keyfile2, NULL ) )
    {
        msg_Dbg( p_access, "Public key authentication failed" );
        goto bailout;
    }

    msg_Info( p_access, "Public key authentication succeeded" );
    i_result = VLC_SUCCESS;

 bailout:
    free( psz_keyfile1 );
    free( psz_keyfile2 );
    return i_result;
}
ivoire's avatar
ivoire committed
176
177
178
179
180
181
182
183
184
185

/**
 * Connect to the sftp server and ask for a file
 * @param p_this: the vlc_object
 * @return VLC_SUCCESS if everything was fine
 */
static int Open( vlc_object_t* p_this )
{
    access_t*   p_access = (access_t*)p_this;
    access_sys_t* p_sys;
Thomas Guillem's avatar
Thomas Guillem committed
186
187
    vlc_url_t credential_url;
    vlc_credential credential;
188
189
    const char* psz_path;
    char* psz_remote_home = NULL;
190
    char* psz_home = NULL;
ivoire's avatar
ivoire committed
191
192
193
    int i_port;
    int i_ret;
    vlc_url_t url;
194
195
    size_t i_len;
    int i_type;
196
    int i_result = VLC_EGENERIC;
ivoire's avatar
ivoire committed
197

198
    if( !p_access->psz_location )
ivoire's avatar
ivoire committed
199
200
        return VLC_EGENERIC;

201
202
203
    p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
    if( !p_sys ) return VLC_ENOMEM;

Petri Hintukainen's avatar
Petri Hintukainen committed
204
    p_sys->i_socket = -1;
ivoire's avatar
ivoire committed
205

Thomas Guillem's avatar
Thomas Guillem committed
206
207
208
    vlc_UrlParse( &credential_url, p_access->psz_url );
    vlc_credential_init( &credential, &credential_url );

ivoire's avatar
ivoire committed
209
    /* Parse the URL */
210
    vlc_UrlParse( &url, p_access->psz_url );
211
    vlc_uri_decode( url.psz_path );
ivoire's avatar
ivoire committed
212
213
214
215

    /* Check for some parameters */
    if( EMPTY_STR( url.psz_host ) )
    {
216
        msg_Err( p_access, "Unable to extract host from %s", p_access->psz_url );
ivoire's avatar
ivoire committed
217
218
219
220
        goto error;
    }

    if( url.i_port <= 0 )
ivoire's avatar
ivoire committed
221
        i_port = var_InheritInteger( p_access, "sftp-port" );
ivoire's avatar
ivoire committed
222
223
224
225
226
    else
        i_port = url.i_port;


    /* Connect to the server using a regular socket */
227
    p_sys->i_socket = net_ConnectTCP( p_access, url.psz_host, i_port );
228
229
230
231
232
    if( p_sys->i_socket < 0 )
    {
        msg_Err( p_access, "Impossible to open the connection to %s:%i", url.psz_host, i_port );
        goto error;
    }
ivoire's avatar
ivoire committed
233
234

    /* Create the ssh connexion and wait until the server answer */
ivoire's avatar
ivoire committed
235
236
237
    if( ( p_sys->ssh_session = libssh2_session_init() ) == NULL )
        goto error;

ivoire's avatar
ivoire committed
238
239
240
241
242
243
244
245
246
247
    while( ( i_ret = libssh2_session_startup( p_sys->ssh_session,
                                              p_sys->i_socket ) )
           == LIBSSH2_ERROR_EAGAIN );

    if( i_ret != 0 )
    {
        msg_Err( p_access, "Impossible to open the connection to %s:%i", url.psz_host, i_port );
        goto error;
    }

248
    /* Set the socket in non-blocking mode */
ivoire's avatar
ivoire committed
249
    libssh2_session_set_blocking( p_sys->ssh_session, 1 );
250
251
252
253
254
255

    /* List the know hosts */
    LIBSSH2_KNOWNHOSTS *ssh_knownhosts = libssh2_knownhost_init( p_sys->ssh_session );
    if( !ssh_knownhosts )
        goto error;

256
    psz_home = config_GetUserDir( VLC_HOME_DIR );
257
    char *psz_knownhosts_file;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
258
    if( asprintf( &psz_knownhosts_file, "%s/.ssh/known_hosts", psz_home ) != -1 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
259
    {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
260
261
        libssh2_knownhost_readfile( ssh_knownhosts, psz_knownhosts_file,
                LIBSSH2_KNOWNHOST_FILE_OPENSSH );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
262
263
        free( psz_knownhosts_file );
    }
264
265
266

    const char *fingerprint = libssh2_session_hostkey( p_sys->ssh_session, &i_len, &i_type );
    struct libssh2_knownhost *host;
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    int knownhost_fingerprint_algo;

    switch( i_type )
    {
        case LIBSSH2_HOSTKEY_TYPE_RSA:
            knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
            break;

        case LIBSSH2_HOSTKEY_TYPE_DSS:
            knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
            break;

        default:
            msg_Err( p_access, "Host uses unrecognized session-key algorithm" );
Petri Hintukainen's avatar
Petri Hintukainen committed
281
            libssh2_knownhost_free( ssh_knownhosts );
282
283
284
285
            goto error;

    }

286
287
288
    int check = libssh2_knownhost_check( ssh_knownhosts, url.psz_host,
                                         fingerprint, i_len,
                                         LIBSSH2_KNOWNHOST_TYPE_PLAIN |
289
290
                                         LIBSSH2_KNOWNHOST_KEYENC_RAW |
                                         knownhost_fingerprint_algo,
291
292
293
294
                                         &host );

    libssh2_knownhost_free( ssh_knownhosts );

Benjamin Drung's avatar
Benjamin Drung committed
295
    /* Check that it does match or at least that the host is unknown */
296
297
298
299
300
301
302
303
304
305
306
307
    switch(check)
    {
    case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
    case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
        msg_Dbg( p_access, "Unable to check the remote host" );
        break;
    case LIBSSH2_KNOWNHOST_CHECK_MATCH:
        msg_Dbg( p_access, "Succesfuly matched the host" );
        break;
    case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
        msg_Err( p_access, "The host does not match !! The remote key changed !!" );
        goto error;
ivoire's avatar
ivoire committed
308
309
    }

310
311
    char* psz_userauthlist = NULL;
    do
ivoire's avatar
ivoire committed
312
    {
313
314
315
        if (!credential.psz_username || !credential.psz_username[0])
            continue;

316
317
318
319
320
321
322
323
324
325
        psz_userauthlist = libssh2_userauth_list( p_sys->ssh_session, credential.psz_username, strlen( credential.psz_username ) );

        /* TODO: Follow PreferredAuthentications in ssh_config */

        if( strstr( psz_userauthlist, "publickey" ) != NULL &&
            ( AuthKeyAgent( p_access, credential.psz_username ) == VLC_SUCCESS ||
              AuthPublicKey( p_access, psz_home, credential.psz_username ) == VLC_SUCCESS ) )
            break;
        if( strstr( psz_userauthlist, "password" ) != NULL &&
            libssh2_userauth_password( p_sys->ssh_session,
Thomas Guillem's avatar
Thomas Guillem committed
326
327
328
                                       credential.psz_username,
                                       credential.psz_password ) == 0 )
        {
329
            vlc_credential_store( &credential, p_access );
Thomas Guillem's avatar
Thomas Guillem committed
330
331
            break;
        }
332
333

        msg_Warn( p_access, "sftp auth failed for %s", credential.psz_username );
334
335
336
337
    } while( vlc_credential_get( &credential, p_access, "sftp-user", "sftp-pwd",
                                _("SFTP authentication"),
                                _("Please enter a valid login and password for "
                                "the sftp connexion to %s"), url.psz_host ) );
ivoire's avatar
ivoire committed
338
339
340
341
342
343
344
345
346
347

    /* Create the sftp session */
    p_sys->sftp_session = libssh2_sftp_init( p_sys->ssh_session );

    if( !p_sys->sftp_session )
    {
        msg_Err( p_access, "Unable to initialize the SFTP session" );
        goto error;
    }

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
    /* No path, default to user Home */
    if( !url.psz_path )
    {
        const size_t i_size = 1024;
        int i_ret;

        psz_remote_home = malloc( i_size );
        if( !psz_remote_home )
            goto error;

        i_ret = libssh2_sftp_symlink_ex( p_sys->sftp_session, ".", 1,
                                         psz_remote_home, i_size - 1,
                                         LIBSSH2_SFTP_REALPATH );
        if( i_ret <= 0 )
        {
            msg_Err( p_access, "Impossible to get the Home directory" );
            goto error;
        }
        psz_remote_home[i_ret] = '\0';
        psz_path = psz_remote_home;
368
369
370
371
372
373
374
375
376
377
378

        /* store base url for directory read */
        char *base = vlc_path2uri( psz_path, "sftp" );
        if( !base )
            goto error;
        if( -1 == asprintf( &p_sys->psz_base_url, "sftp://%s%s", p_access->psz_location, base + 7 ) )
        {
            free( base );
            goto error;
        }
        free( base );
379
380
381
382
    }
    else
        psz_path = url.psz_path;

ivoire's avatar
ivoire committed
383
    /* Get some information */
ivoire's avatar
ivoire committed
384
    LIBSSH2_SFTP_ATTRIBUTES attributes;
385
    if( libssh2_sftp_stat( p_sys->sftp_session, psz_path, &attributes ) )
ivoire's avatar
ivoire committed
386
    {
387
        msg_Err( p_access, "Impossible to get information about the remote path %s", psz_path );
ivoire's avatar
ivoire committed
388
389
        goto error;
    }
390
391
392
393

    if( !LIBSSH2_SFTP_S_ISDIR( attributes.permissions ))
    {
        /* Open the given file */
394
        p_sys->file = libssh2_sftp_open( p_sys->sftp_session, psz_path, LIBSSH2_FXF_READ, 0 );
395
        p_sys->filesize = attributes.filesize;
396

397
        ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
398
399
400
401
    }
    else
    {
        /* Open the given directory */
402
        p_sys->file = libssh2_sftp_opendir( p_sys->sftp_session, psz_path );
403
404

        p_access->pf_readdir = DirRead;
405
        p_access->pf_control = DirControl;
406
407
408
409
410
411
412
413
414
415
416

        if( !p_sys->psz_base_url )
        {
            if( asprintf( &p_sys->psz_base_url, "sftp://%s", p_access->psz_location ) == -1 )
                goto error;

            /* trim trailing '/' */
            size_t len = strlen( p_sys->psz_base_url );
            if( len > 0 && p_sys->psz_base_url[ len - 1 ] == '/' )
                p_sys->psz_base_url[ len - 1 ] = 0;
        }
417
418
419
420
    }

    if( !p_sys->file )
    {
421
        msg_Err( p_access, "Unable to open the remote path %s", psz_path );
422
423
        goto error;
    }
ivoire's avatar
ivoire committed
424

425
    i_result = VLC_SUCCESS;
ivoire's avatar
ivoire committed
426
427

error:
428
    free( psz_home );
429
    free( psz_remote_home );
ivoire's avatar
ivoire committed
430
    vlc_UrlClean( &url );
Thomas Guillem's avatar
Thomas Guillem committed
431
432
    vlc_credential_clean( &credential );
    vlc_UrlClean( &credential_url );
433
434
435
436
    if( i_result != VLC_SUCCESS ) {
        Close( p_this );
    }
    return i_result;
ivoire's avatar
ivoire committed
437
438
439
440
441
442
443
444
445
}


/* Close: quit the module */
static void Close( vlc_object_t* p_this )
{
    access_t*   p_access = (access_t*)p_this;
    access_sys_t* p_sys = p_access->p_sys;

446
447
448
449
450
451
452
453
    if( p_sys->file )
        libssh2_sftp_close_handle( p_sys->file );
    if( p_sys->sftp_session )
        libssh2_sftp_shutdown( p_sys->sftp_session );
    if( p_sys->ssh_session )
        libssh2_session_free( p_sys->ssh_session );
    if( p_sys->i_socket >= 0 )
        net_Close( p_sys->i_socket );
454

455
    free( p_sys->psz_base_url );
ivoire's avatar
ivoire committed
456
457
458
459
    free( p_sys );
}


460
static ssize_t Read( access_t *p_access, void *buf, size_t len )
ivoire's avatar
ivoire committed
461
{
462
463
    access_sys_t *p_sys = p_access->p_sys;

464
    ssize_t val = libssh2_sftp_read(  p_sys->file, buf, len );
465
    if( val < 0 )
ivoire's avatar
ivoire committed
466
467
    {
        msg_Err( p_access, "read failed" );
468
        return 0;
ivoire's avatar
ivoire committed
469
    }
470
471

    return val;
ivoire's avatar
ivoire committed
472
473
474
}


475
static int Seek( access_t* p_access, uint64_t i_pos )
ivoire's avatar
ivoire committed
476
{
477
478
479
    access_sys_t *sys = p_access->p_sys;

    libssh2_sftp_seek( sys->file, i_pos );
ivoire's avatar
ivoire committed
480
481
482
483
484
485
    return VLC_SUCCESS;
}


static int Control( access_t* p_access, int i_query, va_list args )
{
486
    access_sys_t *sys = p_access->p_sys;
ivoire's avatar
ivoire committed
487
488
489
490
491
    bool*       pb_bool;
    int64_t*    pi_64;

    switch( i_query )
    {
492
    case STREAM_CAN_SEEK:
ivoire's avatar
ivoire committed
493
494
495
496
        pb_bool = (bool*)va_arg( args, bool* );
        *pb_bool = true;
        break;

497
    case STREAM_CAN_FASTSEEK:
ivoire's avatar
ivoire committed
498
499
500
501
        pb_bool = (bool*)va_arg( args, bool* );
        *pb_bool = false;
        break;

502
503
    case STREAM_CAN_PAUSE:
    case STREAM_CAN_CONTROL_PACE:
ivoire's avatar
ivoire committed
504
505
506
507
        pb_bool = (bool*)va_arg( args, bool* );
        *pb_bool = true;
        break;

508
    case STREAM_GET_SIZE:
509
510
        if( p_access->pf_readdir != NULL )
            return VLC_EGENERIC;
511
        *va_arg( args, uint64_t * ) = sys->filesize;
512
513
        break;

514
    case STREAM_GET_PTS_DELAY:
ivoire's avatar
ivoire committed
515
        pi_64 = (int64_t*)va_arg( args, int64_t* );
516
517
        *pi_64 = INT64_C(1000)
               * var_InheritInteger( p_access, "network-caching" );
ivoire's avatar
ivoire committed
518
519
        break;

520
    case STREAM_SET_PAUSE_STATE:
ivoire's avatar
ivoire committed
521
522
523
524
525
526
527
528
529
        break;

    default:
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

530
531
532
533
534

/*****************************************************************************
 * Directory access
 *****************************************************************************/

535
static int DirRead (access_t *p_access, input_item_node_t *p_current_node)
536
537
538
{
    access_sys_t *p_sys = p_access->p_sys;
    LIBSSH2_SFTP_ATTRIBUTES attrs;
539
    int i_ret = VLC_SUCCESS;
540
541
542
543
544
545
546
547
548
549
    int err;
    /* Allocate 1024 bytes for file name. Longer names are skipped.
     * libssh2 does not support seeking in directory streams.
     * Retrying with larger buffer is possible, but would require
     * re-opening the directory stream.
     */
    size_t i_size = 1024;
    char *psz_file = malloc( i_size );

    if( !psz_file )
550
        return VLC_ENOMEM;
551

552
553
554
555
556
    struct access_fsdir fsdir;
    access_fsdir_init( &fsdir, p_access, p_current_node );

    while( i_ret == VLC_SUCCESS
        && 0 != ( err = libssh2_sftp_readdir( p_sys->file, psz_file, i_size, &attrs ) ) )
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
    {
        if( err < 0 )
        {
            if( err == LIBSSH2_ERROR_BUFFER_TOO_SMALL )
            {
                /* seeking back is not possible ... */
                msg_Dbg( p_access, "skipped too long file name" );
                continue;
            }
            if( err == LIBSSH2_ERROR_EAGAIN )
            {
                continue;
            }
            msg_Err( p_access, "directory read failed" );
            break;
        }

        /* Create an input item for the current entry */

        char *psz_full_uri, *psz_uri;

578
        psz_uri = vlc_uri_encode( psz_file );
579
        if( psz_uri == NULL )
580
581
582
583
        {
            i_ret = VLC_ENOMEM;
            break;
        }
584

585
        if( asprintf( &psz_full_uri, "%s/%s", p_sys->psz_base_url, psz_uri ) == -1 )
586
587
        {
            free( psz_uri );
588
589
            i_ret = VLC_ENOMEM;
            break;
590
591
592
593
        }
        free( psz_uri );

        int i_type = LIBSSH2_SFTP_S_ISDIR( attrs.permissions ) ? ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
594
595
        i_ret = access_fsdir_additem( &fsdir, psz_full_uri, psz_file, i_type,
                                      ITEM_NET );
Petri Hintukainen's avatar
Petri Hintukainen committed
596
        free( psz_full_uri );
597
598
    }

599
    access_fsdir_finish( &fsdir, i_ret == VLC_SUCCESS );
600
    free( psz_file );
601
    return i_ret;
602
}
603
604
605
606
607

static int DirControl( access_t *p_access, int i_query, va_list args )
{
    switch( i_query )
    {
608
    case STREAM_IS_DIRECTORY:
609
610
611
612
613
614
615
616
        *va_arg( args, bool * ) = true; /* might loop */
        break;
    default:
        return access_vaDirectoryControlHelper( p_access, i_query, args );
    }

    return VLC_SUCCESS;
}