access: dtv: simplify/regroup setup and drop delsys
[vlc.git] / modules / access / sftp.c
1 /*****************************************************************************
2  * sftp.c: SFTP input module
3  *****************************************************************************
4  * Copyright (C) 2009 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: RĂ©mi Duraffort <ivoire@videolan.org>
8  *          Petri Hintukainen <phintuka@gmail.com>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34
35 #include <assert.h>
36
37 #include <vlc_access.h>
38 #include <vlc_input_item.h>
39 #include <vlc_network.h>
40 #include <vlc_url.h>
41 #include <vlc_keystore.h>
42
43 #include <libssh2.h>
44 #include <libssh2_sftp.h>
45
46
47 /*****************************************************************************
48  * Module descriptor
49  *****************************************************************************/
50 static int  Open ( vlc_object_t* );
51 static void Close( vlc_object_t* );
52
53 #define PORT_TEXT N_("SFTP port")
54 #define PORT_LONGTEXT N_("SFTP port number to use on the server")
55 #define USER_TEXT N_("Username")
56 #define USER_LONGTEXT N_("Username that will be used for the connection, " \
57         "if no username is set in the URL.")
58 #define PASS_TEXT N_("Password")
59 #define PASS_LONGTEXT N_("Password that will be used for the connection, " \
60         "if no username or password are set in URL.")
61
62 vlc_module_begin ()
63     set_shortname( "SFTP" )
64     set_description( N_("SFTP input") )
65     set_capability( "access", 0 )
66     set_category( CAT_INPUT )
67     set_subcategory( SUBCAT_INPUT_ACCESS )
68     add_integer( "sftp-port", 22, PORT_TEXT, PORT_LONGTEXT, true )
69     add_string( "sftp-user", NULL, USER_TEXT, USER_LONGTEXT, false )
70     add_password( "sftp-pwd", NULL, PASS_TEXT, PASS_LONGTEXT, false )
71     add_shortcut( "sftp" )
72     set_callbacks( Open, Close )
73 vlc_module_end ()
74
75
76 /*****************************************************************************
77  * Local prototypes
78  *****************************************************************************/
79 static ssize_t  Read( access_t *, uint8_t *, size_t );
80 static int      Seek( access_t *, uint64_t );
81 static int      Control( access_t *, int, va_list );
82
83 static input_item_t* DirRead( access_t *p_access );
84 static int DirControl( access_t *, int, va_list );
85
86 struct access_sys_t
87 {
88     int i_socket;
89     LIBSSH2_SESSION* ssh_session;
90     LIBSSH2_SFTP* sftp_session;
91     LIBSSH2_SFTP_HANDLE* file;
92     uint64_t filesize;
93     char *psz_base_url;
94 };
95
96
97 static int AuthPublicKey( access_t *p_access, const char *psz_home, const char *psz_username )
98 {
99     access_sys_t* p_sys = p_access->p_sys;
100     int i_result = VLC_EGENERIC;
101     char *psz_keyfile1 = NULL;
102     char *psz_keyfile2 = NULL;
103
104     if( !psz_username || !psz_username[0] )
105         return i_result;
106
107     if( asprintf( &psz_keyfile1, "%s/.ssh/id_rsa.pub", psz_home ) == -1 ||
108         asprintf( &psz_keyfile2, "%s/.ssh/id_rsa",     psz_home ) == -1 )
109         goto bailout;
110
111     if( libssh2_userauth_publickey_fromfile( p_sys->ssh_session, psz_username, psz_keyfile1, psz_keyfile2, NULL ) )
112     {
113         msg_Dbg( p_access, "Public key authentication failed" );
114         goto bailout;
115     }
116
117     msg_Info( p_access, "Public key authentication succeeded" );
118     i_result = VLC_SUCCESS;
119
120  bailout:
121     free( psz_keyfile1 );
122     free( psz_keyfile2 );
123     return i_result;
124 }
125
126 /**
127  * Connect to the sftp server and ask for a file
128  * @param p_this: the vlc_object
129  * @return VLC_SUCCESS if everything was fine
130  */
131 static int Open( vlc_object_t* p_this )
132 {
133     access_t*   p_access = (access_t*)p_this;
134     access_sys_t* p_sys;
135     vlc_url_t credential_url;
136     vlc_credential credential;
137     const char* psz_path;
138     char* psz_remote_home = NULL;
139     char* psz_home = NULL;
140     int i_port;
141     int i_ret;
142     vlc_url_t url;
143     size_t i_len;
144     int i_type;
145     int i_result = VLC_EGENERIC;
146
147     if( !p_access->psz_location )
148         return VLC_EGENERIC;
149
150     access_InitFields( p_access );
151     p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
152     if( !p_sys ) return VLC_ENOMEM;
153
154     p_sys->i_socket = -1;
155
156     vlc_UrlParse( &credential_url, p_access->psz_url );
157     vlc_credential_init( &credential, &credential_url );
158
159     /* Parse the URL */
160     vlc_UrlParse( &url, p_access->psz_location );
161     vlc_uri_decode( url.psz_path );
162
163     /* Check for some parameters */
164     if( EMPTY_STR( url.psz_host ) )
165     {
166         msg_Err( p_access, "You might give a non empty host" );
167         goto error;
168     }
169
170     if( url.i_port <= 0 )
171         i_port = var_InheritInteger( p_access, "sftp-port" );
172     else
173         i_port = url.i_port;
174
175
176     /* Connect to the server using a regular socket */
177     p_sys->i_socket = net_Connect( p_access, url.psz_host, i_port, SOCK_STREAM, 0 );
178     if( p_sys->i_socket < 0 )
179     {
180         msg_Err( p_access, "Impossible to open the connection to %s:%i", url.psz_host, i_port );
181         goto error;
182     }
183
184     /* Create the ssh connexion and wait until the server answer */
185     if( ( p_sys->ssh_session = libssh2_session_init() ) == NULL )
186         goto error;
187
188     while( ( i_ret = libssh2_session_startup( p_sys->ssh_session,
189                                               p_sys->i_socket ) )
190            == LIBSSH2_ERROR_EAGAIN );
191
192     if( i_ret != 0 )
193     {
194         msg_Err( p_access, "Impossible to open the connection to %s:%i", url.psz_host, i_port );
195         goto error;
196     }
197
198     /* Set the socket in non-blocking mode */
199     libssh2_session_set_blocking( p_sys->ssh_session, 1 );
200
201     /* List the know hosts */
202     LIBSSH2_KNOWNHOSTS *ssh_knownhosts = libssh2_knownhost_init( p_sys->ssh_session );
203     if( !ssh_knownhosts )
204         goto error;
205
206     psz_home = config_GetUserDir( VLC_HOME_DIR );
207     char *psz_knownhosts_file;
208     if( asprintf( &psz_knownhosts_file, "%s/.ssh/known_hosts", psz_home ) != -1 )
209     {
210         libssh2_knownhost_readfile( ssh_knownhosts, psz_knownhosts_file,
211                 LIBSSH2_KNOWNHOST_FILE_OPENSSH );
212         free( psz_knownhosts_file );
213     }
214
215     const char *fingerprint = libssh2_session_hostkey( p_sys->ssh_session, &i_len, &i_type );
216     struct libssh2_knownhost *host;
217     int check = libssh2_knownhost_check( ssh_knownhosts, url.psz_host,
218                                          fingerprint, i_len,
219                                          LIBSSH2_KNOWNHOST_TYPE_PLAIN |
220                                          LIBSSH2_KNOWNHOST_KEYENC_RAW,
221                                          &host );
222
223     libssh2_knownhost_free( ssh_knownhosts );
224
225     /* Check that it does match or at least that the host is unknown */
226     switch(check)
227     {
228     case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
229     case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
230         msg_Dbg( p_access, "Unable to check the remote host" );
231         break;
232     case LIBSSH2_KNOWNHOST_CHECK_MATCH:
233         msg_Dbg( p_access, "Succesfuly matched the host" );
234         break;
235     case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
236         msg_Err( p_access, "The host does not match !! The remote key changed !!" );
237         goto error;
238     }
239
240     //TODO: ask for the available auth methods
241
242     /* Try public key auth first */
243     if( AuthPublicKey( p_access, psz_home, url.psz_username ) != VLC_SUCCESS )
244     {
245     while( vlc_credential_get( &credential, p_access, "sftp-user", "sftp-pwd",
246                                _("SFTP authentication"),
247                                _("Please enter a valid login and password for "
248                                "the sftp connexion to %s"), url.psz_host ) )
249     {
250         /* send the login/password */
251         if( libssh2_userauth_password( p_sys->ssh_session,
252                                        credential.psz_username,
253                                        credential.psz_password ) == 0 )
254         {
255             vlc_credential_store( &credential, p_access );
256             break;
257         }
258
259         if( AuthPublicKey( p_access, psz_home, credential.psz_username ) == VLC_SUCCESS )
260             break;
261
262         msg_Warn( p_access, "sftp auth failed for %s", credential.psz_username );
263     }
264     }
265
266     /* Create the sftp session */
267     p_sys->sftp_session = libssh2_sftp_init( p_sys->ssh_session );
268
269     if( !p_sys->sftp_session )
270     {
271         msg_Err( p_access, "Unable to initialize the SFTP session" );
272         goto error;
273     }
274
275     /* No path, default to user Home */
276     if( !url.psz_path )
277     {
278         const size_t i_size = 1024;
279         int i_ret;
280
281         psz_remote_home = malloc( i_size );
282         if( !psz_remote_home )
283             goto error;
284
285         i_ret = libssh2_sftp_symlink_ex( p_sys->sftp_session, ".", 1,
286                                          psz_remote_home, i_size - 1,
287                                          LIBSSH2_SFTP_REALPATH );
288         if( i_ret <= 0 )
289         {
290             msg_Err( p_access, "Impossible to get the Home directory" );
291             goto error;
292         }
293         psz_remote_home[i_ret] = '\0';
294         psz_path = psz_remote_home;
295
296         /* store base url for directory read */
297         char *base = vlc_path2uri( psz_path, "sftp" );
298         if( !base )
299             goto error;
300         if( -1 == asprintf( &p_sys->psz_base_url, "sftp://%s%s", p_access->psz_location, base + 7 ) )
301         {
302             free( base );
303             goto error;
304         }
305         free( base );
306     }
307     else
308         psz_path = url.psz_path;
309
310     /* Get some information */
311     LIBSSH2_SFTP_ATTRIBUTES attributes;
312     if( libssh2_sftp_stat( p_sys->sftp_session, psz_path, &attributes ) )
313     {
314         msg_Err( p_access, "Impossible to get information about the remote path %s", psz_path );
315         goto error;
316     }
317
318     if( !LIBSSH2_SFTP_S_ISDIR( attributes.permissions ))
319     {
320         /* Open the given file */
321         p_sys->file = libssh2_sftp_open( p_sys->sftp_session, psz_path, LIBSSH2_FXF_READ, 0 );
322         p_sys->filesize = attributes.filesize;
323
324         ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
325     }
326     else
327     {
328         /* Open the given directory */
329         p_sys->file = libssh2_sftp_opendir( p_sys->sftp_session, psz_path );
330
331         p_access->pf_readdir = DirRead;
332         p_access->pf_control = DirControl;
333
334         if( !p_sys->psz_base_url )
335         {
336             if( asprintf( &p_sys->psz_base_url, "sftp://%s", p_access->psz_location ) == -1 )
337                 goto error;
338
339             /* trim trailing '/' */
340             size_t len = strlen( p_sys->psz_base_url );
341             if( len > 0 && p_sys->psz_base_url[ len - 1 ] == '/' )
342                 p_sys->psz_base_url[ len - 1 ] = 0;
343         }
344     }
345
346     if( !p_sys->file )
347     {
348         msg_Err( p_access, "Unable to open the remote path %s", psz_path );
349         goto error;
350     }
351
352     i_result = VLC_SUCCESS;
353
354 error:
355     free( psz_home );
356     free( psz_remote_home );
357     vlc_UrlClean( &url );
358     vlc_credential_clean( &credential );
359     vlc_UrlClean( &credential_url );
360     if( i_result != VLC_SUCCESS ) {
361         Close( p_this );
362     }
363     return i_result;
364 }
365
366
367 /* Close: quit the module */
368 static void Close( vlc_object_t* p_this )
369 {
370     access_t*   p_access = (access_t*)p_this;
371     access_sys_t* p_sys = p_access->p_sys;
372
373     if( p_sys->file )
374         libssh2_sftp_close_handle( p_sys->file );
375     if( p_sys->sftp_session )
376         libssh2_sftp_shutdown( p_sys->sftp_session );
377     if( p_sys->ssh_session )
378         libssh2_session_free( p_sys->ssh_session );
379     if( p_sys->i_socket >= 0 )
380         net_Close( p_sys->i_socket );
381
382     free( p_sys->psz_base_url );
383     free( p_sys );
384 }
385
386
387 static ssize_t Read( access_t *p_access, uint8_t *buf, size_t len )
388 {
389     access_sys_t *p_sys = p_access->p_sys;
390
391     if( p_access->info.b_eof )
392         return 0;
393
394     ssize_t val = libssh2_sftp_read(  p_sys->file, (char *)buf, len );
395     if( val < 0 )
396     {
397         p_access->info.b_eof = true;
398         msg_Err( p_access, "read failed" );
399         return 0;
400     }
401     else if( val == 0 )
402         p_access->info.b_eof = true;
403
404     return val;
405 }
406
407
408 static int Seek( access_t* p_access, uint64_t i_pos )
409 {
410     p_access->info.b_eof = false;
411
412     libssh2_sftp_seek( p_access->p_sys->file, i_pos );
413     return VLC_SUCCESS;
414 }
415
416
417 static int Control( access_t* p_access, int i_query, va_list args )
418 {
419     bool*       pb_bool;
420     int64_t*    pi_64;
421
422     switch( i_query )
423     {
424     case ACCESS_CAN_SEEK:
425         pb_bool = (bool*)va_arg( args, bool* );
426         *pb_bool = true;
427         break;
428
429     case ACCESS_CAN_FASTSEEK:
430         pb_bool = (bool*)va_arg( args, bool* );
431         *pb_bool = false;
432         break;
433
434     case ACCESS_CAN_PAUSE:
435     case ACCESS_CAN_CONTROL_PACE:
436         pb_bool = (bool*)va_arg( args, bool* );
437         *pb_bool = true;
438         break;
439
440     case ACCESS_GET_SIZE:
441         if( p_access->pf_readdir != NULL )
442             return VLC_EGENERIC;
443         *va_arg( args, uint64_t * ) = p_access->p_sys->filesize;
444         break;
445
446     case ACCESS_GET_PTS_DELAY:
447         pi_64 = (int64_t*)va_arg( args, int64_t* );
448         *pi_64 = INT64_C(1000)
449                * var_InheritInteger( p_access, "network-caching" );
450         break;
451
452     case ACCESS_SET_PAUSE_STATE:
453         break;
454
455     default:
456         return VLC_EGENERIC;
457     }
458
459     return VLC_SUCCESS;
460 }
461
462
463 /*****************************************************************************
464  * Directory access
465  *****************************************************************************/
466
467 static input_item_t* DirRead( access_t *p_access )
468 {
469     access_sys_t *p_sys = p_access->p_sys;
470     LIBSSH2_SFTP_ATTRIBUTES attrs;
471     input_item_t *p_item = NULL;
472     int err;
473     /* Allocate 1024 bytes for file name. Longer names are skipped.
474      * libssh2 does not support seeking in directory streams.
475      * Retrying with larger buffer is possible, but would require
476      * re-opening the directory stream.
477      */
478     size_t i_size = 1024;
479     char *psz_file = malloc( i_size );
480
481     if( !psz_file )
482         return NULL;
483
484     while( !p_item && 0 != ( err = libssh2_sftp_readdir( p_sys->file, psz_file, i_size, &attrs ) ) )
485     {
486         if( err < 0 )
487         {
488             if( err == LIBSSH2_ERROR_BUFFER_TOO_SMALL )
489             {
490                 /* seeking back is not possible ... */
491                 msg_Dbg( p_access, "skipped too long file name" );
492                 continue;
493             }
494             if( err == LIBSSH2_ERROR_EAGAIN )
495             {
496                 continue;
497             }
498             msg_Err( p_access, "directory read failed" );
499             break;
500         }
501
502         if( psz_file[0] == '.' )
503         {
504             continue;
505         }
506
507         /* Create an input item for the current entry */
508
509         char *psz_full_uri, *psz_uri;
510
511         psz_uri = vlc_uri_encode( psz_file );
512         if( psz_uri == NULL )
513             continue;
514
515         if( asprintf( &psz_full_uri, "%s/%s", p_sys->psz_base_url, psz_uri ) == -1 )
516         {
517             free( psz_uri );
518             continue;
519         }
520         free( psz_uri );
521
522         int i_type = LIBSSH2_SFTP_S_ISDIR( attrs.permissions ) ? ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
523         p_item = input_item_NewExt( psz_full_uri, psz_file, -1, i_type, ITEM_NET );
524         free( psz_full_uri );
525
526         if( p_item == NULL )
527         {
528             break;
529         }
530     }
531
532     free( psz_file );
533     return p_item;
534 }
535
536 static int DirControl( access_t *p_access, int i_query, va_list args )
537 {
538     switch( i_query )
539     {
540     case ACCESS_IS_DIRECTORY:
541         *va_arg( args, bool * ) = false; /* is not sorted */
542         *va_arg( args, bool * ) = true; /* might loop */
543         break;
544     default:
545         return access_vaDirectoryControlHelper( p_access, i_query, args );
546     }
547
548     return VLC_SUCCESS;
549 }