ftp.c 23.4 KB
Newer Older
1
/*****************************************************************************
gbazin's avatar
 
gbazin committed
2
 * ftp.c: FTP input module
3
 *****************************************************************************
4
 * Copyright (C) 2001-2006 the VideoLAN team
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 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (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
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
29 30 31
#include <vlc/vlc.h>

#include <stdio.h>
32
#include <assert.h>
33

zorglub's avatar
zorglub committed
34 35
#include <vlc_access.h>
#include <vlc_interface.h>
36

zorglub's avatar
zorglub committed
37
#include <vlc_network.h>
38
#include "vlc_url.h"
zorglub's avatar
zorglub committed
39
#include <vlc_sout.h>
40

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
41 42 43 44
#ifndef IPPORT_FTP
# define IPPORT_FTP 21u
#endif

45
/*****************************************************************************
46
 * Module descriptor
47
 *****************************************************************************/
48 49 50 51
static int   InOpen ( vlc_object_t * );
static void  InClose( vlc_object_t * );
static int  OutOpen ( vlc_object_t * );
static void OutClose( vlc_object_t * );
52

Christophe Massiot's avatar
Christophe Massiot committed
53
#define CACHING_TEXT N_("Caching value in ms")
54
#define CACHING_LONGTEXT N_( \
55
    "Caching value for FTP streams. This " \
zorglub's avatar
zorglub committed
56
    "value should be set in milliseconds." )
gbazin's avatar
 
gbazin committed
57
#define USER_TEXT N_("FTP user name")
zorglub's avatar
zorglub committed
58
#define USER_LONGTEXT N_("User name that will " \
gbazin's avatar
 
gbazin committed
59 60
    "be used for the connection.")
#define PASS_TEXT N_("FTP password")
zorglub's avatar
zorglub committed
61
#define PASS_LONGTEXT N_("Password that will be " \
gbazin's avatar
 
gbazin committed
62 63
    "used for the connection.")
#define ACCOUNT_TEXT N_("FTP account")
zorglub's avatar
zorglub committed
64
#define ACCOUNT_LONGTEXT N_("Account that will be " \
gbazin's avatar
 
gbazin committed
65
    "used for the connection.")
66 67

vlc_module_begin();
zorglub's avatar
zorglub committed
68
    set_shortname( "FTP" );
gbazin's avatar
 
gbazin committed
69
    set_description( _("FTP input") );
Laurent Aimar's avatar
Laurent Aimar committed
70
    set_capability( "access2", 0 );
zorglub's avatar
zorglub committed
71 72
    set_category( CAT_INPUT );
    set_subcategory( SUBCAT_INPUT_ACCESS );
gbazin's avatar
 
gbazin committed
73 74 75 76
    add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
                 CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
    add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
                VLC_FALSE );
77
    add_string( "ftp-pwd", "anonymous@example.com", NULL, PASS_TEXT,
gbazin's avatar
 
gbazin committed
78 79 80
                PASS_LONGTEXT, VLC_FALSE );
    add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
                ACCOUNT_LONGTEXT, VLC_FALSE );
81
    add_shortcut( "ftp" );
82 83 84 85 86 87 88 89 90
    set_callbacks( InOpen, InClose );

    add_submodule();
    set_shortname( "FTP" );
    set_description( _("FTP upload output") );
    set_capability( "sout access", 0 );
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_ACO );
    set_callbacks( OutOpen, OutClose );
91 92
vlc_module_end();

93 94 95
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
96
static int Read( access_t *, uint8_t *, int );
97
static int Write( sout_access_out_t *, block_t * );
Laurent Aimar's avatar
Laurent Aimar committed
98
static int Seek( access_t *, int64_t );
99
static int OutSeek( sout_access_out_t *, int64_t );
Laurent Aimar's avatar
Laurent Aimar committed
100
static int Control( access_t *, int, va_list );
101

102
struct access_sys_t
103
{
104
    vlc_url_t  url;
105

106 107
    int        fd_cmd;
    int        fd_data;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
108

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
109
    char       sz_epsv_ip[NI_MAXNUMERICHOST];
110
};
111 112
#define GET_OUT_SYS( p_this ) \
    ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))
113

114 115 116 117
static int ftp_SendCommand( vlc_object_t *, access_sys_t *, const char *, ... );
static int ftp_ReadCommand( vlc_object_t *, access_sys_t *, int *, char ** );
static int ftp_StartStream( vlc_object_t *, access_sys_t *, int64_t );
static int ftp_StopStream ( vlc_object_t *, access_sys_t * );
118

