1 /*****************************************************************************
2 * ftp.c: FTP input module
3 *****************************************************************************
4 * Copyright (C) 2001-2006 the VideoLAN team
5 * Copyright © 2006 Rémi Denis-Courmont
8 * Authors: Laurent Aimar <fenrir@via.ecp.fr> - original code
9 * Rémi Denis-Courmont <rem # videolan.org> - EPSV support
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
37 #include <vlc_access.h>
38 #include <vlc_interface.h>
40 #include <vlc_network.h>
45 # define IPPORT_FTP 21u
48 /*****************************************************************************
50 *****************************************************************************/
51 static int InOpen ( vlc_object_t * );
52 static void InClose( vlc_object_t * );
53 static int OutOpen ( vlc_object_t * );
54 static void OutClose( vlc_object_t * );
56 #define CACHING_TEXT N_("Caching value in ms")
57 #define CACHING_LONGTEXT N_( \
58 "Caching value for FTP streams. This " \
59 "value should be set in milliseconds." )
60 #define USER_TEXT N_("FTP user name")
61 #define USER_LONGTEXT N_("User name that will " \
62 "be used for the connection.")
63 #define PASS_TEXT N_("FTP password")
64 #define PASS_LONGTEXT N_("Password that will be " \
65 "used for the connection.")
66 #define ACCOUNT_TEXT N_("FTP account")
67 #define ACCOUNT_LONGTEXT N_("Account that will be " \
68 "used for the connection.")
71 set_shortname( "FTP" );
72 set_description( _("FTP input") );
73 set_capability( "access2", 0 );
74 set_category( CAT_INPUT );
75 set_subcategory( SUBCAT_INPUT_ACCESS );
76 add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
77 CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
78 add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
80 add_string( "ftp-pwd", "anonymous@example.com", NULL, PASS_TEXT,
81 PASS_LONGTEXT, VLC_FALSE );
82 add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
83 ACCOUNT_LONGTEXT, VLC_FALSE );
84 add_shortcut( "ftp" );
85 set_callbacks( InOpen, InClose );
88 set_shortname( "FTP" );
89 set_description( _("FTP upload output") );
90 set_capability( "sout access", 0 );
91 set_category( CAT_SOUT );
92 set_subcategory( SUBCAT_SOUT_ACO );
93 set_callbacks( OutOpen, OutClose );
96 /*****************************************************************************
98 *****************************************************************************/
99 static ssize_t Read( access_t *, uint8_t *, size_t );
100 static ssize_t Write( sout_access_out_t *, block_t * );
101 static int Seek( access_t *, int64_t );
102 static int OutSeek( sout_access_out_t *, off_t );
103 static int Control( access_t *, int, va_list );
112 char sz_epsv_ip[NI_MAXNUMERICHOST];
114 #define GET_OUT_SYS( p_this ) \
115 ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))
117 static int ftp_SendCommand( vlc_object_t *, access_sys_t *, const char *, ... );
118 static int ftp_ReadCommand( vlc_object_t *, access_sys_t *, int *, char ** );
119 static int ftp_StartStream( vlc_object_t *, access_sys_t *, int64_t );
120 static int ftp_StopStream ( vlc_object_t *, access_sys_t * );
122 static int Login( vlc_object_t *p_access, access_sys_t *p_sys )
127 /* *** Open a TCP connection with server *** */
128 int fd = p_sys->fd_cmd = net_ConnectTCP( p_access, p_sys->url.psz_host,
132 msg_Err( p_access, "connection failed" );
133 intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"),
134 _("VLC could not connect with the given server.") );
138 while( ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) == 1 );
140 if( i_answer / 100 != 2 )
142 msg_Err( p_access, "connection rejected" );
143 intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"),
144 _("VLC's connection to the given server was rejected.") );
148 msg_Dbg( p_access, "connection accepted (%d)", i_answer );
150 if( p_sys->url.psz_username && *p_sys->url.psz_username )
151 psz = strdup( p_sys->url.psz_username );
153 psz = var_CreateGetString( p_access, "ftp-user" );
155 if( ftp_SendCommand( p_access, p_sys, "USER %s", psz ) < 0 ||
156 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
163 switch( i_answer / 100 )
166 msg_Dbg( p_access, "user accepted" );
169 msg_Dbg( p_access, "password needed" );
170 if( p_sys->url.psz_password && *p_sys->url.psz_password )
171 psz = strdup( p_sys->url.psz_password );
173 psz = var_CreateGetString( p_access, "ftp-pwd" );
175 if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz ) < 0 ||
176 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
183 switch( i_answer / 100 )
186 msg_Dbg( p_access, "password accepted" );
189 msg_Dbg( p_access, "account needed" );
190 psz = var_CreateGetString( p_access, "ftp-account" );
191 if( ftp_SendCommand( p_access, p_sys, "ACCT %s",
193 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
200 if( i_answer / 100 != 2 )
202 msg_Err( p_access, "account rejected" );
203 intf_UserFatal( p_access, VLC_FALSE,
204 _("Network interaction failed"),
205 _("Your account was rejected.") );
208 msg_Dbg( p_access, "account accepted" );
212 msg_Err( p_access, "password rejected" );
213 intf_UserFatal( p_access, VLC_FALSE,
214 _("Network interaction failed"),
215 _("Your password was rejected.") );
220 msg_Err( p_access, "user rejected" );
221 intf_UserFatal( p_access, VLC_FALSE,
222 _("Network interaction failed"),
223 _("Your connection attempt to the server was rejected.") );
230 static int Connect( vlc_object_t *p_access, access_sys_t *p_sys )
232 if( Login( p_access, p_sys ) < 0 )
235 /* Extended passive mode */
236 if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
238 msg_Err( p_access, "cannot request extended passive mode" );
239 net_Close( p_sys->fd_cmd );
243 if( ftp_ReadCommand( p_access, p_sys, NULL, NULL ) == 2 )
245 if( net_GetPeerAddress( p_sys->fd_cmd, p_sys->sz_epsv_ip, NULL ) )
247 net_Close( p_sys->fd_cmd );
253 /* If ESPV ALL fails, we fallback to PASV.
254 * We have to restart the connection in case there is a NAT that
255 * understands EPSV ALL in the way, and hence won't allow PASV on
256 * the initial connection.
258 msg_Info( p_access, "FTP Extended passive mode disabled" );
259 net_Close( p_sys->fd_cmd );
261 if( Login( p_access, p_sys ) )
263 net_Close( p_sys->fd_cmd );
268 /* check binary mode support */
269 if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
270 ftp_ReadCommand( p_access, p_sys, NULL, NULL ) != 2 )
272 msg_Err( p_access, "cannot set binary transfer mode" );
273 net_Close( p_sys->fd_cmd );
281 static int parseURL( vlc_url_t *url, const char *path )
286 /* *** Parse URL and get server addr/port and path *** */
287 while( *path == '/' )
290 vlc_UrlParse( url, path, 0 );
292 if( url->psz_host == NULL || *url->psz_host == '\0' )
295 if( url->i_port <= 0 )
296 url->i_port = IPPORT_FTP; /* default port */
298 /* FTP URLs are relative to user's default directory (RFC1738)
299 For absolute path use ftp://foo.bar//usr/local/etc/filename */
301 if( *url->psz_path == '/' )
308 /****************************************************************************
309 * Open: connect to ftp server and ask for file
310 ****************************************************************************/
311 static int InOpen( vlc_object_t *p_this )
313 access_t *p_access = (access_t*)p_this;
318 STANDARD_READ_ACCESS_INIT
321 if( parseURL( &p_sys->url, p_access->psz_path ) )
324 if( Connect( p_this, p_sys ) )
328 if( ftp_SendCommand( p_this, p_sys, "SIZE %s", p_sys->url.psz_path ) < 0 ||
329 ftp_ReadCommand( p_this, p_sys, NULL, &psz_arg ) != 2 )
331 msg_Err( p_access, "cannot get file size" );
332 net_Close( p_sys->fd_cmd );
335 p_access->info.i_size = atoll( &psz_arg[4] );
337 msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
339 /* Start the 'stream' */
340 if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
342 msg_Err( p_access, "cannot retrieve file" );
343 net_Close( p_sys->fd_cmd );
347 /* Update default_pts to a suitable value for ftp access */
348 var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
353 vlc_UrlClean( &p_sys->url );
358 static int OutOpen( vlc_object_t *p_this )
360 sout_access_out_t *p_access = (sout_access_out_t *)p_this;
363 p_sys = malloc( sizeof( *p_sys ) );
366 memset( p_sys, 0, sizeof( *p_sys ) );
371 if( parseURL( &p_sys->url, p_access->psz_path ) )
374 if( Connect( p_this, p_sys ) )
377 /* Start the 'stream' */
378 if( ftp_StartStream( p_this, p_sys, 0 ) < 0 )
380 msg_Err( p_access, "cannot store file" );
381 net_Close( p_sys->fd_cmd );
385 p_access->pf_seek = OutSeek;
386 p_access->pf_write = Write;
387 p_access->p_sys = (void *)p_sys;
392 vlc_UrlClean( &p_sys->url );
397 /*****************************************************************************
398 * Close: free unused data structures
399 *****************************************************************************/
400 static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
402 msg_Dbg( p_access, "stopping stream" );
403 ftp_StopStream( p_access, p_sys );
405 if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
407 msg_Warn( p_access, "cannot quit" );
411 ftp_ReadCommand( p_access, p_sys, NULL, NULL );
413 net_Close( p_sys->fd_cmd );
416 vlc_UrlClean( &p_sys->url );
420 static void InClose( vlc_object_t *p_this )
422 Close( p_this, ((access_t *)p_this)->p_sys);
425 static void OutClose( vlc_object_t *p_this )
427 Close( p_this, GET_OUT_SYS(p_this));
431 /*****************************************************************************
432 * Seek: try to go at the right place
433 *****************************************************************************/
434 static int _Seek( vlc_object_t *p_access, access_sys_t *p_sys, int64_t i_pos )
439 msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
441 ftp_StopStream( (vlc_object_t *)p_access, p_sys );
442 if( ftp_StartStream( (vlc_object_t *)p_access, p_sys, i_pos ) < 0 )
448 static int Seek( access_t *p_access, int64_t i_pos )
450 int val = _Seek( (vlc_object_t *)p_access, p_access->p_sys, i_pos );
454 p_access->info.b_eof = VLC_FALSE;
455 p_access->info.i_pos = i_pos;
460 static int OutSeek( sout_access_out_t *p_access, off_t i_pos )
462 return _Seek( (vlc_object_t *)p_access, GET_OUT_SYS( p_access ), i_pos);
465 /*****************************************************************************
467 *****************************************************************************/
468 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
470 access_sys_t *p_sys = p_access->p_sys;
473 assert( p_sys->fd_data != -1 );
474 assert( p_access->i_object_type == VLC_OBJECT_ACCESS );
476 if( p_access->info.b_eof )
479 i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
482 p_access->info.b_eof = VLC_TRUE;
483 else if( i_read > 0 )
484 p_access->info.i_pos += i_read;
489 /*****************************************************************************
491 *****************************************************************************/
492 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
494 access_sys_t *p_sys = GET_OUT_SYS(p_access);
497 assert( p_sys->fd_data != -1 );
499 while( p_buffer != NULL )
501 block_t *p_next = p_buffer->p_next;;
503 i_write += net_Write( p_access, p_sys->fd_data, NULL,
504 p_buffer->p_buffer, p_buffer->i_buffer );
505 block_Release( p_buffer );
513 /*****************************************************************************
515 *****************************************************************************/
516 static int Control( access_t *p_access, int i_query, va_list args )
526 case ACCESS_CAN_SEEK:
527 pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
530 case ACCESS_CAN_FASTSEEK:
531 pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
532 *pb_bool = VLC_FALSE;
534 case ACCESS_CAN_PAUSE:
535 pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
536 *pb_bool = VLC_TRUE; /* FIXME */
538 case ACCESS_CAN_CONTROL_PACE:
539 pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
540 *pb_bool = VLC_TRUE; /* FIXME */
545 pi_int = (int*)va_arg( args, int * );
549 case ACCESS_GET_PTS_DELAY:
550 pi_64 = (int64_t*)va_arg( args, int64_t * );
551 var_Get( p_access, "ftp-caching", &val );
552 *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
556 case ACCESS_SET_PAUSE_STATE:
557 pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
559 return Seek( p_access, p_access->info.i_pos );
562 case ACCESS_GET_TITLE_INFO:
563 case ACCESS_SET_TITLE:
564 case ACCESS_SET_SEEKPOINT:
565 case ACCESS_SET_PRIVATE_ID_STATE:
566 case ACCESS_GET_CONTENT_TYPE:
567 case ACCESS_GET_META:
571 msg_Warn( p_access, "unimplemented query in control: %d", i_query);
578 /*****************************************************************************
580 *****************************************************************************/
581 static int ftp_SendCommand( vlc_object_t *p_access, access_sys_t *p_sys,
582 const char *psz_fmt, ... )
587 va_start( args, psz_fmt );
588 vasprintf( &psz_cmd, psz_fmt, args );
591 msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
592 if( net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL, "%s\r\n",
595 msg_Err( p_access, "failed to send command" );
601 /* TODO support this s**t :
602 RFC 959 allows the client to send certain TELNET strings at any moment,
603 even in the middle of a request:
606 * \377\376x where x is one byte.
607 * \377\375x where x is one byte. The server is obliged to send \377\374x
608 * immediately after reading x.
609 * \377\374x where x is one byte.
610 * \377\373x where x is one byte. The server is obliged to send \377\376x
611 * immediately after reading x.
612 * \377x for any other byte x.
614 These strings are not part of the requests, except in the case \377\377,
615 where the request contains one \377. */
616 static int ftp_ReadCommand( vlc_object_t *p_access, access_sys_t *p_sys,
617 int *pi_answer, char **ppsz_answer )
622 psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
623 if( psz_line == NULL || strlen( psz_line ) < 3 )
625 msg_Err( p_access, "cannot get answer" );
626 if( psz_line ) free( psz_line );
627 if( pi_answer ) *pi_answer = 500;
628 if( ppsz_answer ) *ppsz_answer = NULL;
631 msg_Dbg( p_access, "answer=%s", psz_line );
633 if( psz_line[3] == '-' ) /* Multiple response */
637 memcpy( end, psz_line, 3 );
642 char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
644 if( psz_tmp == NULL ) /* Error */
647 if( !strncmp( psz_tmp, end, 4 ) )
656 i_answer = atoi( psz_line );
658 if( pi_answer ) *pi_answer = i_answer;
661 *ppsz_answer = psz_line;
667 return( i_answer / 100 );
670 static int ftp_StartStream( vlc_object_t *p_access, access_sys_t *p_sys,
673 char psz_ipv4[16], *psz_ip = p_sys->sz_epsv_ip;
675 char *psz_arg, *psz_parser;
678 assert( p_sys->fd_data == -1 );
680 if( ( ftp_SendCommand( p_access, p_sys, *psz_ip ? "EPSV" : "PASV" ) < 0 )
681 || ( ftp_ReadCommand( p_access, p_sys, &i_answer, &psz_arg ) != 2 ) )
683 msg_Err( p_access, "cannot set passive mode" );
687 psz_parser = strchr( psz_arg, '(' );
688 if( psz_parser == NULL )
691 msg_Err( p_access, "cannot parse passive mode response" );
697 char psz_fmt[7] = "(|||%u";
698 psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];
700 if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
703 msg_Err( p_access, "cannot parse passive mode response" );
709 unsigned a1, a2, a3, a4, p1, p2;
711 if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
712 &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
713 || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
716 msg_Err( p_access, "cannot parse passive mode response" );
720 sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
722 i_port = (p1 << 8) | p2;
726 msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
728 if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
729 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) != 2 )
731 msg_Err( p_access, "cannot set binary transfer mode" );
737 if( ftp_SendCommand( p_access, p_sys, "REST "I64Fu, i_start ) < 0 ||
738 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 3 )
740 msg_Err( p_access, "cannot set restart offset" );
745 msg_Dbg( p_access, "waiting for data connection..." );
746 p_sys->fd_data = net_ConnectTCP( p_access, psz_ip, i_port );
747 if( p_sys->fd_data < 0 )
749 msg_Err( p_access, "failed to connect with server" );
752 msg_Dbg( p_access, "connection with \"%s:%d\" successful",
756 if( ftp_SendCommand( p_access, p_sys, "%s %s",
757 (p_access->i_object_type == VLC_OBJECT_ACCESS)
759 p_sys->url.psz_path ) < 0 ||
760 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL ) > 2 )
762 msg_Err( p_access, "cannot retrieve file" );
766 shutdown( p_sys->fd_data,
767 ( p_access->i_object_type == VLC_OBJECT_ACCESS ) );
772 static int ftp_StopStream ( vlc_object_t *p_access, access_sys_t *p_sys )
774 if( ftp_SendCommand( p_access, p_sys, "ABOR" ) < 0 )
776 msg_Warn( p_access, "cannot abort file" );
777 if( p_sys->fd_data > 0 )
778 net_Close( p_sys->fd_data );
783 if( p_sys->fd_data != -1 )
786 ftp_ReadCommand( p_access, p_sys, &i_answer, NULL );
787 if ( i_answer != 227 )
788 /* If answer is from the previous command,
789 * rathen that succesful ABOR - read next command */
790 ftp_ReadCommand( p_access, p_sys, NULL, NULL );
792 net_Close( p_sys->fd_data );