ftp.c 35.1 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
 
gbazin committed
2
 * ftp.c: FTP input module
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2001-2006 VLC authors and VideoLAN
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
5
 * Copyright © 2006 Rémi Denis-Courmont
6
 * $Id$
7
 *
8
 * Authors: Laurent Aimar <fenrir@via.ecp.fr> - original code
9
 *          Rémi Denis-Courmont <rem # videolan.org> - EPSV support
10
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
11 12 13
 * 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
14 15 16 17
 * (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
18 19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
21 22 23
 * 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.
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <assert.h>
34
#include <stdint.h>
35
#include <errno.h>
36

37 38
#include <vlc_common.h>
#include <vlc_plugin.h>
zorglub's avatar
zorglub committed
39
#include <vlc_access.h>
40
#include <vlc_dialog.h>
Thomas Guillem's avatar
Thomas Guillem committed
41
#include <vlc_input_item.h>
zorglub's avatar
zorglub committed
42
#include <vlc_network.h>
ivoire's avatar
ivoire committed
43
#include <vlc_url.h>
44
#include <vlc_tls.h>
zorglub's avatar
zorglub committed
45
#include <vlc_sout.h>
46
#include <vlc_charset.h>
47
#include <vlc_interrupt.h>
Thomas Guillem's avatar
Thomas Guillem committed
48
#include <vlc_keystore.h>
49

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
50 51 52 53
#ifndef IPPORT_FTP
# define IPPORT_FTP 21u
#endif

54 55 56 57
#ifndef IPPORT_FTPS
# define IPPORT_FTPS 990u
#endif

58
/*****************************************************************************
59
 * Module descriptor
60
 *****************************************************************************/
61 62
static int   InOpen ( vlc_object_t * );
static void  InClose( vlc_object_t * );
63
#ifdef ENABLE_SOUT
64 65
static int  OutOpen ( vlc_object_t * );
static void OutClose( vlc_object_t * );
66
#endif
67

68 69 70 71 72 73
#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.")
gbazin's avatar
 
gbazin committed
74
#define ACCOUNT_TEXT N_("FTP account")
zorglub's avatar
zorglub committed
75
#define ACCOUNT_LONGTEXT N_("Account that will be " \
gbazin's avatar
 
gbazin committed
76
    "used for the connection.")
77

Thomas Guillem's avatar
Thomas Guillem committed
78 79 80 81
#define LOGIN_DIALOG_TITLE _("FTP authentication")
#define LOGIN_DIALOG_TEXT _("Please enter a valid login and password for " \
        "the ftp connexion to %s")

82 83 84 85 86 87
vlc_module_begin ()
    set_shortname( "FTP" )
    set_description( N_("FTP input") )
    set_capability( "access", 0 )
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_ACCESS )
88 89
    add_string( "ftp-user", NULL, USER_TEXT, USER_LONGTEXT, false )
    add_string( "ftp-pwd", NULL, PASS_TEXT, PASS_LONGTEXT, false )
90
    add_string( "ftp-account", "anonymous", ACCOUNT_TEXT,
ivoire's avatar
ivoire committed
91
                ACCOUNT_LONGTEXT, false )
92
    add_shortcut( "ftp", "ftps", "ftpes" )
93
    set_callbacks( InOpen, InClose )
94

95
#ifdef ENABLE_SOUT
96
    add_submodule ()
ivoire's avatar
ivoire committed
97 98 99 100 101
        set_shortname( "FTP" )
        set_description( N_("FTP upload output") )
        set_capability( "sout access", 0 )
        set_category( CAT_SOUT )
        set_subcategory( SUBCAT_SOUT_ACO )
102
        add_shortcut( "ftp", "ftps", "ftpes" )
ivoire's avatar
ivoire committed
103
        set_callbacks( OutOpen, OutClose )
104
#endif
105
vlc_module_end ()
106

107 108 109
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
110 111 112

