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