119
static int Login( vlc_object_t *p_access, access_sys_t *p_sys )
120
{
121
    int i_answer;
122
    char *psz;
123

124
    /* *** Open a TCP connection with server *** */
125 126
    int fd = p_sys->fd_cmd = net_ConnectTCP( p_access, p_sys->url.psz_host,
                                             p_sys->url.i_port );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
127
    if( fd == -1 )
128
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
        msg_Err( p_access, "connection failed" );
130 131
        intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"), 
                        _("VLC could not connect with the given server.") );
132
        return -1;
133
    }
Laurent Aimar's avatar
Laurent Aimar committed
134

135
    while( ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) == 1 );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
136

137 138
    if( i_answer / 100 != 2 )
    {
Laurent Aimar's avatar
Laurent Aimar committed
139
        msg_Err( p_access, "connection rejected" );
140 141
        intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"), 
                        _("VLC's connection to the given server was rejected.") );
142
        return -1;
143 144
    }

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

147 148 149 150
    if( p_sys->url.psz_username && *p_sys->url.psz_username )
        psz = strdup( p_sys->url.psz_username );
    else
        psz = var_CreateGetString( p_access, "ftp-user" );
151

152 153
    if( ftp_SendCommand( p_access, p_sys, "USER %s", psz ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
154
    {
155
        free( psz );
156
        return -1;
157
    }
158
    free( psz );
159

160 161 162
    switch( i_answer / 100 )
    {
        case 2:
Laurent Aimar's avatar
Laurent Aimar committed
163
            msg_Dbg( p_access, "user accepted" );
164 165
            break;
        case 3:
Laurent Aimar's avatar
Laurent Aimar committed
166
            msg_Dbg( p_access, "password needed" );
167 168
            if( p_sys->url.psz_password && *p_sys->url.psz_password )
                psz = strdup( p_sys->url.psz_password );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
169
            else
170 171
                psz = var_CreateGetString( p_access, "ftp-pwd" );

172 173
            if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz ) < 0 ||
                ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
174
            {
175
                free( psz );
176
                return -1;
177
            }
178
            free( psz );
179

180 181 182
            switch( i_answer / 100 )
            {
                case 2:
Laurent Aimar's avatar
Laurent Aimar committed
183
                    msg_Dbg( p_access, "password accepted" );
184 185
                    break;
                case 3:
Laurent Aimar's avatar
Laurent Aimar committed
186
                    msg_Dbg( p_access, "account needed" );
187
                    psz = var_CreateGetString( p_access, "ftp-account" );
188
                    if( ftp_SendCommand( p_access, p_sys, "ACCT %s",
189
                                         psz ) < 0 ||
190
                        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
191
                    {
192
                        free( psz );
193
                        return -1;
194
                    }
195
                    free( psz );
196

197 198
                    if( i_answer / 100 != 2 )
                    {
Laurent Aimar's avatar
Laurent Aimar committed
199
                        msg_Err( p_access, "account rejected" );
200 201 202
                        intf_UserFatal( p_access, VLC_FALSE, 
                                        _("Network interaction failed"), 
                                        _("Your account was rejected.") );
203
                        return -1;
204
                    }
Laurent Aimar's avatar
Laurent Aimar committed
205
                    msg_Dbg( p_access, "account accepted" );
206
                    break;
207

208
                default:
Laurent Aimar's avatar
Laurent Aimar committed
209
                    msg_Err( p_access, "password rejected" );
210 211 212
                    intf_UserFatal( p_access, VLC_FALSE, 
                                    _("Network interaction failed"), 
                                    _("Your password was rejected.") );
213
                    return -1;
214 215 216
            }
            break;
        default:
Laurent Aimar's avatar
Laurent Aimar committed
217
            msg_Err( p_access, "user rejected" );
218 219
            intf_UserFatal( p_access, VLC_FALSE, 
                        _("Network interaction failed"), 
220
                        _("Your connection attempt to the server was rejected.") );
221
            return -1;
222 223
    }

224 225 226
    return 0;
}

227
static int Connect( vlc_object_t *p_access, access_sys_t *p_sys )
228
{
229 230
    if( Login( p_access, p_sys ) < 0 )
        return -1;
231

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
232
    /* Extended passive mode */
233
    if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
234 235
    {
        msg_Err( p_access, "cannot request extended passive mode" );
236
        net_Close( p_sys->fd_cmd );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
237 238 239
        return -1;
    }

240
    if( ftp_ReadCommand( p_access, p_sys, NULL, NULL ) == 2 )
241
    {
242
        if( net_GetPeerAddress( p_sys->fd_cmd, p_sys->sz_epsv_ip, NULL ) )
243 244 245 246
        {
            net_Close( p_sys->fd_cmd );
            return -1;
        }
247
    }
248 249 250 251 252 253 254
    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.
         */
255
        msg_Info( p_access, "FTP Extended passive mode disabled" );
256
        net_Close( p_sys->fd_cmd );
257

Rémi Denis-Courmont's avatar
Oups  
Rémi Denis-Courmont committed
258
        if( Login( p_access, p_sys ) )
259 260 261 262
        {
            net_Close( p_sys->fd_cmd );
            return -1;
        }
263
    }
264

265 266 267
    /* check binary mode support */
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, NULL, NULL ) != 2 )
268
    {
hartman's avatar
hartman committed
269
        msg_Err( p_access, "cannot set binary transfer mode" );
270 271
        net_Close( p_sys->fd_cmd );
        return -1;
272 273
    }

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    return 0;
}