typedef struct access_sys_t access_sys_t;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
113 114 115 116
static ssize_t Read( stream_t *, void *, size_t );
static int Seek( stream_t *, uint64_t );
static int Control( stream_t *, int, va_list );
static int DirRead( stream_t *, input_item_node_t * );
117 118 119 120
#ifdef ENABLE_SOUT
static int OutSeek( sout_access_out_t *, off_t );
static ssize_t Write( sout_access_out_t *, block_t * );
#endif
121

122
static int LoginUserPwd( vlc_object_t *, access_sys_t *,
Thomas Guillem's avatar
Thomas Guillem committed
123
                         const char *, const char *, bool * );
124 125 126 127 128 129
static void FeaturesCheck( void *, const char * );

typedef struct ftp_features_t
{
    bool b_unicode;
    bool b_authtls;
130
    bool b_mlst;
131 132 133 134 135 136 137 138 139
} ftp_features_t;

enum tls_mode_e
{
    NONE = 0,
    IMPLICIT,/* ftps */
    EXPLICIT /* ftpes */
};

140
struct access_sys_t
141
{
142
    vlc_url_t  url;
143

144 145 146
    ftp_features_t   features;
    vlc_tls_creds_t *p_creds;
    enum tls_mode_e  tlsmode;
147 148
    vlc_tls_t *cmd;
    vlc_tls_t *data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
149

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
150
    char       sz_epsv_ip[NI_MAXNUMERICHOST];
151
    bool       out;
152
    uint64_t   offset;
153
    uint64_t   size;
154
};
155 156
#define GET_OUT_SYS( p_this ) \
    ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))
157

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
static int ftp_SendCommand( vlc_object_t *obj, access_sys_t *sys,
                            const char *fmt, ... )
{
    size_t fmtlen = strlen( fmt );
    char fmtbuf[fmtlen + 3];

    memcpy( fmtbuf, fmt, fmtlen );
    memcpy( fmtbuf + fmtlen, "\r\n", 3 );

    va_list args;
    char *cmd;
    int val;

    va_start( args, fmt );
    val = vasprintf( &cmd, fmtbuf, args );
    va_end( args );
    if( unlikely(val == -1) )
        return -1;

177 178 179 180 181
    if( strncmp( cmd, "PASS ", 5 ) && strncmp( cmd, "ACCT ", 5 ) )
        msg_Dbg( obj, "sending request: \"%.*s\" (%d bytes)", val-2, cmd, val );
    else
        msg_Dbg( obj, "sending request: \"%.*s XXXX\" (XX bytes)", 4, cmd );

182
    if( vlc_tls_Write( sys->cmd, cmd, val ) != val )
183 184 185 186 187 188 189 190 191 192
    {
        msg_Err( obj, "request failure" );
        val = -1;
    }
    else
        val = 0;
    free( cmd );
    return val;
}

193 194
static char *ftp_GetLine( vlc_object_t *obj, access_sys_t *sys )
{
195
    char *resp = vlc_tls_GetLine( sys->cmd );
196 197 198 199 200
    if( resp == NULL )
        msg_Err( obj, "response failure" );
    return resp;
}

201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
/* TODO support this s**t :
 RFC 959 allows the client to send certain TELNET strings at any moment,
 even in the middle of a request:

 * \377\377.
 * \377\376x where x is one byte.
 * \377\375x where x is one byte. The server is obliged to send \377\374x
 *                                immediately after reading x.
 * \377\374x where x is one byte.
 * \377\373x where x is one byte. The server is obliged to send \377\376x
 *                                immediately after reading x.
 * \377x for any other byte x.

 These strings are not part of the requests, except in the case \377\377,
 where the request contains one \377. */
216 217 218
static int ftp_RecvReply( vlc_object_t *obj, access_sys_t *sys,
                          char **restrict strp,
                          void (*cb)(void *, const char *), void *opaque )
