]> git.sesse.net Git - vlc/blob - modules/access/ftp.c
More cosmetic
[vlc] / modules / access / ftp.c
1 /*****************************************************************************
2  * ftp.c: FTP input module
3  *****************************************************************************
4  * Copyright (C) 2001-2006 the VideoLAN team
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  * 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.
15  *
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.
20  *
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  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include <stdlib.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/input.h>
33 #include <vlc_interaction.h>
34
35 #include "network.h"
36 #include "vlc_url.h"
37
38 /*****************************************************************************
39  * Module descriptor
40  *****************************************************************************/
41 static int     Open ( vlc_object_t * );
42 static void    Close( vlc_object_t * );
43
44 #define CACHING_TEXT N_("Caching value in ms")
45 #define CACHING_LONGTEXT N_( \
46     "Caching value for FTP streams. This " \
47     "value should be set in milliseconds." )
48 #define USER_TEXT N_("FTP user name")
49 #define USER_LONGTEXT N_("User name that will " \
50     "be used for the connection.")
51 #define PASS_TEXT N_("FTP password")
52 #define PASS_LONGTEXT N_("Password that will be " \
53     "used for the connection.")
54 #define ACCOUNT_TEXT N_("FTP account")
55 #define ACCOUNT_LONGTEXT N_("Account that will be " \
56     "used for the connection.")
57
58 vlc_module_begin();
59     set_shortname( "FTP" );
60     set_description( _("FTP input") );
61     set_capability( "access2", 0 );
62     set_category( CAT_INPUT );
63     set_subcategory( SUBCAT_INPUT_ACCESS );
64     add_integer( "ftp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
65                  CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
66     add_string( "ftp-user", "anonymous", NULL, USER_TEXT, USER_LONGTEXT,
67                 VLC_FALSE );
68     add_string( "ftp-pwd", "anonymous@example.com", NULL, PASS_TEXT,
69                 PASS_LONGTEXT, VLC_FALSE );
70     add_string( "ftp-account", "anonymous", NULL, ACCOUNT_TEXT,
71                 ACCOUNT_LONGTEXT, VLC_FALSE );
72     add_shortcut( "ftp" );
73     set_callbacks( Open, Close );
74 vlc_module_end();
75
76 /*****************************************************************************
77  * Local prototypes
78  *****************************************************************************/
79 static int Read( access_t *, uint8_t *, int );
80 static int Seek( access_t *, int64_t );
81 static int Control( access_t *, int, va_list );
82
83 struct access_sys_t
84 {
85     vlc_url_t  url;
86
87     int        fd_cmd;
88     int        fd_data;
89
90     char       sz_epsv_ip[NI_MAXNUMERICHOST];
91 };
92
93 static int  ftp_SendCommand( access_t *, char *, ... );
94 static int  ftp_ReadCommand( access_t *, int *, char ** );
95 static int  ftp_StartStream( access_t *, int64_t );
96 static int  ftp_StopStream ( access_t *);
97
98 static int Connect( access_t *p_access, access_sys_t *p_sys )
99 {
100     int fd, i_answer;
101     char *psz;
102
103     /* *** Open a TCP connection with server *** */
104     p_sys->fd_cmd = fd = net_ConnectTCP( p_access, p_sys->url.psz_host,
105                                       p_sys->url.i_port );
106     if( fd == -1 )
107     {
108         msg_Err( p_access, "connection failed" );
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     while( ftp_ReadCommand( p_access, &i_answer, NULL ) == 1 );
115
116     if( i_answer / 100 != 2 )
117     {
118         msg_Err( p_access, "connection rejected" );
119         intf_UserFatal( p_access, VLC_FALSE, _("Network interaction failed"), 
120                         _("VLC's connection to the given server was rejected.") );
121         return -1;
122     }
123
124     msg_Dbg( p_access, "connection accepted (%d)", i_answer );
125
126     if( p_sys->url.psz_username && *p_sys->url.psz_username )
127         psz = strdup( p_sys->url.psz_username );
128     else
129         psz = var_CreateGetString( p_access, "ftp-user" );
130
131     if( ftp_SendCommand( p_access, "USER %s", psz ) < 0 ||
132         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
133     {
134         free( psz );
135         return -1;
136     }
137     free( psz );
138
139     switch( i_answer / 100 )
140     {
141         case 2:
142             msg_Dbg( p_access, "user accepted" );
143             break;
144         case 3:
145             msg_Dbg( p_access, "password needed" );
146             if( p_sys->url.psz_password && *p_sys->url.psz_password )
147                 psz = strdup( p_sys->url.psz_password );
148             else
149                 psz = var_CreateGetString( p_access, "ftp-pwd" );
150
151             if( ftp_SendCommand( p_access, "PASS %s", psz ) < 0 ||
152                 ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
153             {
154                 free( psz );
155                 return -1;
156             }
157             free( psz );
158
159             switch( i_answer / 100 )
160             {
161                 case 2:
162                     msg_Dbg( p_access, "password accepted" );
163                     break;
164                 case 3:
165                     msg_Dbg( p_access, "account needed" );
166                     psz = var_CreateGetString( p_access, "ftp-account" );
167                     if( ftp_SendCommand( p_access, "ACCT %s",
168                                          psz ) < 0 ||
169                         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
170                     {
171                         free( psz );
172                         return -1;
173                     }
174                     free( psz );
175
176                     if( i_answer / 100 != 2 )
177                     {
178                         msg_Err( p_access, "account rejected" );
179                         intf_UserFatal( p_access, VLC_FALSE, 
180                                         _("Network interaction failed"), 
181                                         _("Your account was rejected.") );
182                         return -1;
183                     }
184                     msg_Dbg( p_access, "account accepted" );
185                     break;
186
187                 default:
188                     msg_Err( p_access, "password rejected" );
189                     intf_UserFatal( p_access, VLC_FALSE, 
190                                     _("Network interaction failed"), 
191                                     _("Your password was rejected.") );
192                     return -1;
193             }
194             break;
195         default:
196             msg_Err( p_access, "user rejected" );
197             intf_UserFatal( p_access, VLC_FALSE, 
198                         _("Network interaction failed"), 
199                         _("Your connection attemp to the server was rejected.") );
200             return -1;
201     }
202
203     return 0;
204 }
205
206 /****************************************************************************
207  * Open: connect to ftp server and ask for file
208  ****************************************************************************/
209 static int Open( vlc_object_t *p_this )
210 {
211     access_t     *p_access = (access_t*)p_this;
212     access_sys_t *p_sys;
213     char         *psz;
214
215     int          i_answer;
216     char         *psz_arg;
217
218     /* Init p_access */
219     STANDARD_READ_ACCESS_INIT
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         msg_Info( p_access, "FTP Extended passive mode disabled" );
274
275         if( ( p_sys->fd_cmd = Connect( p_access, p_sys ) ) < 0 )
276            goto exit_error;
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 != -1 )
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_ConnectTCP( 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