static int parseURL( vlc_url_t *url, const char *path )
{
    if( path == NULL )
        return -1;

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

    vlc_UrlParse( url, path, 0 );

    if( url->psz_host == NULL || *url->psz_host == '\0' )
        return -1;

    if( url->i_port <= 0 )
        url->i_port = IPPORT_FTP; /* default port */

    /* FTP URLs are relative to user's default directory (RFC1738)
    For absolute path use ftp://foo.bar//usr/local/etc/filename */

    if( *url->psz_path == '/' )
        url->psz_path++;

    return 0;
}


/****************************************************************************
 * Open: connect to ftp server and ask for file
 ****************************************************************************/
static int InOpen( vlc_object_t *p_this )
{
    access_t     *p_access = (access_t*)p_this;
    access_sys_t *p_sys;
    char         *psz_arg;

    /* Init p_access */
    STANDARD_READ_ACCESS_INIT
    p_sys->fd_data = -1;

    if( parseURL( &p_sys->url, p_access->psz_path ) )
        goto exit_error;

    if( Connect( p_this, p_sys ) )
        goto exit_error;

324
    /* get size */
325 326
    if( ftp_SendCommand( p_this, p_sys, "SIZE %s", p_sys->url.psz_path ) < 0 ||
        ftp_ReadCommand( p_this, p_sys, NULL, &psz_arg ) != 2 )
327
    {
Laurent Aimar's avatar
Laurent Aimar committed
328
        msg_Err( p_access, "cannot get file size" );
329
        net_Close( p_sys->fd_cmd );
330 331
        goto exit_error;
    }
332
    p_access->info.i_size = atoll( &psz_arg[4] );
333
    free( psz_arg );
334
    msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
335

336
    /* Start the 'stream' */
337
    if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
338
    {
Laurent Aimar's avatar
Laurent Aimar committed
339
        msg_Err( p_access, "cannot retrieve file" );
340
        net_Close( p_sys->fd_cmd );
341 342 343 344
        goto exit_error;
    }

    /* Update default_pts to a suitable value for ftp access */
Laurent Aimar's avatar
Laurent Aimar committed
345
    var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
346

347
    return VLC_SUCCESS;
348 349

exit_error:
350 351 352
    vlc_UrlClean( &p_sys->url );
    free( p_sys );
    return VLC_EGENERIC;
353 354
}

355 356 357 358 359 360 361 362 363 364 365 366 367
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;

    p_sys = malloc( sizeof( *p_sys ) );
    if( p_sys == NULL )
        return VLC_ENOMEM;
    memset( p_sys, 0, sizeof( *p_sys ) );

    /* Init p_access */
    p_sys->fd_data = -1;

368
    if( parseURL( &p_sys->url, p_access->psz_path ) )
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
        goto exit_error;

    if( Connect( p_this, p_sys ) )
        goto exit_error;

    /* Start the 'stream' */
    if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
    {
        msg_Err( p_access, "cannot store file" );
        net_Close( p_sys->fd_cmd );
        goto exit_error;
    }

    p_access->pf_seek = OutSeek;
    p_access->pf_write = Write;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