219
{
220
    char *resp = ftp_GetLine( obj, sys );
221
    if( resp == NULL )
222
        return -1;
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

    char *end;
    unsigned code = strtoul( resp, &end, 10 );
    if( (end - resp) != 3 || (*end != '-' && *end != ' ') )
    {
        msg_Err( obj, "malformatted response" );
        goto error;
    }
    msg_Dbg( obj, "received response: \"%s\"", resp );

    if( *end == '-' ) /* Multi-line response */
    {
        bool done;

        *end = ' ';
        do
        {
240
            char *line = ftp_GetLine( obj, sys );
241 242 243 244
            if( line == NULL )
                goto error;

            done = !strncmp( resp, line, 4 );
245 246
            if( !done )
                cb( opaque, line );
247 248 249 250 251 252 253 254 255
            free( line );
        }
        while( !done );
    }

    if( strp != NULL )
        *strp = resp;
    else
        free( resp );
256
    return code;
257 258 259 260 261
error:
    free( resp );
    return -1;
}

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
static int ftp_RecvAnswer( vlc_object_t *obj, access_sys_t *sys,
                           int *restrict codep, char **restrict strp,
                           void (*cb)(void *, const char *), void *opaque )
{
    char *str;
    int val = ftp_RecvReply( obj, sys, &str, cb, opaque );
    if( (val / 100) == 1 )
    {   /* There can be zero or one preliminary reply per command */
        free( str );
        val = ftp_RecvReply( obj, sys, &str, cb, opaque );
    }

    if( val >= 0 )
    {
        if( codep != NULL )
            *codep = val;
        if( strp != NULL )
            *strp = str;
        else
            free( str );
        val /= 100;
    }
    else
    {
        if( codep != NULL )
            *codep = 500;
        if( strp != NULL )
            *strp = NULL;
    }
    return val;
}

294 295 296 297 298 299 300 301 302 303 304
static void DummyLine( void *data, const char *str )
{
    (void) data; (void) str;
}

static int ftp_RecvCommand( vlc_object_t *obj, access_sys_t *sys,
                            int *restrict codep, char **restrict strp )
{
    return ftp_RecvAnswer( obj, sys, codep, strp, DummyLine, NULL );
}

305 306 307 308 309 310 311 312
static int ftp_RecvCommandInit( vlc_object_t *obj, access_sys_t *sys )
{
    int val = ftp_RecvReply( obj, sys, NULL, DummyLine, NULL );
    if( val >= 0 )
        val /= 100;
    return val;
}

Thomas Guillem's avatar
Thomas Guillem committed
313
static int ftp_StartStream( vlc_object_t *, access_sys_t *, uint64_t, bool );
314
static int ftp_StopStream ( vlc_object_t *, access_sys_t * );
315

316 317
static int readTLSMode( vlc_object_t *obj, access_sys_t *p_sys,
                        const char * psz_access )
318 319 320 321 322 323 324
{
    if ( !strncmp( psz_access, "ftps", 4 ) )
        p_sys->tlsmode = IMPLICIT;
    else
    if ( !strncmp( psz_access, "ftpes", 5 ) )
        p_sys->tlsmode = EXPLICIT;
    else
325 326
    {
        p_sys->p_creds = NULL;
327
        p_sys->tlsmode = NONE;
328 329 330 331 332
        return 0;
    }

    p_sys->p_creds = vlc_tls_ClientCreate( obj );
    return (p_sys->p_creds != NULL) ? 0 : -1;
333 334
}

335
static int createCmdTLS( vlc_object_t *p_access, access_sys_t *p_sys,
336 337 338
                         const char *psz_session_name )
{
    /* TLS/SSL handshake */
339 340 341 342 343 344
    vlc_tls_t *secure = vlc_tls_ClientSessionCreate( p_sys->p_creds,
                                                     p_sys->cmd,
                                                     p_sys->url.psz_host,
                                                     psz_session_name,
                                                     NULL, NULL );
    if( secure == NULL )
345 346 347 348
    {
        msg_Err( p_access, "cannot establish FTP/TLS session on command channel" );
        return -1;
    }
349
    p_sys->cmd = secure;
350 351 352
    return 0;
}

