url: remove vlc_UrlParse() option parameter
[vlc.git] / modules / access / ftp.c
1 /*****************************************************************************
2  * ftp.c: FTP input module
3  *****************************************************************************
4  * Copyright (C) 2001-2006 VLC authors and VideoLAN
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 it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <assert.h>
34 #include <errno.h>
35
36 #include <vlc_common.h>
37 #include <vlc_plugin.h>
38 #include <vlc_access.h>
39 #include <vlc_dialog.h>
40 #include <vlc_input_item.h>
41 #include <vlc_network.h>
42 #include <vlc_url.h>
43 #include <vlc_tls.h>
44 #include <vlc_sout.h>
45 #include <vlc_charset.h>
46 #include <vlc_interrupt.h>
47
48 #ifndef IPPORT_FTP
49 # define IPPORT_FTP 21u
50 #endif
51
52 #ifndef IPPORT_FTPS
53 # define IPPORT_FTPS 990u
54 #endif
55
56 /*****************************************************************************
57  * Module descriptor
58  *****************************************************************************/
59 static int   InOpen ( vlc_object_t * );
60 static void  InClose( vlc_object_t * );
61 #ifdef ENABLE_SOUT
62 static int  OutOpen ( vlc_object_t * );
63 static void OutClose( vlc_object_t * );
64 #endif
65
66 #define USER_TEXT N_("Username")
67 #define USER_LONGTEXT N_("Username that will be used for the connection, " \
68         "if no username is set in the URL.")
69 #define PASS_TEXT N_("Password")
70 #define PASS_LONGTEXT N_("Password that will be used for the connection, " \
71         "if no username or password are set in URL.")
72 #define ACCOUNT_TEXT N_("FTP account")
73 #define ACCOUNT_LONGTEXT N_("Account that will be " \
74     "used for the connection.")
75
76 vlc_module_begin ()
77     set_shortname( "FTP" )
78     set_description( N_("FTP input") )
79     set_capability( "access", 0 )
80     set_category( CAT_INPUT )
81     set_subcategory( SUBCAT_INPUT_ACCESS )
82     add_string( "ftp-user", "anonymous", USER_TEXT, USER_LONGTEXT,
83                 false )
84     add_string( "ftp-pwd", "anonymous@example.com", PASS_TEXT,
85                 PASS_LONGTEXT, false )
86     add_string( "ftp-account", "anonymous", ACCOUNT_TEXT,
87                 ACCOUNT_LONGTEXT, false )
88     add_shortcut( "ftp", "ftps", "ftpes" )
89     set_callbacks( InOpen, InClose )
90
91 #ifdef ENABLE_SOUT
92     add_submodule ()
93         set_shortname( "FTP" )
94         set_description( N_("FTP upload output") )
95         set_capability( "sout access", 0 )
96         set_category( CAT_SOUT )
97         set_subcategory( SUBCAT_SOUT_ACO )
98         add_shortcut( "ftp", "ftps", "ftpes" )
99         set_callbacks( OutOpen, OutClose )
100 #endif
101 vlc_module_end ()
102
103 /*****************************************************************************
104  * Local prototypes
105  *****************************************************************************/
106 static ssize_t Read( access_t *, uint8_t *, size_t );
107 static int Seek( access_t *, uint64_t );
108 static int Control( access_t *, int, va_list );
109 static input_item_t* DirRead( access_t * );
110 #ifdef ENABLE_SOUT
111 static int OutSeek( sout_access_out_t *, off_t );
112 static ssize_t Write( sout_access_out_t *, block_t * );
113 #endif
114
115 static void FeaturesCheck( void *, const char * );
116
117 typedef struct ftp_features_t
118 {
119     bool b_unicode;
120     bool b_authtls;
121 } ftp_features_t;
122
123 enum tls_mode_e
124 {
125     NONE = 0,
126     IMPLICIT,/* ftps */
127     EXPLICIT /* ftpes */
128 };
129
130 struct access_sys_t
131 {
132     vlc_url_t  url;
133
134     ftp_features_t   features;
135     vlc_tls_creds_t *p_creds;
136     enum tls_mode_e  tlsmode;
137     struct
138     {
139         vlc_tls_t   *p_tls;
140         int          fd;
141     } cmd, data;
142
143     char       sz_epsv_ip[NI_MAXNUMERICHOST];
144     bool       out;
145     uint64_t   size;
146 };
147 #define GET_OUT_SYS( p_this ) \
148     ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))
149
150 static int ftp_SendCommand( vlc_object_t *obj, access_sys_t *sys,
151                             const char *fmt, ... )
152 {
153     size_t fmtlen = strlen( fmt );
154     char fmtbuf[fmtlen + 3];
155
156     memcpy( fmtbuf, fmt, fmtlen );
157     memcpy( fmtbuf + fmtlen, "\r\n", 3 );
158
159     va_list args;
160     char *cmd;
161     int val;
162
163     va_start( args, fmt );
164     val = vasprintf( &cmd, fmtbuf, args );
165     va_end( args );
166     if( unlikely(val == -1) )
167         return -1;
168
169     msg_Dbg( obj, "sending request: \"%.*s\" (%d bytes)", val - 2, cmd, val );
170     if( ((sys->cmd.p_tls != NULL)
171             ? vlc_tls_Write( sys->cmd.p_tls, cmd, val )
172             : net_Write( obj, sys->cmd.fd, cmd, val )) != val )
173     {
174         msg_Err( obj, "request failure" );
175         val = -1;
176     }
177     else
178         val = 0;
179     free( cmd );
180     return val;
181 }
182
183 /* TODO support this s**t :
184  RFC 959 allows the client to send certain TELNET strings at any moment,
185  even in the middle of a request:
186
187  * \377\377.
188  * \377\376x where x is one byte.
189  * \377\375x where x is one byte. The server is obliged to send \377\374x
190  *                                immediately after reading x.
191  * \377\374x where x is one byte.
192  * \377\373x where x is one byte. The server is obliged to send \377\376x
193  *                                immediately after reading x.
194  * \377x for any other byte x.
195
196  These strings are not part of the requests, except in the case \377\377,
197  where the request contains one \377. */
198 static int ftp_RecvAnswer( vlc_object_t *obj, access_sys_t *sys,
199                            int *restrict codep, char **restrict strp,
200                            void (*cb)(void *, const char *), void *opaque )
201 {
202     if( codep != NULL )
203         *codep = 500;
204     if( strp != NULL )
205         *strp = NULL;
206
207     char *resp;
208     if( sys->cmd.p_tls != NULL )
209         resp = vlc_tls_GetLine( sys->cmd.p_tls );
210     else
211         resp = net_Gets( obj, sys->cmd.fd );
212     if( resp == NULL )
213     {
214         msg_Err( obj, "response failure" );
215         goto error;
216     }
217
218     char *end;
219     unsigned code = strtoul( resp, &end, 10 );
220     if( (end - resp) != 3 || (*end != '-' && *end != ' ') )
221     {
222         msg_Err( obj, "malformatted response" );
223         goto error;
224     }
225     msg_Dbg( obj, "received response: \"%s\"", resp );
226
227     if( *end == '-' ) /* Multi-line response */
228     {
229         bool done;
230
231         *end = ' ';
232         do
233         {
234             char *line;
235             if( sys->cmd.p_tls != NULL )
236                 line = vlc_tls_GetLine( sys->cmd.p_tls );
237             else
238                 line = net_Gets( obj, sys->cmd.fd );
239             if( line == NULL )
240             {
241                 msg_Err( obj, "response failure" );
242                 goto error;
243             }
244
245             done = !strncmp( resp, line, 4 );
246             if( !done )
247                 cb( opaque, line );
248             free( line );
249         }
250         while( !done );
251     }
252
253     if( codep != NULL )
254         *codep = code;
255     if( strp != NULL )
256         *strp = resp;
257     else
258         free( resp );
259     return code / 100;
260 error:
261     free( resp );
262     return -1;
263 }
264
265 static void DummyLine( void *data, const char *str )
266 {
267     (void) data; (void) str;
268 }
269
270 static int ftp_RecvCommand( vlc_object_t *obj, access_sys_t *sys,
271                             int *restrict codep, char **restrict strp )
272 {
273     return ftp_RecvAnswer( obj, sys, codep, strp, DummyLine, NULL );
274 }
275
276 static int ftp_StartStream( vlc_object_t *, access_sys_t *, uint64_t, bool );
277 static int ftp_StopStream ( vlc_object_t *, access_sys_t * );
278
279 static void readTLSMode( access_sys_t *p_sys, const char * psz_access )
280 {
281     if ( !strncmp( psz_access, "ftps", 4 ) )
282         p_sys->tlsmode = IMPLICIT;
283     else
284     if ( !strncmp( psz_access, "ftpes", 5 ) )
285         p_sys->tlsmode = EXPLICIT;
286     else
287         p_sys->tlsmode = NONE;
288 }
289
290 static int createCmdTLS( vlc_object_t *p_access, access_sys_t *p_sys, int fd,
291                          const char *psz_session_name )
292 {
293     p_sys->p_creds = vlc_tls_ClientCreate( p_access );
294     if( p_sys->p_creds == NULL ) return -1;
295
296     /* TLS/SSL handshake */
297     p_sys->cmd.p_tls = vlc_tls_ClientSessionCreate( p_sys->p_creds, fd,
298                                                     p_sys->url.psz_host,
299                                                     psz_session_name,
300                                                     NULL, NULL );
301     if( p_sys->cmd.p_tls == NULL )
302     {
303         msg_Err( p_access, "cannot establish FTP/TLS session on command channel" );
304         return -1;
305     }
306
307     return 0;
308 }
309
310 static void clearCmdTLS( access_sys_t *p_sys )
311 {
312     if ( p_sys->cmd.p_tls ) vlc_tls_SessionDelete( p_sys->cmd.p_tls );
313     if ( p_sys->p_creds ) vlc_tls_Delete( p_sys->p_creds );
314     p_sys->cmd.p_tls = NULL;
315     p_sys->p_creds = NULL;
316 }
317
318 static int Login( vlc_object_t *p_access, access_sys_t *p_sys )
319 {
320     int i_answer;
321
322     /* *** Open a TCP connection with server *** */
323     int fd = p_sys->cmd.fd = net_ConnectTCP( p_access, p_sys->url.psz_host,
324                                              p_sys->url.i_port );
325     if( fd == -1 )
326     {
327         msg_Err( p_access, "connection failed" );
328         dialog_Fatal( p_access, _("Network interaction failed"), "%s",
329                         _("VLC could not connect with the given server.") );
330         return -1;
331     }
332
333     if ( p_sys->tlsmode == IMPLICIT ) /* FTPS Mode */
334     {
335         if ( createCmdTLS( p_access, p_sys, fd, "ftps") < 0 )
336             goto error;
337     }
338
339     while( ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) == 1 );
340
341     if( i_answer / 100 != 2 )
342     {
343         msg_Err( p_access, "connection rejected" );
344         dialog_Fatal( p_access, _("Network interaction failed"), "%s",
345                         _("VLC's connection to the given server was rejected.") );
346         return -1;
347     }
348
349     msg_Dbg( p_access, "connection accepted (%d)", i_answer );
350
351     /* Features check first */
352     if( ftp_SendCommand( p_access, p_sys, "FEAT" ) < 0
353      || ftp_RecvAnswer( p_access, p_sys, NULL, NULL,
354                         FeaturesCheck, &p_sys->features ) < 0 )
355     {
356          msg_Err( p_access, "cannot get server features" );
357          return -1;
358     }
359
360     /* Create TLS Session */
361     if( p_sys->tlsmode == EXPLICIT )
362     {
363         if ( ! p_sys->features.b_authtls )
364         {
365             msg_Err( p_access, "Server does not support TLS" );
366             return -1;
367         }
368
369         if( ftp_SendCommand( p_access, p_sys, "AUTH TLS" ) < 0
370          || ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0
371          || i_answer != 234 )
372         {
373              msg_Err( p_access, "cannot switch to TLS: server replied with code %d",
374                       i_answer );
375              return -1;
376         }
377
378         if ( createCmdTLS( p_access, p_sys, fd, "ftpes") < 0 )
379         {
380             goto error;
381         }
382     }
383
384     if( p_sys->tlsmode != NONE )
385     {
386         if( ftp_SendCommand( p_access, p_sys, "PBSZ 0" ) < 0 ||
387             ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
388             i_answer != 200 )
389         {
390             msg_Err( p_access, "Can't truncate Protection buffer size for TLS" );
391             goto error;
392         }
393
394         if( ftp_SendCommand( p_access, p_sys, "PROT P" ) < 0 ||
395             ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
396             i_answer != 200 )
397         {
398             msg_Err( p_access, "Can't set Data channel protection" );
399             goto error;
400         }
401     }
402
403     /* Send credentials over channel */
404     char *psz;
405
406     if( p_sys->url.psz_username && *p_sys->url.psz_username )
407         psz = strdup( p_sys->url.psz_username );
408     else
409         psz = var_InheritString( p_access, "ftp-user" );
410     if( !psz )
411         goto error;
412
413     if( ftp_SendCommand( p_access, p_sys, "USER %s", psz ) < 0 ||
414         ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
415     {
416         free( psz );
417         goto error;
418     }
419     free( psz );
420
421     switch( i_answer / 100 )
422     {
423         case 2:
424             /* X.509 auth successful after AUTH TLS / RFC 2228 sec. 4 */
425             if ( i_answer == 232 )
426                 msg_Dbg( p_access, "user accepted and authenticated" );
427             else
428                 msg_Dbg( p_access, "user accepted" );
429             break;
430         case 3:
431             msg_Dbg( p_access, "password needed" );
432             if( p_sys->url.psz_password && *p_sys->url.psz_password )
433                 psz = strdup( p_sys->url.psz_password );
434             else
435                 psz = var_InheritString( p_access, "ftp-pwd" );
436             if( !psz )
437                 goto error;
438
439             if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz ) < 0 ||
440                 ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
441             {
442                 free( psz );
443                 goto error;
444             }
445             free( psz );
446
447             switch( i_answer / 100 )
448             {
449                 case 2:
450                     msg_Dbg( p_access, "password accepted" );
451                     break;
452                 case 3:
453                     msg_Dbg( p_access, "account needed" );
454                     psz = var_InheritString( p_access, "ftp-account" );
455                     if( ftp_SendCommand( p_access, p_sys, "ACCT %s",
456                                          psz ) < 0 ||
457                         ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
458                     {
459                         free( psz );
460                         goto error;
461                     }
462                     free( psz );
463
464                     if( i_answer / 100 != 2 )
465                     {
466                         msg_Err( p_access, "account rejected" );
467                         dialog_Fatal( p_access,
468                                       _("Network interaction failed"),
469                                       "%s", _("Your account was rejected.") );
470                         goto error;
471                     }
472                     msg_Dbg( p_access, "account accepted" );
473                     break;
474
475                 default:
476                     msg_Err( p_access, "password rejected" );
477                     dialog_Fatal( p_access, _("Network interaction failed"),
478                                   "%s",  _("Your password was rejected.") );
479                     goto error;
480             }
481             break;
482         default:
483             msg_Err( p_access, "user rejected" );
484             dialog_Fatal( p_access, _("Network interaction failed"), "%s",
485                         _("Your connection attempt to the server was rejected.") );
486             goto error;
487     }
488
489     return 0;
490
491 error:
492     clearCmdTLS( p_sys );
493     return -1;
494 }
495
496 static void FeaturesCheck( void *opaque, const char *feature )
497 {
498     ftp_features_t *features = opaque;
499
500     if( strcasestr( feature, "UTF8" ) != NULL )
501         features->b_unicode = true;
502     else
503     if( strcasestr( feature, "AUTH TLS" ) != NULL )
504         features->b_authtls = true;
505 }
506
507 static const char *IsASCII( const char *str )
508 {
509     int8_t c;
510     for( const char *p = str; (c = *p) != '\0'; p++ )
511         if( c < 0 )
512             return NULL;
513     return str;
514 }
515
516 static int Connect( vlc_object_t *p_access, access_sys_t *p_sys )
517 {
518     if( Login( p_access, p_sys ) < 0 )
519         return -1;
520
521     /* Extended passive mode */
522     if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
523     {
524         msg_Err( p_access, "cannot request extended passive mode" );
525         goto error;
526     }
527
528     if( ftp_RecvCommand( p_access, p_sys, NULL, NULL ) == 2 )
529     {
530         if( net_GetPeerAddress( p_sys->cmd.fd, p_sys->sz_epsv_ip, NULL ) )
531             goto error;
532     }
533     else
534     {
535         /* If ESPV ALL fails, we fallback to PASV.
536          * We have to restart the connection in case there is a NAT that
537          * understands EPSV ALL in the way, and hence won't allow PASV on
538          * the initial connection.
539          */
540         msg_Info( p_access, "FTP Extended passive mode disabled" );
541         clearCmdTLS( p_sys );
542         net_Close( p_sys->cmd.fd );
543
544         if( Login( p_access, p_sys ) )
545             goto error;
546     }
547
548     if( p_sys->url.psz_path &&
549         (p_sys->features.b_unicode ? IsUTF8 : IsASCII)(p_sys->url.psz_path) == NULL )
550     {
551         msg_Err( p_access, "unsupported path: \"%s\"", p_sys->url.psz_path );
552         goto error;
553     }
554
555     /* check binary mode support */
556     if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
557         ftp_RecvCommand( p_access, p_sys, NULL, NULL ) != 2 )
558     {
559         msg_Err( p_access, "cannot set binary transfer mode" );
560         goto error;
561     }
562
563     return 0;
564
565 error:
566     clearCmdTLS( p_sys );
567     net_Close( p_sys->cmd.fd );
568     return -1;
569 }
570
571
572 static int parseURL( vlc_url_t *url, const char *path, enum tls_mode_e mode )
573 {
574     if( path == NULL )
575         return VLC_EGENERIC;
576
577     /* *** Parse URL and get server addr/port and path *** */
578     while( *path == '/' )
579         path++;
580
581     vlc_UrlParse( url, path );
582
583     if( url->psz_host == NULL || *url->psz_host == '\0' )
584         return VLC_EGENERIC;
585
586     if( url->i_port <= 0 )
587     {
588         if( mode == IMPLICIT )
589             url->i_port = IPPORT_FTPS;
590         else
591             url->i_port = IPPORT_FTP; /* default port */
592     }
593
594     if( url->psz_path == NULL )
595         return VLC_SUCCESS;
596     /* FTP URLs are relative to user's default directory (RFC1738 §3.2)
597     For absolute path use ftp://foo.bar//usr/local/etc/filename */
598     /* FIXME: we should issue a series of CWD, one per slash */
599     if( url->psz_path )
600     {
601         assert( url->psz_path[0] == '/' );
602         url->psz_path++;
603     }
604
605     char *type = strstr( url->psz_path, ";type=" );
606     if( type )
607     {
608         *type = '\0';
609         if( strchr( "iI", type[6] ) == NULL )
610             return VLC_EGENERIC; /* ASCII and directory not supported */
611     }
612     decode_URI( url->psz_path );
613     return VLC_SUCCESS;
614 }
615
616
617 /****************************************************************************
618  * Open: connect to ftp server and ask for file
619  ****************************************************************************/
620 static int InOpen( vlc_object_t *p_this )
621 {
622     access_t     *p_access = (access_t*)p_this;
623     access_sys_t *p_sys;
624     char         *psz_arg;
625     bool          b_directory = false;
626
627     /* Init p_access */
628     access_InitFields( p_access );
629     p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
630     if( !p_sys )
631         return VLC_ENOMEM;
632     p_sys->data.fd = -1;
633     p_sys->out = false;
634     p_sys->size = 0;
635     readTLSMode( p_sys, p_access->psz_access );
636
637     if( parseURL( &p_sys->url, p_access->psz_location, p_sys->tlsmode ) )
638         goto exit_error;
639
640     if( Connect( p_this, p_sys ) )
641         goto exit_error;
642
643     /* get size */
644     if( p_sys->url.psz_path == NULL )
645         b_directory = true;
646     else
647     if( ftp_SendCommand( p_this, p_sys, "SIZE %s", p_sys->url.psz_path ) < 0 )
648         goto error;
649     else
650     if ( ftp_RecvCommand( p_this, p_sys, NULL, &psz_arg ) == 2 )
651     {
652         p_sys->size = atoll( &psz_arg[4] );
653         free( psz_arg );
654         msg_Dbg( p_access, "file size: %"PRIu64, p_sys->size );
655     }
656     else
657     if( ftp_SendCommand( p_this, p_sys, "CWD %s", p_sys->url.psz_path ) < 0 )
658         goto error;
659     else
660     if( ftp_RecvCommand( p_this, p_sys, NULL, NULL ) != 2 )
661     {
662         msg_Err( p_this, "file or directory does not exist" );
663         goto error;
664     }
665     else
666         b_directory = true;
667
668     if( b_directory )
669     {
670         p_access->pf_readdir = DirRead;
671         p_access->pf_control = access_vaDirectoryControlHelper;
672         p_access->info.b_dir_can_loop = true;
673     } else
674         ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek ); \
675
676     /* Start the 'stream' */
677     if( ftp_StartStream( p_this, p_sys, 0, b_directory ) < 0 )
678     {
679         msg_Err( p_this, "cannot retrieve file" );
680         clearCmdTLS( p_sys );
681         net_Close( p_sys->cmd.fd );
682         goto exit_error;
683     }
684
685     return VLC_SUCCESS;
686
687 error:
688     clearCmdTLS( p_sys );
689     net_Close( p_sys->cmd.fd );
690
691 exit_error:
692     vlc_UrlClean( &p_sys->url );
693     free( p_sys );
694     return VLC_EGENERIC;
695 }
696
697 #ifdef ENABLE_SOUT
698 static int OutOpen( vlc_object_t *p_this )
699 {
700     sout_access_out_t *p_access = (sout_access_out_t *)p_this;
701     access_sys_t      *p_sys;
702
703     p_sys = calloc( 1, sizeof( *p_sys ) );
704     if( !p_sys )
705         return VLC_ENOMEM;
706
707     /* Init p_access */
708     p_sys->data.fd = -1;
709     p_sys->out = true;
710     readTLSMode( p_sys, p_access->psz_access );
711
712     if( parseURL( &p_sys->url, p_access->psz_path, p_sys->tlsmode ) )
713         goto exit_error;
714     if( p_sys->url.psz_path == NULL )
715     {
716         msg_Err( p_this, "no filename specified" );
717         goto exit_error;
718     }
719
720     if( Connect( p_this, p_sys ) )
721         goto exit_error;
722
723     /* Start the 'stream' */
724     if( ftp_StartStream( p_this, p_sys, 0, false ) < 0 )
725     {
726         msg_Err( p_access, "cannot store file" );
727         clearCmdTLS( p_sys );
728         net_Close( p_sys->cmd.fd );
729         goto exit_error;
730     }
731
732     p_access->pf_seek = OutSeek;
733     p_access->pf_write = Write;
734     p_access->p_sys = (void *)p_sys;
735
736     return VLC_SUCCESS;
737
738 exit_error:
739     vlc_UrlClean( &p_sys->url );
740     free( p_sys );
741     return VLC_EGENERIC;
742 }
743 #endif
744
745 /*****************************************************************************
746  * Close: free unused data structures
747  *****************************************************************************/
748 static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
749 {
750     msg_Dbg( p_access, "stopping stream" );
751     ftp_StopStream( p_access, p_sys );
752
753     if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
754     {
755         msg_Warn( p_access, "cannot quit" );
756     }
757     else
758     {
759         ftp_RecvCommand( p_access, p_sys, NULL, NULL );
760     }
761
762     clearCmdTLS( p_sys );
763     net_Close( p_sys->cmd.fd );
764
765     /* free memory */
766     vlc_UrlClean( &p_sys->url );
767     free( p_sys );
768 }
769
770 static void InClose( vlc_object_t *p_this )
771 {
772     Close( p_this, ((access_t *)p_this)->p_sys);
773 }
774
775 #ifdef ENABLE_SOUT
776 static void OutClose( vlc_object_t *p_this )
777 {
778     Close( p_this, GET_OUT_SYS(p_this));
779 }
780 #endif
781
782
783 /*****************************************************************************
784  * Seek: try to go at the right place
785  *****************************************************************************/
786 static int _Seek( vlc_object_t *p_access, access_sys_t *p_sys, uint64_t i_pos )
787 {
788     msg_Dbg( p_access, "seeking to %"PRIu64, i_pos );
789
790     ftp_StopStream( (vlc_object_t *)p_access, p_sys );
791     if( ftp_StartStream( (vlc_object_t *)p_access, p_sys, i_pos, false ) < 0 )
792         return VLC_EGENERIC;
793
794     return VLC_SUCCESS;
795 }
796
797 static int Seek( access_t *p_access, uint64_t i_pos )
798 {
799     int val = _Seek( (vlc_object_t *)p_access, p_access->p_sys, i_pos );
800     if( val )
801         return val;
802
803     p_access->info.b_eof = false;
804     p_access->info.i_pos = i_pos;
805
806     return VLC_SUCCESS;
807 }
808
809 #ifdef ENABLE_SOUT
810 static int OutSeek( sout_access_out_t *p_access, off_t i_pos )
811 {
812     return _Seek( (vlc_object_t *)p_access, GET_OUT_SYS( p_access ), i_pos);
813 }
814 #endif
815
816 /*****************************************************************************
817  * Read:
818  *****************************************************************************/
819 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
820 {
821     access_sys_t *p_sys = p_access->p_sys;
822     int i_read;
823
824     assert( p_sys->data.fd != -1 );
825     assert( !p_sys->out );
826
827     if( p_access->info.b_eof )
828         return 0;
829
830     if( p_sys->data.p_tls != NULL )
831         i_read = vlc_tls_Read( p_sys->data.p_tls, p_buffer, i_len, false );
832     else
833         i_read = vlc_recv_i11e( p_sys->data.fd, p_buffer, i_len, 0 );
834
835     if( i_read == 0 )
836         p_access->info.b_eof = true;
837     else if( i_read > 0 )
838         p_access->info.i_pos += i_read;
839     else if( errno != EINTR && errno != EAGAIN )
840     {
841         msg_Err( p_access, "receive error: %s", vlc_strerror_c(errno) );
842         p_access->info.b_eof = true;
843     }
844
845     return i_read;
846 }
847
848 /*****************************************************************************
849  * DirRead:
850  *****************************************************************************/
851 static input_item_t* DirRead( access_t *p_access )
852 {
853     access_sys_t *p_sys = p_access->p_sys;
854     input_item_t *p_item = NULL;
855
856     assert( p_sys->data.fd != -1 );
857     assert( !p_sys->out );
858
859     for( ;;)
860     {
861         char *psz_line;
862         if( p_sys->data.p_tls != NULL )
863             psz_line = vlc_tls_GetLine( p_sys->data.p_tls );
864         else
865             psz_line = net_Gets( p_access, p_sys->data.fd );
866
867         char *psz_uri;
868         if( asprintf( &psz_uri, "%s://%s:%d%s%s/%s",
869                       ( p_sys->tlsmode == NONE ) ? "ftp" :
870                       ( ( p_sys->tlsmode == IMPLICIT ) ? "ftps" : "ftpes" ),
871                       p_sys->url.psz_host, p_sys->url.i_port,
872                       p_sys->url.psz_path ? "/" : "",
873                       p_sys->url.psz_path ? p_sys->url.psz_path : "",
874                       psz_line ) != -1 )
875         {
876             p_item = input_item_NewWithTypeExt( psz_uri, psz_line, 0, NULL,
877                                                 0, -1, ITEM_TYPE_UNKNOWN, 1 );
878             free( psz_uri );
879         }
880         free( psz_line );
881     }
882     return p_item;
883 }
884
885 /*****************************************************************************
886  * Write:
887  *****************************************************************************/
888 #ifdef ENABLE_SOUT
889 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
890 {
891     access_sys_t *p_sys = GET_OUT_SYS(p_access);
892     size_t i_write = 0;
893
894     assert( p_sys->data.fd != -1 );
895
896     while( p_buffer != NULL )
897     {
898         block_t *p_next = p_buffer->p_next;;
899
900         if( p_sys->data.p_tls != NULL )
901             i_write += vlc_tls_Write( p_sys->data.p_tls,
902                                       p_buffer->p_buffer, p_buffer->i_buffer );
903         else
904             i_write += net_Write( p_access, p_sys->data.fd,
905                                   p_buffer->p_buffer, p_buffer->i_buffer );
906         block_Release( p_buffer );
907
908         p_buffer = p_next;
909     }
910
911     return i_write;
912 }
913 #endif
914
915 /*****************************************************************************
916  * Control:
917  *****************************************************************************/
918 static int Control( access_t *p_access, int i_query, va_list args )
919 {
920     bool    *pb_bool;
921     int64_t *pi_64;
922
923     switch( i_query )
924     {
925         case ACCESS_CAN_SEEK:
926             pb_bool = (bool*)va_arg( args, bool* );
927             *pb_bool = true;
928             break;
929         case ACCESS_CAN_FASTSEEK:
930             pb_bool = (bool*)va_arg( args, bool* );
931             *pb_bool = false;
932             break;
933         case ACCESS_CAN_PAUSE:
934             pb_bool = (bool*)va_arg( args, bool* );
935             *pb_bool = true;    /* FIXME */
936             break;
937         case ACCESS_CAN_CONTROL_PACE:
938             pb_bool = (bool*)va_arg( args, bool* );
939             *pb_bool = true;    /* FIXME */
940             break;
941         case ACCESS_GET_SIZE:
942             *va_arg( args, uint64_t * ) = p_access->p_sys->size;
943             break;
944
945         case ACCESS_GET_PTS_DELAY:
946             pi_64 = (int64_t*)va_arg( args, int64_t * );
947             *pi_64 = INT64_C(1000)
948                    * var_InheritInteger( p_access, "network-caching" );
949             break;
950
951         case ACCESS_SET_PAUSE_STATE:
952             pb_bool = (bool*)va_arg( args, bool* );
953             if ( !pb_bool )
954               return Seek( p_access, p_access->info.i_pos );
955             break;
956
957         default:
958             return VLC_EGENERIC;
959
960     }
961     return VLC_SUCCESS;
962 }
963
964 static int ftp_StartStream( vlc_object_t *p_access, access_sys_t *p_sys,
965                             uint64_t i_start, bool b_directory )
966 {
967     char psz_ipv4[16], *psz_ip = p_sys->sz_epsv_ip;
968     int  i_answer;
969     char *psz_arg, *psz_parser;
970     int  i_port;
971
972     assert( p_sys->data.fd == -1 );
973
974     if( ( ftp_SendCommand( p_access, p_sys, *psz_ip ? "EPSV" : "PASV" ) < 0 )
975      || ( ftp_RecvCommand( p_access, p_sys, &i_answer, &psz_arg ) != 2 ) )
976     {
977         msg_Err( p_access, "cannot set passive mode" );
978         return VLC_EGENERIC;
979     }
980
981     psz_parser = strchr( psz_arg, '(' );
982     if( psz_parser == NULL )
983     {
984         free( psz_arg );
985         msg_Err( p_access, "cannot parse passive mode response" );
986         return VLC_EGENERIC;
987     }
988
989     if( *psz_ip )
990     {
991         char psz_fmt[7] = "(|||%u";
992         psz_fmt[1] = psz_fmt[2] = psz_fmt[3] = psz_parser[1];
993
994         if( sscanf( psz_parser, psz_fmt, &i_port ) < 1 )
995         {
996             free( psz_arg );
997             msg_Err( p_access, "cannot parse passive mode response" );
998             return VLC_EGENERIC;
999         }
1000     }
1001     else
1002     {
1003         unsigned a1, a2, a3, a4, p1, p2;
1004
1005         if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
1006                       &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
1007          || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
1008         {
1009             free( psz_arg );
1010             msg_Err( p_access, "cannot parse passive mode response" );
1011             return VLC_EGENERIC;
1012         }
1013
1014         sprintf( psz_ipv4, "%u.%u.%u.%u", a1, a2, a3, a4 );
1015         psz_ip = psz_ipv4;
1016         i_port = (p1 << 8) | p2;
1017     }
1018     free( psz_arg );
1019
1020     msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );
1021
1022     if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
1023         ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) != 2 )
1024     {
1025         msg_Err( p_access, "cannot set binary transfer mode" );
1026         return VLC_EGENERIC;
1027     }
1028
1029     if( i_start > 0 )
1030     {
1031         if( ftp_SendCommand( p_access, p_sys, "REST %"PRIu64, i_start ) < 0 ||
1032             ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) > 3 )
1033         {
1034             msg_Err( p_access, "cannot set restart offset" );
1035             return VLC_EGENERIC;
1036         }
1037     }
1038
1039     msg_Dbg( p_access, "waiting for data connection..." );
1040     p_sys->data.fd = net_ConnectTCP( p_access, psz_ip, i_port );
1041     if( p_sys->data.fd < 0 )
1042     {
1043         msg_Err( p_access, "failed to connect with server" );
1044         return VLC_EGENERIC;
1045     }
1046     msg_Dbg( p_access, "connection with \"%s:%d\" successful",
1047              psz_ip, i_port );
1048
1049     if( b_directory )
1050     {
1051         if( ftp_SendCommand( p_access, p_sys, "NLST" ) < 0 ||
1052             ftp_RecvCommand( p_access, p_sys, NULL, &psz_arg ) > 2 )
1053         {
1054             msg_Err( p_access, "cannot list directory contents" );
1055             return VLC_EGENERIC;
1056         }
1057     }
1058     else
1059     {
1060         /* "1xx" message */
1061         assert( p_sys->url.psz_path );
1062         if( ftp_SendCommand( p_access, p_sys, "%s %s",
1063                              p_sys->out ? "STOR" : "RETR",
1064                              p_sys->url.psz_path ) < 0
1065          || ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) > 2 )
1066         {
1067             msg_Err( p_access, "cannot retrieve file" );
1068             return VLC_EGENERIC;
1069         }
1070     }
1071
1072     if( p_sys->tlsmode != NONE )
1073     {
1074         /* FIXME: Do Reuse TLS Session */
1075         /* TLS/SSL handshake */
1076         p_sys->data.p_tls = vlc_tls_ClientSessionCreate( p_sys->p_creds,
1077                             p_sys->data.fd, p_sys->url.psz_host,
1078                             ( p_sys->tlsmode == EXPLICIT ) ? "ftpes-data"
1079                                                            : "ftps-data",
1080                                                          NULL, NULL );
1081         if( p_sys->data.p_tls == NULL )
1082         {
1083             msg_Err( p_access, "cannot establish FTP/TLS session for data" \
1084                              ": server not allowing new session ?" );
1085             return VLC_EGENERIC;
1086         }
1087     }
1088     else
1089         shutdown( p_sys->data.fd, p_sys->out ? SHUT_RD : SHUT_WR );
1090
1091     return VLC_SUCCESS;
1092 }
1093
1094 static int ftp_StopStream ( vlc_object_t *p_access, access_sys_t *p_sys )
1095 {
1096     if( ftp_SendCommand( p_access, p_sys, "ABOR" ) < 0 )
1097     {
1098         msg_Warn( p_access, "cannot abort file" );
1099         if( p_sys->data.fd > 0 )
1100         {
1101             if ( p_sys->data.p_tls ) vlc_tls_SessionDelete( p_sys->data.p_tls );
1102             net_Close( p_sys->data.fd );
1103         }
1104         p_sys->data.fd = -1;
1105         p_sys->data.p_tls = NULL;
1106         return VLC_EGENERIC;
1107     }
1108
1109     if( p_sys->data.fd != -1 )
1110     {
1111         if ( p_sys->data.p_tls ) vlc_tls_SessionDelete( p_sys->data.p_tls );
1112         net_Close( p_sys->data.fd );
1113         p_sys->data.fd = -1;
1114         p_sys->data.p_tls = NULL;
1115         /* Read the final response from RETR/STOR, i.e. 426 or 226 */
1116         ftp_RecvCommand( p_access, p_sys, NULL, NULL );
1117     }
1118     /* Read the response from ABOR, i.e. 226 or 225 */
1119     ftp_RecvCommand( p_access, p_sys, NULL, NULL );
1120
1121     return VLC_SUCCESS;
1122 }