384
    p_access->p_sys = (void *)p_sys;
385 386 387 388

    return VLC_SUCCESS;

exit_error:
Rémi Denis-Courmont's avatar
Oups  
Rémi Denis-Courmont committed
389
    vlc_UrlClean( &p_sys->url );
390 391 392 393
    free( p_sys );
    return VLC_EGENERIC;
}

394 395 396
/*****************************************************************************
 * Close: free unused data structures
 *****************************************************************************/
397
static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
398
{
Laurent Aimar's avatar
Laurent Aimar committed
399
    msg_Dbg( p_access, "stopping stream" );
400
    ftp_StopStream( p_access, p_sys );
401

402
    if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
403
    {
Laurent Aimar's avatar
Laurent Aimar committed
404
        msg_Warn( p_access, "cannot quit" );
405 406 407
    }
    else
    {
408
        ftp_ReadCommand( p_access, p_sys, NULL, NULL );
409
    }
410
    net_Close( p_sys->fd_cmd );
411 412

    /* free memory */
413 414
    vlc_UrlClean( &p_sys->url );
    free( p_sys );
415 416
}

417 418 419 420 421 422 423 424 425 426 427
static void InClose( vlc_object_t *p_this )
{
    Close( p_this, ((access_t *)p_this)->p_sys);
}

static void OutClose( vlc_object_t *p_this )
{
    Close( p_this, GET_OUT_SYS(p_this));
}


428 429 430
/*****************************************************************************
 * Seek: try to go at the right place
 *****************************************************************************/
431
static int _Seek( vlc_object_t *p_access, access_sys_t *p_sys, int64_t i_pos )
432 433
{
    if( i_pos < 0 )
Laurent Aimar's avatar
Laurent Aimar committed
434
        return VLC_EGENERIC;
435

Laurent Aimar's avatar
Laurent Aimar committed
436
    msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
437

438 439
    ftp_StopStream( (vlc_object_t *)p_access, p_sys );
    if( ftp_StartStream( (vlc_object_t *)p_access, p_sys, i_pos ) < 0 )
Laurent Aimar's avatar
Laurent Aimar committed
440
        return VLC_EGENERIC;
441 442 443 444 445 446 447 448 449

    return VLC_SUCCESS;
}

static int Seek( access_t *p_access, int64_t i_pos )
{
    int val = _Seek( (vlc_object_t *)p_access, p_access->p_sys, i_pos );
    if( val )
        return val;
450

451 452
    p_access->info.b_eof = VLC_FALSE;
    p_access->info.i_pos = i_pos;
453

Laurent Aimar's avatar
Laurent Aimar committed
454
    return VLC_SUCCESS;
455 456
}

457 458 459 460 461
static int OutSeek( sout_access_out_t *p_access, off_t i_pos )
{
    return _Seek( (vlc_object_t *)p_access, GET_OUT_SYS( p_access ), i_pos);
}

462 463 464
/*****************************************************************************
 * Read:
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
465
static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
466
{
Laurent Aimar's avatar
Laurent Aimar committed
467 468 469
    access_sys_t *p_sys = p_access->p_sys;
    int i_read;

470 471 472
    assert( p_sys->fd_data != -1 );
    assert( p_access->i_object_type == VLC_OBJECT_ACCESS );

473
    if( p_access->info.b_eof )
Laurent Aimar's avatar
Laurent Aimar committed
474 475
        return 0;

476 477
    i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
                       VLC_FALSE );
Laurent Aimar's avatar
Laurent Aimar committed
478
    if( i_read == 0 )
479
        p_access->info.b_eof = VLC_TRUE;
Laurent Aimar's avatar
Laurent Aimar committed
480
    else if( i_read > 0 )
481
        p_access->info.i_pos += i_read;
Laurent Aimar's avatar
Laurent Aimar committed
482 483 484 485

    return i_read;
}

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
/*****************************************************************************
 * Write:
 *****************************************************************************/
static int Write( sout_access_out_t *p_access, block_t *p_buffer )
{
    access_sys_t *p_sys = GET_OUT_SYS(p_access);
    size_t i_write = 0;

    assert( p_sys->fd_data != -1 );

    while( p_buffer != NULL )
    {
        block_t *p_next = p_buffer->p_next;;

        i_write += net_Write( p_access, p_sys->fd_data, NULL,
                              p_buffer->p_buffer, p_buffer->i_buffer );
        block_Release( p_buffer );

        p_buffer = p_next;
    }

    return i_write;
}