353
static void clearCmd( access_sys_t *p_sys )
354
{
355 356 357 358 359
    if( p_sys->cmd != NULL )
    {
        vlc_tls_Close( p_sys->cmd );
        p_sys->cmd = NULL;
    }
360 361
}

Thomas Guillem's avatar
Thomas Guillem committed
362
static int Login( vlc_object_t *p_access, access_sys_t *p_sys, const char *path )
363
{
364
    int i_answer;
365

366
    /* *** Open a TCP connection with server *** */
367 368 369
    p_sys->cmd = vlc_tls_SocketOpenTCP( p_access, p_sys->url.psz_host,
                                        p_sys->url.i_port );
    if( p_sys->cmd == NULL )
370
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
371
        msg_Err( p_access, "connection failed" );
Thomas Guillem's avatar
Thomas Guillem committed
372 373
        vlc_dialog_display_error( p_access, _("Network interaction failed"), "%s",
            _("VLC could not connect with the given server.") );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
374
        goto error;
375
    }
Laurent Aimar's avatar
Laurent Aimar committed
376

377 378
    if ( p_sys->tlsmode == IMPLICIT ) /* FTPS Mode */
    {
379
        if ( createCmdTLS( p_access, p_sys, "ftps") < 0 )
380 381 382
            goto error;
    }

383
    while( ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) == 1 );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
384

385 386
    if( i_answer / 100 != 2 )
    {
Laurent Aimar's avatar
Laurent Aimar committed
387
        msg_Err( p_access, "connection rejected" );
Thomas Guillem's avatar
Thomas Guillem committed
388 389
        vlc_dialog_display_error( p_access, _("Network interaction failed"), "%s",
            _("VLC's connection to the given server was rejected.") );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
390
        goto error;
391 392
    }

Laurent Aimar's avatar
Laurent Aimar committed
393
    msg_Dbg( p_access, "connection accepted (%d)", i_answer );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
394

