]> git.sesse.net Git - vlc/blob - modules/access/ftp.c
Use integer rather than strings for UDP/TCP port numbers
[vlc] / modules / access / ftp.c
1 /*****************************************************************************
2  * ftp.c: FTP input module
3  *****************************************************************************
4  * Copyright (C) 2001-2005 VideoLAN
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       *psz_epsv_ip;
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 /****************************************************************************
103  * Open: connect to ftp server and ask for file
104  ****************************************************************************/
105 static int Open( vlc_object_t *p_this )
106 {
107     access_t     *p_access = (access_t*)p_this;
108     access_sys_t *p_sys;
109     char         *psz;
110
111     int          i_answer;
112     char         *psz_arg;
113
114     /* Init p_access */
115     p_access->pf_read = Read;
116     p_access->pf_block = NULL;
117     p_access->pf_seek = Seek;
118     p_access->pf_control = Control;
119     p_access->info.i_update = 0;
120     p_access->info.i_size = 0;
121     p_access->info.i_pos = 0;
122     p_access->info.b_eof = VLC_FALSE;
123     p_access->info.i_title = 0;
124     p_access->info.i_seekpoint = 0;
125     p_access->p_sys = p_sys = malloc( sizeof( access_sys_t ) );
126     memset( p_sys, 0, sizeof( access_sys_t ) );
127     p_sys->fd_cmd = -1;
128     p_sys->fd_data = -1;
129     p_sys->psz_epsv_ip = NULL;
130
131     /* *** Parse URL and get server addr/port and path *** */
132     psz = p_access->psz_path;
133     while( *psz == '/' )
134     {
135         psz++;
136     }
137     vlc_UrlParse( &p_sys->url, psz, 0 );
138
139     if( p_sys->url.psz_host == NULL || *p_sys->url.psz_host == '\0' )
140     {
141         msg_Err( p_access, "invalid server name" );
142         goto exit_error;
143     }
144     if( p_sys->url.i_port <= 0 )
145     {
146         p_sys->url.i_port = 21; /* default port */
147     }
148
149     /* *** Open a TCP connection with server *** */
150     msg_Dbg( p_access, "waiting for connection..." );
151     p_sys->fd_cmd = net_OpenTCP( p_access, p_sys->url.psz_host,
152                                  p_sys->url.i_port );
153     if( p_sys->fd_cmd < 0 )
154     {
155         msg_Err( p_access, "failed to connect with server" );
156         goto exit_error;
157     }
158
159     for( ;; )
160     {
161         if( ftp_ReadCommand( p_access, &i_answer, NULL ) != 1 )
162         {
163             break;
164         }
165     }
166     if( i_answer / 100 != 2 )
167     {
168         msg_Err( p_access, "connection rejected" );
169         goto exit_error;
170     }
171
172     msg_Dbg( p_access, "connection accepted (%d)", i_answer );
173
174     psz = var_CreateGetString( p_access, "ftp-user" );
175     if( ftp_SendCommand( p_access, "USER %s", psz ) < 0 ||
176         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
177     {
178         free( psz );
179         goto exit_error;
180     }
181     free( psz );
182
183     switch( i_answer / 100 )
184     {
185         case 2:
186             msg_Dbg( p_access, "user accepted" );
187             break;
188         case 3:
189             msg_Dbg( p_access, "password needed" );
190             psz = var_CreateGetString( p_access, "ftp-pwd" );
191             if( ftp_SendCommand( p_access, "PASS %s", psz ) < 0 ||
192                 ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
193             {
194                 free( psz );
195                 goto exit_error;
196             }
197             free( psz );
198
199             switch( i_answer / 100 )
200             {
201                 case 2:
202                     msg_Dbg( p_access, "password accepted" );
203                     break;
204                 case 3:
205                     msg_Dbg( p_access, "account needed" );
206                     psz = var_CreateGetString( p_access, "ftp-account" );
207                     if( ftp_SendCommand( p_access, "ACCT %s",
208                                          psz ) < 0 ||
209                         ftp_ReadCommand( p_access, &i_answer, NULL ) < 0 )
210                     {
211                         free( psz );
212                         goto exit_error;
213                     }
214                     free( psz );
215
216                     if( i_answer / 100 != 2 )
217                     {
218                         msg_Err( p_access, "account rejected" );
219                         goto exit_error;
220                     }
221                     msg_Dbg( p_access, "account accepted" );
222                     break;
223
224                 default:
225                     msg_Err( p_access, "password rejected" );
226                     goto exit_error;
227             }
228             break;
229         default:
230             msg_Err( p_access, "user rejected" );
231             goto exit_error;
232     }
233
234     /* Extended passive mode */
235     if( ftp_SendCommand( p_access, "EPSV ALL" ) < 0 )
236     {
237         msg_Err( p_access, "cannot request extended passive mode" );
238         goto exit_error;
239     }
240
241     if( ftp_ReadCommand( p_access, &i_answer, NULL ) == 2 )
242     {
243         char hostaddr[NI_MAXNUMERICHOST];
244         struct sockaddr_storage addr;
245         socklen_t len = sizeof (addr);
246
247         if( getpeername( p_sys->fd_cmd, (struct sockaddr *)&addr, &len ) )
248         {
249             msg_Err( p_access, "getpeername failed" );
250             goto exit_error;
251         }
252
253         i_answer = vlc_getnameinfo( p_this, (struct sockaddr *)&addr, len,
254                                     hostaddr, sizeof( hostaddr ), NULL,
255                                     NI_NUMERICHOST );
256         if( i_answer )
257         {
258             msg_Err( p_access, "getnameinfo failed: %s",
259                      vlc_gai_strerror( i_answer ) );
260             goto exit_error;
261         }
262         p_sys->psz_epsv_ip = strdup( hostaddr );
263     }
264     if( p_sys->psz_epsv_ip == NULL )
265         msg_Info( p_access, "FTP Extended passive mode disabled" );
266
267     /* binary mode */
268     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
269         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
270     {
271         msg_Err( p_access, "cannot set binary transfer mode" );
272         goto exit_error;
273     }
274
275     /* get size */
276     if( ftp_SendCommand( p_access, "SIZE %s", p_sys->url.psz_path ) < 0 ||
277         ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 )
278     {
279         msg_Err( p_access, "cannot get file size" );
280         goto exit_error;
281     }
282     p_access->info.i_size = atoll( &psz_arg[4] );
283     free( psz_arg );
284     msg_Dbg( p_access, "file size: "I64Fd, p_access->info.i_size );
285
286     /* Start the 'stream' */
287     if( ftp_StartStream( p_access, 0 ) < 0 )
288     {
289         msg_Err( p_access, "cannot retrieve file" );
290         goto exit_error;
291     }
292
293     /* Update default_pts to a suitable value for ftp access */
294     var_Create( p_access, "ftp-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
295
296     return VLC_SUCCESS;
297
298 exit_error:
299     if( p_sys->fd_cmd > 0 )
300     {
301         net_Close( p_sys->fd_cmd );
302     }
303     vlc_UrlClean( &p_sys->url );
304     free( p_sys );
305     return VLC_EGENERIC;
306 }
307
308 /*****************************************************************************
309  * Close: free unused data structures
310  *****************************************************************************/
311 static void Close( vlc_object_t *p_this )
312 {
313     access_t      *p_access = (access_t*)p_this;
314     access_sys_t  *p_sys = p_access->p_sys;
315
316     msg_Dbg( p_access, "stopping stream" );
317     ftp_StopStream( p_access );
318
319     if( ftp_SendCommand( p_access, "QUIT" ) < 0 )
320     {
321         msg_Warn( p_access, "cannot quit" );
322     }
323     else
324     {
325         ftp_ReadCommand( p_access, NULL, NULL );
326     }
327     net_Close( p_sys->fd_cmd );
328     if( p_sys->psz_epsv_ip != NULL )
329         free( p_sys->psz_epsv_ip );
330
331     /* free memory */
332     vlc_UrlClean( &p_sys->url );
333     free( p_sys );
334 }
335
336 /*****************************************************************************
337  * Seek: try to go at the right place
338  *****************************************************************************/
339 static int Seek( access_t *p_access, int64_t i_pos )
340 {
341     if( i_pos < 0 )
342     {
343         return VLC_EGENERIC;
344     }
345     msg_Dbg( p_access, "seeking to "I64Fd, i_pos );
346
347     ftp_StopStream( p_access );
348     if( ftp_StartStream( p_access, i_pos ) < 0 )
349     {
350         p_access->info.b_eof = VLC_TRUE;
351         return VLC_EGENERIC;
352     }
353
354     p_access->info.b_eof = VLC_FALSE;
355     p_access->info.i_pos = i_pos;
356
357     return VLC_SUCCESS;
358 }
359
360 /*****************************************************************************
361  * Read:
362  *****************************************************************************/
363 static int Read( access_t *p_access, uint8_t *p_buffer, int i_len )
364 {
365     access_sys_t *p_sys = p_access->p_sys;
366     int i_read;
367
368     if( p_access->info.b_eof )
369         return 0;
370
371     i_read = net_Read( p_access, p_sys->fd_data, NULL, p_buffer, i_len,
372                        VLC_FALSE );
373     if( i_read == 0 )
374         p_access->info.b_eof = VLC_TRUE;
375     else if( i_read > 0 )
376         p_access->info.i_pos += i_read;
377
378     return i_read;
379 }
380
381 /*****************************************************************************
382  * Control:
383  *****************************************************************************/
384 static int Control( access_t *p_access, int i_query, va_list args )
385 {
386     vlc_bool_t   *pb_bool;
387     int          *pi_int;
388     int64_t      *pi_64;
389     vlc_value_t  val;
390
391     switch( i_query )
392     {
393         /* */
394         case ACCESS_CAN_SEEK:
395             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
396             *pb_bool = VLC_TRUE;
397             break;
398         case ACCESS_CAN_FASTSEEK:
399             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
400             *pb_bool = VLC_FALSE;
401             break;
402         case ACCESS_CAN_PAUSE:
403             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
404             *pb_bool = VLC_TRUE;    /* FIXME */
405             break;
406         case ACCESS_CAN_CONTROL_PACE:
407             pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
408             *pb_bool = VLC_TRUE;    /* FIXME */
409             break;
410
411         /* */
412         case ACCESS_GET_MTU:
413             pi_int = (int*)va_arg( args, int * );
414             *pi_int = 0;
415             break;
416
417         case ACCESS_GET_PTS_DELAY:
418             pi_64 = (int64_t*)va_arg( args, int64_t * );
419             var_Get( p_access, "ftp-caching", &val );
420             *pi_64 = (int64_t)var_GetInteger( p_access, "ftp-caching" ) * I64C(1000);
421             break;
422
423         /* */
424         case ACCESS_SET_PAUSE_STATE:
425             /* Nothing to do */
426             break;
427
428         case ACCESS_GET_TITLE_INFO:
429         case ACCESS_SET_TITLE:
430         case ACCESS_SET_SEEKPOINT:
431         case ACCESS_SET_PRIVATE_ID_STATE:
432             return VLC_EGENERIC;
433
434         default:
435             msg_Warn( p_access, "unimplemented query in control" );
436             return VLC_EGENERIC;
437
438     }
439     return VLC_SUCCESS;
440 }
441
442 /*****************************************************************************
443  * ftp_*:
444  *****************************************************************************/
445 static int ftp_SendCommand( access_t *p_access, char *psz_fmt, ... )
446 {
447     access_sys_t *p_sys = p_access->p_sys;
448     va_list      args;
449     char         *psz_cmd;
450
451     va_start( args, psz_fmt );
452     vasprintf( &psz_cmd, psz_fmt, args );
453     va_end( args );
454
455     msg_Dbg( p_access, "ftp_SendCommand:\"%s\"", psz_cmd);
456     if( net_Printf( VLC_OBJECT(p_access), p_sys->fd_cmd, NULL, "%s\r\n",
457                     psz_cmd ) < 0 )
458     {
459         msg_Err( p_access, "failed to send command" );
460         return VLC_EGENERIC;
461     }
462     return VLC_SUCCESS;
463 }
464
465 /* TODO support this s**t :
466  RFC 959 allows the client to send certain TELNET strings at any moment,
467  even in the middle of a request:
468
469  * \377\377.
470  * \377\376x where x is one byte.
471  * \377\375x where x is one byte. The server is obliged to send \377\374x
472  *                                immediately after reading x.
473  * \377\374x where x is one byte.
474  * \377\373x where x is one byte. The server is obliged to send \377\376x
475  *                                immediately after reading x.
476  * \377x for any other byte x.
477
478  These strings are not part of the requests, except in the case \377\377,
479  where the request contains one \377. */
480 static int ftp_ReadCommand( access_t *p_access,
481                             int *pi_answer, char **ppsz_answer )
482 {
483     access_sys_t *p_sys = p_access->p_sys;
484     char         *psz_line;
485     int          i_answer;
486
487     psz_line = net_Gets( p_access, p_sys->fd_cmd, NULL );
488     msg_Dbg( p_access, "answer=%s", psz_line );
489     if( psz_line == NULL || strlen( psz_line ) < 3 )
490     {
491         msg_Err( p_access, "cannot get answer" );
492         if( psz_line ) free( psz_line );
493         if( pi_answer ) *pi_answer    = 500;
494         if( ppsz_answer ) *ppsz_answer  = NULL;
495         return -1;
496     }
497
498     if( psz_line[3] == '-' )    /* Multiple response */
499     {
500         char end[4];
501
502         memcpy( end, psz_line, 3 );
503         end[3] = ' ';
504
505         for( ;; )
506         {
507             char *psz_tmp = net_Gets( p_access, p_sys->fd_cmd, NULL );
508
509             if( psz_tmp == NULL )   /* Error */
510                 break;
511
512             if( !strncmp( psz_tmp, end, 4 ) )
513             {
514                 free( psz_tmp );
515                 break;
516             }
517             free( psz_tmp );
518         }
519     }
520
521     i_answer = atoi( psz_line );
522
523     if( pi_answer ) *pi_answer = i_answer;
524     if( ppsz_answer )
525     {
526         *ppsz_answer = psz_line;
527     }
528     else
529     {
530         free( psz_line );
531     }
532     return( i_answer / 100 );
533 }
534
535 static int ftp_StartStream( access_t *p_access, off_t i_start )
536 {
537     access_sys_t *p_sys = p_access->p_sys;
538
539     char psz_ipv4[16], *psz_ip;
540     int  i_answer;
541     char *psz_arg, *psz_parser;
542     unsigned  a1, a2, a3, a4, p1, p2;
543     int  i_port;
544
545     if( ( ftp_SendCommand( p_access, p_sys->psz_epsv_ip != NULL
546                                      ? "EPSV" : "PASV" ) < 0 )
547      || ( ftp_ReadCommand( p_access, &i_answer, &psz_arg ) != 2 ) )
548     {
549         msg_Err( p_access, "cannot set passive mode" );
550         return VLC_EGENERIC;
551     }
552
553     psz_parser = strchr( psz_arg, '(' );
554     if( psz_parser == NULL )
555     {
556         free( psz_arg );
557         msg_Err( p_access, "cannot parse passive mode response" );
558         return VLC_EGENERIC;
559     }
560
561     psz_ip = p_sys->psz_epsv_ip;
562     if( psz_ip != NULL )
563     {
564         char psz_fmt[7] = "(|||%u";
565         psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];
566
567         if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
568         {
569             free( psz_arg );
570             msg_Err( p_access, "cannot parse passive mode response" );
571             return VLC_EGENERIC;
572         }
573     }
574     else
575     {
576         if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
577                       &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
578          || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
579         {
580             free( psz_arg );
581             msg_Err( p_access, "cannot parse passive mode response" );
582             return VLC_EGENERIC;
583         }
584
585         sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
586         psz_ip = psz_ipv4;
587         i_port = (p1 << 8) | p2;
588     }
589     free( psz_arg );
590
591     msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
592
593     if( ftp_SendCommand( p_access, "TYPE I" ) < 0 ||
594         ftp_ReadCommand( p_access, &i_answer, NULL ) != 2 )
595     {
596         msg_Err( p_access, "cannot set binary transfer mode" );
597         return VLC_EGENERIC;
598     }
599
600     if( i_start > 0 )
601     {
602         if( ftp_SendCommand( p_access, "REST "I64Fu, i_start ) < 0 ||
603             ftp_ReadCommand( p_access, &i_answer, NULL ) > 3 )
604         {
605             msg_Err( p_access, "cannot set restart point" );
606             return VLC_EGENERIC;
607         }
608     }
609
610     msg_Dbg( p_access, "waiting for data connection..." );
611     p_sys->fd_data = net_OpenTCP( p_access, psz_ip, i_port );
612     if( p_sys->fd_data < 0 )
613     {
614         msg_Err( p_access, "failed to connect with server" );
615         return VLC_EGENERIC;
616     }
617     msg_Dbg( p_access, "connection with \"%s:%d\" successful",
618              psz_ip, i_port );
619
620     /* "1xx" message */
621     if( ftp_SendCommand( p_access, "RETR %s", p_sys->url.psz_path ) < 0 ||
622         ftp_ReadCommand( p_access, &i_answer, NULL ) > 2 )
623     {
624         msg_Err( p_access, "cannot retreive file" );
625         return VLC_EGENERIC;
626     }
627     return VLC_SUCCESS;
628 }
629
630 static int ftp_StopStream ( access_t *p_access )
631 {
632     access_sys_t *p_sys = p_access->p_sys;
633
634     int i_answer;
635
636     if( ftp_SendCommand( p_access, "ABOR" ) < 0 )
637     {
638         msg_Warn( p_access, "cannot abord file" );
639         if(  p_sys->fd_data > 0 )
640             net_Close( p_sys->fd_data );
641         p_sys->fd_data = -1;
642         return VLC_EGENERIC;
643     }
644     if(  p_sys->fd_data > 0 )
645     {
646         net_Close( p_sys->fd_data );
647         p_sys->fd_data = -1;
648         ftp_ReadCommand( p_access, &i_answer, NULL );
649     }
650     ftp_ReadCommand( p_access, &i_answer, NULL );
651
652     return VLC_SUCCESS;
653 }
654