Laurent Aimar's avatar
Laurent Aimar committed
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
/*****************************************************************************
 * Control:
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
{
    vlc_bool_t   *pb_bool;
    int          *pi_int;
    int64_t      *pi_64;
    vlc_value_t  val;

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;
            break;
        case ACCESS_CAN_FASTSEEK:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_FALSE;
            break;
        case ACCESS_CAN_PAUSE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;    /* FIXME */
            break;
        case ACCESS_CAN_CONTROL_PACE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_TRUE;    /* FIXME */
            break;

        /* */
        case ACCESS_GET_MTU:
            pi_int = (int*)va_arg( args, int * );
            *pi_int = 0;
            break;

        case ACCESS_GET_PTS_DELAY:
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            var_Get( p_access, "ftp-caching", &val );
549
            *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
Laurent Aimar's avatar
Laurent Aimar committed
550
            break;
551

Laurent Aimar's avatar
Laurent Aimar committed
552 553 554 555 556
        /* */
        case ACCESS_SET_PAUSE_STATE:
            /* Nothing to do */
            break;

557 558 559
        case ACCESS_GET_TITLE_INFO:
        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
560
        case ACCESS_SET_PRIVATE_ID_STATE:
561 562
            return VLC_EGENERIC;

Laurent Aimar's avatar
Laurent Aimar committed
563
        default:
564
            msg_Warn( p_access, "unimplemented query in control" );
Laurent Aimar's avatar
Laurent Aimar committed
565 566 567 568
            return VLC_EGENERIC;

    }
    return VLC_SUCCESS;
569 570
}

571 572 573
/*****************************************************************************
 * ftp_*:
 *****************************************************************************/
574 575
static int ftp_SendCommand( vlc_object_t *p_access, access_sys_t *p_sys,
                            const char *psz_fmt, ... )
576
{
577 578
    va_list      args;
    char         *psz_cmd;
579 580

    va_start( args, psz_fmt );
581 582
    vasprintf( &psz_cmd, psz_fmt, args );
    va_end( args );
583

Laurent Aimar's avatar
Laurent Aimar committed
584
    msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
585 586
    if( net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL, "%s\r\n",
                    psz_cmd ) < 0 )
587
    {
Laurent Aimar's avatar
Laurent Aimar committed
588
        msg_Err( p_access, "failed to send command" );
589
        return VLC_EGENERIC;
590
    }
591
    return VLC_SUCCESS;
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
}

/* 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. */
609
static int ftp_ReadCommand( vlc_object_t *p_access, access_sys_t *p_sys,
gbazin's avatar
 
gbazin committed
610
                            int *pi_answer, char **ppsz_answer )
611
{
612 613 614
    char         *psz_line;
    int          i_answer;

615
    psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
616 617
    if( psz_line == NULL || strlen( psz_line ) < 3 )
    {
Laurent Aimar's avatar
Laurent Aimar committed
618
        msg_Err( p_access, "cannot get answer" );
619 620 621 622
        if( psz_line ) free( psz_line );
        if( pi_answer ) *pi_answer    = 500;
        if( ppsz_answer ) *ppsz_answer  = NULL;
        return -1;
623
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
624
    msg_Dbg( p_access, "answer=%s", psz_line );
625

626 627 628 629 630 631 632 633 634
    if( psz_line[3] == '-' )    /* Multiple response */
    {
        char end[4];

        memcpy( end, psz_line, 3 );
        end[3] = ' ';

        for( ;; )
        {
635
            char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
636 637 638 639 640 641 642 643 644 645 646 647 648

            if( psz_tmp == NULL )   /* Error */
                break;

            if( !strncmp( psz_tmp, end, 4 ) )
            {
                free( psz_tmp );
                break;
            }
            free( psz_tmp );
        }
    }

649
    i_answer = atoi( psz_line );
650 651 652 653

    if( pi_answer ) *pi_answer = i_answer;
    if( ppsz_answer )
    {
654
        *ppsz_answer = psz_line;
655 656 657
    }
    else
    {
658
        free( psz_line );
659 660 661 662
    }
    return( i_answer / 100 );
}

663 664
static int ftp_StartStream( vlc_object_t *p_access, access_sys_t *p_sys,
                            off_t i_start )
665
{
666
    char psz_ipv4[16], *psz_ip = p_sys->sz_epsv_ip;
667 668 669 670
    int  i_answer;
    char *psz_arg, *psz_parser;
    int  i_port;

671
    assert( p_sys->fd_data == -1 );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
672

673 674
    if( ( ftp_SendCommand( p_access, p_sys, *psz_ip ? "EPSV" : "PASV" ) < 0 )
     || ( ftp_ReadCommand( p_access, p_sys, &i_answer, &psz_arg ) != 2 ) )
675
    {
676
        msg_Err( p_access, "cannot set passive mode" );
677
        return VLC_EGENERIC;
678
    }
679

680
    psz_parser = strchr( psz_arg, '(' );
681
    if( psz_parser == NULL )
682
    {
683
        free( psz_arg );
684
        msg_Err( p_access, "cannot parse passive mode response" );
685
        return VLC_EGENERIC;
686
    }
687

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
688
    if( *psz_ip )
689 690 691 692 693 694 695 696 697 698 699 700 701
    {
        char psz_fmt[7] = "(|||%u";
        psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];

        if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }
    }
    else
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
702
        unsigned a1, a2, a3, a4, p1, p2;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