395 396 397 398 399 400
    /* Features check first */
    if( ftp_SendCommand( p_access, p_sys, "FEAT" ) < 0
     || ftp_RecvAnswer( p_access, p_sys, NULL, NULL,
                        FeaturesCheck, &p_sys->features ) < 0 )
    {
         msg_Err( p_access, "cannot get server features" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
401
         goto error;
402 403 404 405 406 407 408 409
    }

    /* Create TLS Session */
    if( p_sys->tlsmode == EXPLICIT )
    {
        if ( ! p_sys->features.b_authtls )
        {
            msg_Err( p_access, "Server does not support TLS" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
410
            goto error;
411 412 413 414 415 416 417 418
        }

        if( ftp_SendCommand( p_access, p_sys, "AUTH TLS" ) < 0
         || ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0
         || i_answer != 234 )
        {
             msg_Err( p_access, "cannot switch to TLS: server replied with code %d",
                      i_answer );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
419
             goto error;
420 421
        }

422
        if( createCmdTLS( p_access, p_sys, "ftpes") < 0 )
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
        {
            goto error;
        }
    }

    if( p_sys->tlsmode != NONE )
    {
        if( ftp_SendCommand( p_access, p_sys, "PBSZ 0" ) < 0 ||
            ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
            i_answer != 200 )
        {
            msg_Err( p_access, "Can't truncate Protection buffer size for TLS" );
            goto error;
        }

        if( ftp_SendCommand( p_access, p_sys, "PROT P" ) < 0 ||
            ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
            i_answer != 200 )
        {
            msg_Err( p_access, "Can't set Data channel protection" );
            goto error;
        }
    }

Thomas Guillem's avatar
Thomas Guillem committed
447 448
    vlc_url_t url;
    vlc_credential credential;
Thomas Guillem's avatar
Thomas Guillem committed
449
    if( vlc_UrlParseFixup( &url, path ) != 0 )
450 451 452 453
    {
        vlc_UrlClean( &url );
        goto error;
    }
Thomas Guillem's avatar
Thomas Guillem committed
454 455 456
    vlc_credential_init( &credential, &url );
    bool b_logged = false;

457 458 459 460 461
    /* First: try credentials from url / option */
    vlc_credential_get( &credential, p_access, "ftp-user", "ftp-pwd",
                        NULL, NULL );
    do
    {
462 463 464 465 466 467 468 469 470
        const char *psz_username = credential.psz_username;

        if( psz_username == NULL ) /* use anonymous by default */
            psz_username = "anonymous";

        if( LoginUserPwd( p_access, p_sys, psz_username,
                          credential.psz_password, &b_logged ) != 0
         || b_logged )
            break;
471
    }
Thomas Guillem's avatar
Thomas Guillem committed
472 473
    while( vlc_credential_get( &credential, p_access, "ftp-user", "ftp-pwd",
                               LOGIN_DIALOG_TITLE, LOGIN_DIALOG_TEXT,
474 475
                               url.psz_host ) );

Thomas Guillem's avatar
Thomas Guillem committed
476
    if( b_logged )
477
    {
478
        vlc_credential_store( &credential, p_access );
Thomas Guillem's avatar
Thomas Guillem committed
479 480
        vlc_credential_clean( &credential );
        vlc_UrlClean( &url );
481 482
        return 0;
    }
Thomas Guillem's avatar
Thomas Guillem committed
483 484
    vlc_credential_clean( &credential );
    vlc_UrlClean( &url );
485
error:
486
    clearCmd( p_sys );
487 488 489 490
    return -1;
}

static int LoginUserPwd( vlc_object_t *p_access, access_sys_t *p_sys,
Thomas Guillem's avatar
Thomas Guillem committed
491 492
                         const char *psz_user, const char *psz_pwd,
                         bool *p_logged )
493 494 495 496 497 498 499
{
    int i_answer;

    /* Send credentials over channel */
    if( ftp_SendCommand( p_access, p_sys, "USER %s", psz_user ) < 0 ||
        ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
        return -1;
500

501 502 503
    switch( i_answer / 100 )
    {
        case 2:
504 505 506 507 508
            /* X.509 auth successful after AUTH TLS / RFC 2228 sec. 4 */
            if ( i_answer == 232 )
                msg_Dbg( p_access, "user accepted and authenticated" );
            else
                msg_Dbg( p_access, "user accepted" );
509 510
            break;
        case 3:
Laurent Aimar's avatar
Laurent Aimar committed
511
            msg_Dbg( p_access, "password needed" );
512

513
            if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz_pwd ) < 0 ||
514
                ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
515
                return -1;
516

517 518 519
            switch( i_answer / 100 )
            {
                case 2:
Laurent Aimar's avatar
Laurent Aimar committed
520
                    msg_Dbg( p_access, "password accepted" );
521 522
                    break;
                case 3:
523 524
                {
                    char *psz;
Laurent Aimar's avatar
Laurent Aimar committed
525
                    msg_Dbg( p_access, "account needed" );
ivoire's avatar
ivoire committed
526
                    psz = var_InheritString( p_access, "ftp-account" );
527
                    if( ftp_SendCommand( p_access, p_sys, "ACCT %s",
528
                                         psz ) < 0 ||
529
                        ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
530
                    {
531
                        free( psz );
532
                        return -1;
533
                    }
534
                    free( psz );
535

536 537
                    if( i_answer / 100 != 2 )
                    {
Laurent Aimar's avatar
Laurent Aimar committed
538
                        msg_Err( p_access, "account rejected" );
Thomas Guillem's avatar
Thomas Guillem committed
539 540 541
                        vlc_dialog_display_error( p_access,
                          _("Network interaction failed"),
                          "%s", _("Your account was rejected.") );
542
                        return -1;
543
                    }
Laurent Aimar's avatar
Laurent Aimar committed
544
                    msg_Dbg( p_access, "account accepted" );
545
                    break;
546
                }
547

548
                default:
Thomas Guillem's avatar
Thomas Guillem committed
549 550 551
                    msg_Warn( p_access, "password rejected" );
                    *p_logged = false;
                    return 0;
552 553 554
            }
            break;
        default:
Thomas Guillem's avatar
Thomas Guillem committed
555 556 557
            msg_Warn( p_access, "user rejected" );
            *p_logged = false;
            return 0;
558 559
    }

Thomas Guillem's avatar
Thomas Guillem committed
560
    *p_logged = true;
561 562 563
    return 0;
}

564 565
static void FeaturesCheck( void *opaque, const char *feature )
{
566
    ftp_features_t *features = opaque;
567 568

    if( strcasestr( feature, "UTF8" ) != NULL )
569 570 571 572
        features->b_unicode = true;
    else
    if( strcasestr( feature, "AUTH TLS" ) != NULL )
        features->b_authtls = true;
573 574 575

    if( strcasestr( feature, "MLST" ) != NULL )
        features->b_mlst = true;
576 577 578 579 580 581 582 583 584 585 586
}

static const char *IsASCII( const char *str )
{
    int8_t c;
    for( const char *p = str; (c = *p) != '\0'; p++ )
        if( c < 0 )
            return NULL;
    return str;
}

Thomas Guillem's avatar
Thomas Guillem committed
587
static int Connect( vlc_object_t *p_access, access_sys_t *p_sys, const char *path )
588
{
Thomas Guillem's avatar
Thomas Guillem committed
589
    if( Login( p_access, p_sys, path ) < 0 )
590
        return -1;
591

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
592
    /* Extended passive mode */
593
    if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
594 595
    {
        msg_Err( p_access, "cannot request extended passive mode" );
596
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
597 598
    }

599
    if( ftp_RecvCommand( p_access, p_sys, NULL, NULL ) == 2 )
600
    {
601 602
        int fd = vlc_tls_GetFD(p_sys->cmd);
        if( net_GetPeerAddress( fd, p_sys->sz_epsv_ip, NULL ) )
603
            goto error;
604
    }
605 606 607 608 609 610 611
    else
    {
        /* If ESPV ALL fails, we fallback to PASV.
         * We have to restart the connection in case there is a NAT that
         * understands EPSV ALL in the way, and hence won't allow PASV on
         * the initial connection.
         */
612
        msg_Info( p_access, "FTP Extended passive mode disabled" );
613
        clearCmd( p_sys );
614

Thomas Guillem's avatar
Thomas Guillem committed
615
        if( Login( p_access, p_sys, path ) )
616 617 618
            goto error;
    }

619 620
    if( p_sys->url.psz_path &&
        (p_sys->features.b_unicode ? IsUTF8 : IsASCII)(p_sys->url.psz_path) == NULL )
621 622 623
    {
        msg_Err( p_access, "unsupported path: \"%s\"", p_sys->url.psz_path );
        goto error;
624
    }
625

626 627
    /* check binary mode support */
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
628
        ftp_RecvCommand( p_access, p_sys, NULL, NULL ) != 2 )
629
    {
hartman's avatar
hartman committed
630
        msg_Err( p_access, "cannot set binary transfer mode" );
631
        goto error;
632 633
    }

634
    return 0;
635

636
error:
637
    clearCmd( p_sys );
638
    return -1;
639 640 641
}


642
static int parseURL( vlc_url_t *url, const char *path, enum tls_mode_e mode )
643 644
{
    if( path == NULL )
645
        return VLC_EGENERIC;
646 647 648 649 650

    /* *** Parse URL and get server addr/port and path *** */
    while( *path == '/' )
        path++;

Thomas Guillem's avatar
Thomas Guillem committed
651
    vlc_UrlParseFixup( url, path );
652 653

    if( url->psz_host == NULL || *url->psz_host == '\0' )
654
        return VLC_EGENERIC;
655 656

    if( url->i_port <= 0 )
657 658 659 660 661 662
    {
        if( mode == IMPLICIT )
            url->i_port = IPPORT_FTPS;
        else
            url->i_port = IPPORT_FTP; /* default port */
    }
663

664 665
    if( url->psz_path == NULL )
        return VLC_SUCCESS;
666
    /* FTP URLs are relative to user's default directory (RFC1738 §3.2)
667
    For absolute path use ftp://foo.bar//usr/local/etc/filename */
668
    /* FIXME: we should issue a series of CWD, one per slash */
669 670 671 672 673
    if( url->psz_path )
    {
        assert( url->psz_path[0] == '/' );
        url->psz_path++;
    }
674 675 676 677 678 679 680 681

    char *type = strstr( url->psz_path, ";type=" );
    if( type )
    {
        *type = '\0';
        if( strchr( "iI", type[6] ) == NULL )
            return VLC_EGENERIC; /* ASCII and directory not supported */
    }
682
    vlc_uri_decode( url->psz_path );
683
    return VLC_SUCCESS;
684 685 686 687 688 689 690 691
}


/****************************************************************************
 * Open: connect to ftp server and ask for file
 ****************************************************************************/
static int InOpen( vlc_object_t *p_this )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
692
    stream_t     *p_access = (stream_t*)p_this;
693 694
    access_sys_t *p_sys;
    char         *psz_arg;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
695
    bool          b_directory;
696 697

    /* Init p_access */
698
    p_sys = p_access->p_sys = (access_sys_t*)vlc_obj_calloc( p_this, 1, sizeof( access_sys_t ) );
Thomas Guillem's avatar
Thomas Guillem committed
699 700
    if( !p_sys )
        return VLC_ENOMEM;
701
    p_sys->data = NULL;
702
    p_sys->out = false;
703
    p_sys->offset = 0;
704
    p_sys->size = UINT64_MAX;
705

706
    if( readTLSMode( p_this, p_sys, p_access->psz_name ) )
707
        goto exit_error;
708

709
    if( parseURL( &p_sys->url, p_access->psz_url, p_sys->tlsmode ) )
710 711
        goto exit_error;

Thomas Guillem's avatar
Thomas Guillem committed
712
    if( Connect( p_this, p_sys, p_access->psz_url ) )
713 714
        goto exit_error;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
    do {
        /* get size */
        if( p_sys->url.psz_path == NULL || !*p_sys->url.psz_path )
        {
            b_directory = true;
            break;
        }

        if( ftp_SendCommand( p_this, p_sys, "SIZE %s",
                             p_sys->url.psz_path ) < 0 )
            goto error;

        int val = ftp_RecvCommand( p_this, p_sys, NULL, &psz_arg );
        if( val == 2 )
        {
            b_directory = false;
            p_sys->size = atoll( &psz_arg[4] );
            free( psz_arg );
            msg_Dbg( p_access, "file size: %"PRIu64, p_sys->size );
            break;
        }
        if( val >= 0 )
            free( psz_arg );

        if( ftp_SendCommand( p_this, p_sys, "CWD %s",
                             p_sys->url.psz_path ) < 0 )
            goto error;

        if( ftp_RecvCommand( p_this, p_sys, NULL, NULL ) == 2 )
        {
            b_directory = true;
            break;
        }

749
        msg_Err( p_this, "file or directory does not exist" );
750
        goto error;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
751
    } while (0);
Thomas Guillem's avatar
Thomas Guillem committed
752 753

    if( b_directory )
754
    {
Thomas Guillem's avatar
Thomas Guillem committed
755
        p_access->pf_readdir = DirRead;
756
        p_access->pf_control = access_vaDirectoryControlHelper;
757
    } else
Thomas Guillem's avatar
Thomas Guillem committed
758
        ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek ); \
