]> git.sesse.net Git - vlc/blob - modules/access/ftp.c
FTP URLs are relative to user's default directory (RFC1738)
[vlc] / modules / access / ftp.c
1 /*****************************************************************************
2  * ftp.c: FTP input module
3  *****************************************************************************
4  * Copyright (C) 2001-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr> - original code
8  *          RĂ©mi Denis-Courmont <rem # videolan.org> - EPSV support
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29
30 #include <vlc/vlc.h>
31 #include <vlc/input.h>
32
33 #include "network.h"
34 #if defined( UNDER_CE )
35 #   include <winsock.h>
36 #elif defined( WIN32 )
37 #   include <winsock2.h>
38 #else
39 #   include <sys/socket.h>
40 #endif
41
42 /*****************************************************************************
43  * Module descriptor
44  *****************************************************************************/
45 static int     Open ( vlc_object_t * );
46 static void    Close( vlc_object_t * );
47
48 #define CACHING_TEXT N_("Caching value in ms")
49 #define CACHING_LONGTEXT N_( \
50     "Allows you to modify the default caching value for FTP streams. This " \
51     "value should be set in millisecond units." )
52 #define USER_TEXT N_("FTP user name")
53 #define USER_LONGTEXT N_("Allows you to modify the user name that will " \
54     "be used for the connection.")
55 #define PASS_TEXT N_("FTP password")
56 #define PASS_LONGTEXT N_("Allows you to modify the password that will be " \
57     "used for the connection.")
58 #define ACCOUNT_TEXT N_("FTP account")
59 #define ACCOUNT_LONGTEXT N_("Allows you to modify the account that will be " \
60     "used for the connection.")
61
62 vlc_module_begin();
63     set_shortname( "FTP" );
64     set_description( _("FTP input") );
65     set_capability( "access2", 0 );
66     set_category( CAT_INPUT );
67     set_subcategory( SUBCAT_INPUT_ACCESS );
68     add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
69                  CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
70     add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
71                 VLC_FALSE );
72     add_string( "ftp-pwd", "anonymous@dummy.org", NULL, PASS_TEXT,
73                 PASS_LONGTEXT, VLC_FALSE );
74     add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
75                 ACCOUNT_LONGTEXT, VLC_FALSE );
76     add_shortcut( "ftp" );
77     set_callbacks( Open, Close );
78 vlc_module_end();
79
80 /*****************************************************************************
81  * Local prototypes
82  *****************************************************************************/
83 static int Read( access_t *, uint8_t *, int );
84 static int Seek( access_t *, int64_t );
85 static int Control( access_t *, int, va_list );
86
87 struct access_sys_t
88 {
89     vlc_url_t  url;
90
91     int        fd_cmd;
92     int        fd_data;
93     
94     char       sz_epsv_ip[NI_MAXNUMERICHOST];
95 };
96
97 static int  ftp_SendCommand( access_t *, char *, ... );
98 static int  ftp_ReadCommand( access_t *, int *, char ** );
99 static int  ftp_StartStream( access_t *, int64_t );
100 static int  ftp_StopStream ( access_t *);
101
102 static int Connect( access_t *p_access, access_sys_t *p_sys )
103 {
104     int fd, i_answer;
105     char *psz;
106
107     /* *** Open a TCP connection with server *** */
108     msg_Dbg( p_access, "waiting for connection..." );
109     p_sys->fd_cmd = fd = net_OpenTCP( p_access, p_sys->url.psz_host,
110                                       p_sys->url.i_port );
111     if( fd < 0 )
112     {
113         msg_Err( p_access, "failed to connect with server" );
114         return -1;
115     }
116
117     for( ;; )
118     {
119         if( ftp_ReadCommand( p_access, &i_answer, NULL ) != 1 )
120         {
121             break;
122         }
123     }
124     if( i_answer / 100 != 2 )
125     {
126         msg_Err( p_access, "connection rejected" );
127         return -1;
128     }
129
130     msg_Dbg( p_access, "connection accepted (%d)", i_answer );
131
132     psz = var_CreateGetString( p_access, "ftp-user" );
133     if( ftp_SendCommand( p_access, "USER %s", psz ) < 0 ||
134         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
135     {
136         free( psz );
137         return -1;
138     }
139     free( psz );
140
141     switch( i_answer / 100 )
142     {
143         case 2:
144             msg_Dbg( p_access, "user accepted" );
145             break;
146         case 3:
147             msg_Dbg( p_access, "password needed" );
148             psz = var_CreateGetString( p_access, "ftp-pwd" );
149             if( ftp_SendCommand( p_access, "PASS %s", psz ) < 0 ||
150                 ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
151             {
152                 free( psz );
153                 return -1;
154             }
155             free( psz );
156
157             switch( i_answer / 100 )
158             {
159                 case 2:
160                     msg_Dbg( p_access, "password accepted" );
161                     break;
162                 case 3:
163                     msg_Dbg( p_access, "account needed" );
164                     psz = var_CreateGetString( p_access, "ftp-account" );
165                     if( ftp_SendCommand( p_access, "ACCT %s",
166                                          psz ) < 0 ||
167                         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
168                     {
169                         free( psz );
170                         return -1;
171                     }
172                     free( psz );
173
174                     if( i_answer / 100 != 2 )
175                     {
176                         msg_Err( p_access, "account rejected" );
177                         return -1;
178                     }
179                     msg_Dbg( p_access, "account accepted" );
180                     break;
181
182                 default:
183                     msg_Err( p_access, "password rejected" );
184                     return -1;
185             }
186             break;
187         default:
188             msg_Err( p_access, "user rejected" );
189             return -1;
190     }
191
192     return 0;
193 }
194
195 /****************************************************************************
196  * Open: connect to ftp server and ask for file
197  ****************************************************************************/
198 static int Open( vlc_object_t *p_this )
199 {
200     access_t     *p_access = (access_t*)p_this;
201     access_sys_t *p_sys;
202     char         *psz;
203
204     int          i_answer;
205     char         *psz_arg;
206
207     /* Init p_access */
208     p_access->pf_read = Read;
209     p_access->pf_block = NULL;
210     p_access->pf_seek = Seek;
211     p_access->pf_control = Control;
212     p_access->info.i_update = 0;
213     p_access->info.i_size = 0;
214     p_access->info.i_pos = 0;
215     p_access->info.b_eof = VLC_FALSE;
216     p_access->info.i_title = 0;
217     p_access->info.i_seekpoint = 0;
218     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
219     memset( p_sys, 0, sizeof( access_sys_t ) );
220     p_sys->fd_cmd = -1;
221     p_sys->fd_data = -1;
222
223     /* *** Parse URL and get server addr/port and path *** */
224     psz = p_access->psz_path;
225     while( *psz == '/' )
226     {
227         psz++;
228     }
229     vlc_UrlParse( &p_sys->url, psz, 0 );
230
231     if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
232     {
233         msg_Err( p_access, "invalid server name" );
234         goto exit_error;
235     }
236     if( p_sys->url.i_port <= 0 )
237     {
238         p_sys->url.i_port = 21; /* default port */
239     }
240
241     /* FTP URLs are relative to user's default directory (RFC1738)
242        For absolute path use ftp://foo.bar//usr/local/etc/filename */
243
244     if( *p_sys->url.psz_path == '/' )
245         p_sys->url.psz_path++;
246
247     if( Connect( p_access, p_sys ) < 0 )
248         goto exit_error;
249
250     /* Extended passive mode */
251     if( ftp_SendCommand( p_access, "EPSV ALL" ) < 0 )
252     {
253         msg_Err( p_access, "cannot request extended passive mode" );
254         return -1;
255     }
256
257     if( ftp_ReadCommand( p_access, &i_answer, NULL ) == 2 )
258     {
259         if( net_GetPeerAddress( p_sys->fd_cmd, p_sys->sz_epsv_ip, NULL ) )
260             goto exit_error;
261     }
262     else
263     {
264         /* If ESPV ALL fails, we fallback to PASV.
265          * We have to restart the connection in case there is a NAT that
266          * understands EPSV ALL in the way, and hence won't allow PASV on
267          * the initial connection.
268          */
269         net_Close( p_sys->fd_cmd );
270         p_sys->fd_cmd = -1;
271         *p_sys->sz_epsv_ip = '\0';
272
273         if( ( p_sys->fd_cmd = Connect( p_access, p_sys ) ) < 0 )
274            goto exit_error;
275
276         msg_Info( p_access, "FTP Extended passive mode disabled" );
277     }
278     
279     /* binary mode */
280     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
281         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
282     {
283         msg_Err( p_access, "cannot set binary transfer mode" );
284         goto exit_error;
285     }
286
287     /* get size */
288     if( ftp_SendCommand( p_access, "SIZE %s", p_sys->url.psz_path ) < 0 ||
289         ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 )
290     {
291         msg_Err( p_access, "cannot get file size" );
292         goto exit_error;
293     }
294     p_access->info.i_size = atoll( &psz_arg[4] );
295     free( psz_arg );
296     msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
297
298     /* Start the 'stream' */
299     if( ftp_StartStream( p_access, 0 ) < 0 )
300     {
301         msg_Err( p_access, "cannot retrieve file" );
302         goto exit_error;
303     }
304
305     /* Update default_pts to a suitable value for ftp access */
306     var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
307
308     return VLC_SUCCESS;
309
310 exit_error:
311     if( p_sys->fd_cmd >= 0 )
312         net_Close( p_sys->fd_cmd );
313     vlc_UrlClean( &p_sys->url );
314     free( p_sys );
315     return VLC_EGENERIC;
316 }
317
318 /*****************************************************************************
319  * Close: free unused data structures
320  *****************************************************************************/
321 static void Close( vlc_object_t *p_this )
322 {
323     access_t      *p_access = (access_t*)p_this;
324     access_sys_t  *p_sys = p_access->p_sys;
325
326     msg_Dbg( p_access, "stopping stream" );
327     ftp_StopStream( p_access );
328
329     if( ftp_SendCommand( p_access, "QUIT" ) < 0 )
330     {
331         msg_Warn( p_access, "cannot quit" );
332     }
333     else
334     {
335         ftp_ReadCommand( p_access, NULL, NULL );
336     }
337     net_Close( p_sys->fd_cmd );
338
339     /* free memory */
340     vlc_UrlClean( &p_sys->url );
341     free( p_sys );
342 }
343
344 /*****************************************************************************
345  * Seek: try to go at the right place
346  *****************************************************************************/
347 static int Seek( access_t *p_access, int64_t i_pos )
348 {
349     if( i_pos < 0 )
350     {
351         return VLC_EGENERIC;
352     }
353     msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
354
355     ftp_StopStream( p_access );
356     if( ftp_StartStream( p_access, i_pos ) < 0 )
357     {
358         p_access->info.b_eof = VLC_TRUE;
359         return VLC_EGENERIC;
360     }
361
362     p_access->info.b_eof = VLC_FALSE;
363     p_access->info.i_pos = i_pos;
364
365     return VLC_SUCCESS;
366 }
367
368 /*****************************************************************************
369  * Read:
370  *****************************************************************************/
371 static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
372 {
373     access_sys_t *p_sys = p_access->p_sys;
374     int i_read;
375
376     if( p_access->info.b_eof )
377         return 0;
378
379     i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
380                        VLC_FALSE );
381     if( i_read == 0 )
382         p_access->info.b_eof = VLC_TRUE;
383     else if( i_read > 0 )
384         p_access->info.i_pos += i_read;
385
386     return i_read;
387 }
388
389 /*****************************************************************************
390  * Control:
391  *****************************************************************************/
392 static int Control( access_t *p_access, int i_query, va_list args )
393 {
394     vlc_bool_t   *pb_bool;
395     int          *pi_int;
396     int64_t      *pi_64;
397     vlc_value_t  val;
398
399     switch( i_query )
400     {
401         /* */
402         case ACCESS_CAN_SEEK:
403             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
404             *pb_bool = VLC_TRUE;
405             break;
406         case ACCESS_CAN_FASTSEEK:
407             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
408             *pb_bool = VLC_FALSE;
409             break;
410         case ACCESS_CAN_PAUSE:
411             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
412             *pb_bool = VLC_TRUE;    /* FIXME */
413             break;
414         case ACCESS_CAN_CONTROL_PACE:
415             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
416             *pb_bool = VLC_TRUE;    /* FIXME */
417             break;
418
419         /* */
420         case ACCESS_GET_MTU:
421             pi_int = (int*)va_arg( args, int * );
422             *pi_int = 0;
423             break;
424
425         case ACCESS_GET_PTS_DELAY:
426             pi_64 = (int64_t*)va_arg( args, int64_t * );
427             var_Get( p_access, "ftp-caching", &val );
428             *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
429             break;
430
431         /* */
432         case ACCESS_SET_PAUSE_STATE:
433             /* Nothing to do */
434             break;
435
436         case ACCESS_GET_TITLE_INFO:
437         case ACCESS_SET_TITLE:
438         case ACCESS_SET_SEEKPOINT:
439         case ACCESS_SET_PRIVATE_ID_STATE:
440             return VLC_EGENERIC;
441
442         default:
443             msg_Warn( p_access, "unimplemented query in control" );
444             return VLC_EGENERIC;
445
446     }
447     return VLC_SUCCESS;
448 }
449
450 /*****************************************************************************
451  * ftp_*:
452  *****************************************************************************/
453 static int ftp_SendCommand( access_t *p_access, char *psz_fmt, ... )
454 {
455     access_sys_t *p_sys = p_access->p_sys;
456     va_list      args;
457     char         *psz_cmd;
458
459     va_start( args, psz_fmt );
460     vasprintf( &psz_cmd, psz_fmt, args );
461     va_end( args );
462
463     msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
464     if( net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL, "%s\r\n",
465                     psz_cmd ) < 0 )
466     {
467         msg_Err( p_access, "failed to send command" );
468         return VLC_EGENERIC;
469     }
470     return VLC_SUCCESS;
471 }
472
473 /* TODO support this s**t :
474  RFC 959 allows the client to send certain TELNET strings at any moment,
475  even in the middle of a request:
476
477  * \377\377.
478  * \377\376x where x is one byte.
479  * \377\375x where x is one byte. The server is obliged to send \377\374x
480  *                                immediately after reading x.
481  * \377\374x where x is one byte.
482  * \377\373x where x is one byte. The server is obliged to send \377\376x
483  *                                immediately after reading x.
484  * \377x for any other byte x.
485
486  These strings are not part of the requests, except in the case \377\377,
487  where the request contains one \377. */
488 static int ftp_ReadCommand( access_t *p_access,
489                             int *pi_answer, char **ppsz_answer )
490 {
491     access_sys_t *p_sys = p_access->p_sys;
492     char         *psz_line;
493     int          i_answer;
494
495     psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
496     msg_Dbg( p_access, "answer=%s", psz_line );
497     if( psz_line == NULL || strlen( psz_line ) < 3 )
498     {
499         msg_Err( p_access, "cannot get answer" );
500         if( psz_line ) free( psz_line );
501         if( pi_answer ) *pi_answer    = 500;
502         if( ppsz_answer ) *ppsz_answer  = NULL;
503         return -1;
504     }
505
506     if( psz_line[3] == '-' )    /* Multiple response */
507     {
508         char end[4];
509
510         memcpy( end, psz_line, 3 );
511         end[3] = ' ';
512
513         for( ;; )
514         {
515             char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
516
517             if( psz_tmp == NULL )   /* Error */
518                 break;
519
520             if( !strncmp( psz_tmp, end, 4 ) )
521             {
522                 free( psz_tmp );
523                 break;
524             }
525             free( psz_tmp );
526         }
527     }
528
529     i_answer = atoi( psz_line );
530
531     if( pi_answer ) *pi_answer = i_answer;
532     if( ppsz_answer )
533     {
534         *ppsz_answer = psz_line;
535     }
536     else
537     {
538         free( psz_line );
539     }
540     return( i_answer / 100 );
541 }
542
543 static int ftp_StartStream( access_t *p_access, off_t i_start )
544 {
545     access_sys_t *p_sys = p_access->p_sys;
546
547     char psz_ipv4[16], *psz_ip;
548     int  i_answer;
549     char *psz_arg, *psz_parser;
550     int  i_port;
551
552     psz_ip = p_sys->sz_epsv_ip;
553
554     if( ( ftp_SendCommand( p_access, *psz_ip ? "EPSV" : "PASV" ) < 0 )
555      || ( ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 ) )
556     {
557         msg_Err( p_access, "cannot set passive mode" );
558         return VLC_EGENERIC;
559     }
560
561     psz_parser = strchr( psz_arg, '(' );
562     if( psz_parser == NULL )
563     {
564         free( psz_arg );
565         msg_Err( p_access, "cannot parse passive mode response" );
566         return VLC_EGENERIC;
567     }
568
569     if( psz_ip != NULL )
570     {
571         char psz_fmt[7] = "(|||%u";
572         psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];
573
574         if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
575         {
576             free( psz_arg );
577             msg_Err( p_access, "cannot parse passive mode response" );
578             return VLC_EGENERIC;
579         }
580     }
581     else
582     {
583         unsigned  a1, a2, a3, a4, p1, p2;
584
585         if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
586                       &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
587          || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
588         {
589             free( psz_arg );
590             msg_Err( p_access, "cannot parse passive mode response" );
591             return VLC_EGENERIC;
592         }
593
594         sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
595         psz_ip = psz_ipv4;
596         i_port = (p1 << 8) | p2;
597     }
598     free( psz_arg );
599
600     msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
601
602     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
603         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
604     {
605         msg_Err( p_access, "cannot set binary transfer mode" );
606         return VLC_EGENERIC;
607     }
608
609     if( i_start > 0 )
610     {
611         if( ftp_SendCommand( p_access, "REST "I64Fu, i_start ) < 0 ||
612             ftp_ReadCommand( p_access, &i_answer, NULL ) > 3 )
613         {
614             msg_Err( p_access, "cannot set restart point" );
615             return VLC_EGENERIC;
616         }
617     }
618
619     msg_Dbg( p_access, "waiting for data connection..." );
620     p_sys->fd_data = net_OpenTCP( p_access, psz_ip, i_port );
621     if( p_sys->fd_data < 0 )
622     {
623         msg_Err( p_access, "failed to connect with server" );
624         return VLC_EGENERIC;
625     }
626     msg_Dbg( p_access, "connection with \"%s:%d\" successful",
627              psz_ip, i_port );
628
629     /* "1xx" message */
630     if( ftp_SendCommand( p_access, "RETR %s", p_sys->url.psz_path ) < 0 ||
631         ftp_ReadCommand( p_access, &i_answer, NULL ) > 2 )
632     {
633         msg_Err( p_access, "cannot retreive file" );
634         return VLC_EGENERIC;
635     }
636     return VLC_SUCCESS;
637 }
638
639 static int ftp_StopStream ( access_t *p_access )
640 {
641     access_sys_t *p_sys = p_access->p_sys;
642
643     int i_answer;
644
645     if( ftp_SendCommand( p_access, "ABOR" ) < 0 )
646     {
647         msg_Warn( p_access, "cannot abort file" );
648         if(  p_sys->fd_data > 0 )
649             net_Close( p_sys->fd_data );
650         p_sys->fd_data = -1;
651         return VLC_EGENERIC;
652     }
653     if(  p_sys->fd_data > 0 )
654     {
655         net_Close( p_sys->fd_data );
656         p_sys->fd_data = -1;
657         ftp_ReadCommand( p_access, &i_answer, NULL );
658     }
659     ftp_ReadCommand( p_access, &i_answer, NULL );
660
661     return VLC_SUCCESS;
662 }
663