703

704 705 706 707 708 709 710 711 712 713 714 715 716
        if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
                      &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
         || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }

        sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
        psz_ip = psz_ipv4;
        i_port = (p1 << 8) | p2;
    }
717
    free( psz_arg );
718

Laurent Aimar's avatar
Laurent Aimar committed
719
    msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
720

721 722
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) != 2 )
723
    {
hartman's avatar
hartman committed
724
        msg_Err( p_access, "cannot set binary transfer mode" );
725
        return VLC_EGENERIC;
726 727 728 729
    }

    if( i_start > 0 )
    {
730 731
        if( ftp_SendCommand( p_access, p_sys, "REST "I64Fu, i_start ) < 0 ||
            ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 3 )
732
        {
733
            msg_Err( p_access, "cannot set restart offset" );
734
            return VLC_EGENERIC;
735 736 737
        }
    }

Laurent Aimar's avatar
Laurent Aimar committed
738
    msg_Dbg( p_access, "waiting for data connection..." );
739
    p_sys->fd_data = net_ConnectTCP( p_access, psz_ip, i_port );
740
    if( p_sys->fd_data < 0 )
741
    {
Laurent Aimar's avatar
Laurent Aimar committed
742
        msg_Err( p_access, "failed to connect with server" );
743
        return VLC_EGENERIC;
744
    }
Laurent Aimar's avatar
Laurent Aimar committed
745
    msg_Dbg( p_access, "connection with \"%s:%d\" successful",
746 747 748
             psz_ip, i_port );

    /* "1xx" message */
749 750 751 752 753
    if( ftp_SendCommand( p_access, p_sys, "%s %s",
                         (p_access->i_object_type == VLC_OBJECT_ACCESS)
                                 ? "RETR" : "STOR",
                         p_sys->url.psz_path ) < 0 ||
        ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 2 )
754
    {
755
        msg_Err( p_access, "cannot retrieve file" );
756
        return VLC_EGENERIC;
757
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
758

759 760
    shutdown( p_sys->fd_data,
              ( p_access->i_object_type == VLC_OBJECT_ACCESS ) );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
761

762
    return VLC_SUCCESS;
763 764
}

765
static int ftp_StopStream ( vlc_object_t *p_access, access_sys_t *p_sys )
766
{
767
    if( ftp_SendCommand( p_access, p_sys, "ABOR" ) < 0 )
768
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
769
        msg_Warn( p_access, "cannot abort file" );
Laurent Aimar's avatar
Laurent Aimar committed
770 771 772
        if(  p_sys->fd_data > 0 )
            net_Close( p_sys->fd_data );
        p_sys->fd_data = -1;
773
        return VLC_EGENERIC;
774
    }
775 776

    if( p_sys->fd_data != -1 )
Laurent Aimar's avatar
Laurent Aimar committed
777 778 779
    {
        net_Close( p_sys->fd_data );
        p_sys->fd_data = -1;
780
        ftp_ReadCommand( p_access, p_sys, NULL, NULL );
Laurent Aimar's avatar
Laurent Aimar committed
781
    }
782
    ftp_ReadCommand( p_access, p_sys, NULL, NULL );
783

784
    return VLC_SUCCESS;
785
}