759

760
    /* Start the 'stream' */
Thomas Guillem's avatar
Thomas Guillem committed
761
    if( ftp_StartStream( p_this, p_sys, 0, b_directory ) < 0 )
762
    {
763
        msg_Err( p_this, "cannot retrieve file" );
764
        goto error;
765 766
    }

767
    return VLC_SUCCESS;
768

769
error:
770
    clearCmd( p_sys );
771

772
exit_error:
773
    vlc_UrlClean( &p_sys->url );
774
    vlc_tls_Delete( p_sys->p_creds );
775
    return VLC_EGENERIC;
776 777
}

778
#ifdef ENABLE_SOUT
779 780 781 782 783
static int OutOpen( vlc_object_t *p_this )
{
    sout_access_out_t *p_access = (sout_access_out_t *)p_this;
    access_sys_t      *p_sys;

784
    p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
ivoire's avatar
ivoire committed
785
    if( !p_sys )
786 787 788
        return VLC_ENOMEM;

    /* Init p_access */
789
    p_sys->data = NULL;
790
    p_sys->out = true;
791 792 793

    if( readTLSMode( p_this, p_sys, p_access->psz_access ) )
        goto exit_error;
794

795
    if( parseURL( &p_sys->url, p_access->psz_path, p_sys->tlsmode ) )
796
        goto exit_error;
797 798 799 800 801
    if( p_sys->url.psz_path == NULL )
    {
        msg_Err( p_this, "no filename specified" );
        goto exit_error;
    }
802

Thomas Guillem's avatar
Thomas Guillem committed
803
    if( Connect( p_this, p_sys, p_access->psz_path ) )
804 805 806
        goto exit_error;

    /* Start the 'stream' */
Thomas Guillem's avatar
Thomas Guillem committed
807
    if( ftp_StartStream( p_this, p_sys, 0, false ) < 0 )
808 809
    {
        msg_Err( p_access, "cannot store file" );
810
        clearCmd( p_sys );
811 812 813 814 815
        goto exit_error;
    }

    p_access->pf_seek = OutSeek;
    p_access->pf_write = Write;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
816
    p_access->p_sys = (void *)p_sys;
817 818 819 820

    return VLC_SUCCESS;

exit_error:
Rémi Denis-Courmont's avatar
Oups  
Rémi Denis-Courmont committed
821
    vlc_UrlClean( &p_sys->url );
822
    vlc_tls_Delete( p_sys->p_creds );
823 824
    return VLC_EGENERIC;
}
825
#endif
826

827 828 829
/*****************************************************************************
 * Close: free unused data structures
 *****************************************************************************/
830
static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
831
{
Laurent Aimar's avatar
Laurent Aimar committed
832
    msg_Dbg( p_access, "stopping stream" );
833
    ftp_StopStream( p_access, p_sys );
834

835
    if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
836
    {
Laurent Aimar's avatar
Laurent Aimar committed
837
        msg_Warn( p_access, "cannot quit" );
838 839 840
    }
    else
    {
841
        ftp_RecvCommand( p_access, p_sys, NULL, NULL );
842
    }
843

844
    clearCmd( p_sys );
845 846

    /* free memory */
847
    vlc_UrlClean( &p_sys->url );
848
    vlc_tls_Delete( p_sys->p_creds );
849 850
}

851 852
static void InClose( vlc_object_t *p_this )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
853
    Close( p_this, ((stream_t *)p_this)->p_sys);
854 855
}

856
#ifdef ENABLE_SOUT
857 858 859 860
static void OutClose( vlc_object_t *p_this )
{
    Close( p_this, GET_OUT_SYS(p_this));
}
861
#endif
862 863


864 865 866
/*****************************************************************************
 * Seek: try to go at the right place
 *****************************************************************************/
867 868
static int SeekCommon( vlc_object_t *p_access, access_sys_t *p_sys,
                       uint64_t i_pos )
869
{
870
    msg_Dbg( p_access, "seeking to %"PRIu64, i_pos );
871