config: remove advanced from add_password()
[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)
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( stream_t *, void *, size_t );
80 static int      Seek( stream_t *, uint64_t );
81 static int      Control( stream_t *, int, va_list );
82
83 static int DirRead( stream_t *, input_item_node_t * );
84
85 struct access_sys_t
86 {
87     int i_socket;
88     LIBSSH2_SESSION* ssh_session;
89     LIBSSH2_SFTP* sftp_session;
90     LIBSSH2_SFTP_HANDLE* file;
91     uint64_t filesize;
92     char *psz_base_url;
93 };
94
95 static int AuthKeyAgent( stream_t *p_access, const char *psz_username )
96 {
97     access_sys_t* p_sys = p_access->p_sys;
98     int i_result = VLC_EGENERIC;
99     LIBSSH2_AGENT *p_sshagent = NULL;
100     struct libssh2_agent_publickey *p_identity = NULL,
101                                    *p_prev_identity = NULL;
102
103     if( !psz_username || !psz_username[0] )
104         return i_result;
105
106     p_sshagent = libssh2_agent_init( p_sys->ssh_session );
107
108     if( !p_sshagent )
109     {
110         msg_Dbg( p_access, "Failed to initialize key agent" );
111         goto bailout;
112     }
113     if( libssh2_agent_connect( p_sshagent ) )
114     {
115         msg_Dbg( p_access, "Failed to connect key agent" );
116         goto bailout;
117     }
118     if( libssh2_agent_list_identities( p_sshagent ) )
119     {
120         msg_Dbg( p_access, "Failed to request identities" );
121         goto bailout;
122     }
123
124     while( libssh2_agent_get_identity( p_sshagent, &p_identity, p_prev_identity ) == 0 )
125     {
126         msg_Dbg( p_access, "Using key %s", p_identity->comment );
127         if( libssh2_agent_userauth( p_sshagent, psz_username, p_identity ) == 0 )
128         {
129             msg_Info( p_access, "Public key agent authentication succeeded" );
130             i_result = VLC_SUCCESS;
131             goto bailout;
132         }
133         msg_Dbg( p_access, "Public key agent authentication failed" );
134         p_prev_identity = p_identity;
135     }
136
137 bailout:
138     if( p_sshagent )
139     {
140         libssh2_agent_disconnect( p_sshagent );
141         libssh2_agent_free( p_sshagent );
142     }
143     return i_result;
144 }
145
146
147 static int AuthPublicKey( stream_t *p_access, const char *psz_home, const char *psz_username )
148 {
149     access_sys_t* p_sys = p_access->p_sys;
150     int i_result = VLC_EGENERIC;
151     char *psz_keyfile1 = NULL;
152     char *psz_keyfile2 = NULL;
153
154     if( !psz_username || !psz_username[0] )
155         return i_result;
156
157     if( asprintf( &psz_keyfile1, "%s/.ssh/id_rsa.pub", psz_home ) == -1 ||
158         asprintf( &psz_keyfile2, "%s/.ssh/id_rsa",     psz_home ) == -1 )
159         goto bailout;
160
161     if( libssh2_userauth_publickey_fromfile( p_sys->ssh_session, psz_username, psz_keyfile1, psz_keyfile2, NULL ) )
162     {
163         msg_Dbg( p_access, "Public key authentication failed" );
164         goto bailout;
165     }
166
167     msg_Info( p_access, "Public key authentication succeeded" );
168     i_result = VLC_SUCCESS;
169
170  bailout:
171     free( psz_keyfile1 );
172     free( psz_keyfile2 );
173     return i_result;
174 }
175
176 static void SSHSessionDestroy( stream_t *p_access )
177 {
178     access_sys_t* p_sys = p_access->p_sys;
179
180     if( p_sys->ssh_session )
181     {
182         libssh2_session_free( p_sys->ssh_session );
183         p_sys->ssh_session = NULL;
184     }
185     if( p_sys->i_socket >= 0 )
186     {
187         net_Close( p_sys->i_socket );
188         p_sys->i_socket = -1;
189     }
190 }
191
192 static int SSHSessionInit( stream_t *p_access, const char *psz_host, int i_port )
193 {
194     access_sys_t* p_sys = p_access->p_sys;
195
196     /* Connect to the server using a regular socket */
197     assert( p_sys->i_socket == -1 );
198     p_sys->i_socket = net_ConnectTCP( p_access, psz_host, i_port );
199     if( p_sys->i_socket < 0 )
200         goto error;
201
202     /* Create the ssh connexion and wait until the server answer */
203     p_sys->ssh_session = libssh2_session_init();
204     if( p_sys->ssh_session == NULL )
205         goto error;
206
207     int i_ret;
208     while( ( i_ret = libssh2_session_startup( p_sys->ssh_session, p_sys->i_socket ) )
209            == LIBSSH2_ERROR_EAGAIN );
210
211     if( i_ret != 0 )
212         goto error;
213
214     /* Set the socket in non-blocking mode */
215     libssh2_session_set_blocking( p_sys->ssh_session, 1 );
216     return VLC_SUCCESS;
217
218 error:
219     msg_Err( p_access, "Impossible to open the connection to %s:%i",
220              psz_host, i_port );
221     SSHSessionDestroy( p_access );
222     return VLC_EGENERIC;
223 }
224
225 /**
226  * Connect to the sftp server and ask for a file
227  * @param p_this: the vlc_object
228  * @return VLC_SUCCESS if everything was fine
229  */
230 static int Open( vlc_object_t* p_this )
231 {
232     stream_t*   p_access = (stream_t*)p_this;
233     access_sys_t* p_sys;
234     vlc_credential credential;
235     char* psz_path = NULL;
236     char *psz_session_username = NULL;
237     char* psz_home = NULL;
238     int i_port;
239     vlc_url_t url;
240     size_t i_len;
241     int i_type;
242     int i_result = VLC_EGENERIC;
243
244     if( !p_access->psz_location )
245         return VLC_EGENERIC;
246
247     p_sys = p_access->p_sys = vlc_obj_calloc( p_this, 1, sizeof( access_sys_t ) );
248     if( !p_sys ) return VLC_ENOMEM;
249
250     p_sys->i_socket = -1;
251
252     /* Parse the URL */
253     if( vlc_UrlParseFixup( &url, p_access->psz_url ) != 0 )
254     {
255         vlc_UrlClean( &url );
256         return VLC_EGENERIC;
257     }
258     vlc_credential_init( &credential, &url );
259     if( url.psz_path != NULL )
260     {
261         psz_path = vlc_uri_decode_duplicate( url.psz_path );
262         if( psz_path == NULL )
263             goto error;
264     }
265
266     /* Check for some parameters */
267     if( EMPTY_STR( url.psz_host ) )
268     {
269         msg_Err( p_access, "Unable to extract host from %s", p_access->psz_url );
270         goto error;
271     }
272
273     if( url.i_port <= 0 )
274         i_port = var_InheritInteger( p_access, "sftp-port" );
275     else
276         i_port = url.i_port;
277
278     /* Create the ssh connexion and wait until the server answer */
279     if( SSHSessionInit( p_access, url.psz_host, i_port ) != VLC_SUCCESS )
280         goto error;
281
282     /* List the know hosts */
283     LIBSSH2_KNOWNHOSTS *ssh_knownhosts = libssh2_knownhost_init( p_sys->ssh_session );
284     if( !ssh_knownhosts )
285         goto error;
286
287     psz_home = config_GetUserDir( VLC_HOME_DIR );
288     char *psz_knownhosts_file;
289     if( asprintf( &psz_knownhosts_file, "%s/.ssh/known_hosts", psz_home ) != -1 )
290     {
291         libssh2_knownhost_readfile( ssh_knownhosts, psz_knownhosts_file,
292                 LIBSSH2_KNOWNHOST_FILE_OPENSSH );
293         free( psz_knownhosts_file );
294     }
295
296     const char *fingerprint = libssh2_session_hostkey( p_sys->ssh_session, &i_len, &i_type );
297     struct libssh2_knownhost *host;
298     int knownhost_fingerprint_algo;
299
300     switch( i_type )
301     {
302         case LIBSSH2_HOSTKEY_TYPE_RSA:
303             knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
304             break;
305
306         case LIBSSH2_HOSTKEY_TYPE_DSS:
307             knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
308             break;
309
310         default:
311             msg_Err( p_access, "Host uses unrecognized session-key algorithm" );
312             libssh2_knownhost_free( ssh_knownhosts );
313             goto error;
314
315     }
316
317     int check = libssh2_knownhost_check( ssh_knownhosts, url.psz_host,
318                                          fingerprint, i_len,
319                                          LIBSSH2_KNOWNHOST_TYPE_PLAIN |
320                                          LIBSSH2_KNOWNHOST_KEYENC_RAW |
321                                          knownhost_fingerprint_algo,
322                                          &host );
323
324     libssh2_knownhost_free( ssh_knownhosts );
325
326     /* Check that it does match or at least that the host is unknown */
327     switch(check)
328     {
329     case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
330     case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
331         msg_Dbg( p_access, "Unable to check the remote host" );
332         break;
333     case LIBSSH2_KNOWNHOST_CHECK_MATCH:
334         msg_Dbg( p_access, "Succesfuly matched the host" );
335         break;
336     case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
337         msg_Err( p_access, "The host does not match !! The remote key changed !!" );
338         goto error;
339     }
340
341     vlc_credential_get( &credential, p_access, "sftp-user", "sftp-pwd",
342                         NULL, NULL );
343     char* psz_userauthlist = NULL;
344     bool b_publickey_tried = false;
345     do
346     {
347         if (!credential.psz_username || !credential.psz_username[0])
348             continue;
349
350         if( !psz_session_username )
351         {
352             psz_session_username = strdup( credential.psz_username );
353             psz_userauthlist =
354                 libssh2_userauth_list( p_sys->ssh_session, credential.psz_username,
355                                        strlen( credential.psz_username ) );
356         }
357         else if( strcmp( psz_session_username, credential.psz_username ) != 0 )
358         {
359             msg_Warn( p_access, "The username changed, starting a new ssh session" );
360
361             SSHSessionDestroy( p_access );
362             if( SSHSessionInit( p_access, url.psz_host, i_port ) != VLC_SUCCESS )
363                 goto error;
364
365             b_publickey_tried = false;
366             free( psz_session_username );
367             psz_session_username = strdup( credential.psz_username );
368             psz_userauthlist =
369                 libssh2_userauth_list( p_sys->ssh_session, credential.psz_username,
370                                        strlen( credential.psz_username ) );
371         }
372         if( !psz_session_username || !psz_userauthlist )
373             goto error;
374
375         /* TODO: Follow PreferredAuthentications in ssh_config */
376
377         if( strstr( psz_userauthlist, "publickey" ) != NULL && !b_publickey_tried )
378         {
379             /* Don't try public key multiple times to avoid getting black
380              * listed */
381             b_publickey_tried = true;
382             if( AuthKeyAgent( p_access, credential.psz_username ) == VLC_SUCCESS
383              || AuthPublicKey( p_access, psz_home, credential.psz_username ) == VLC_SUCCESS )
384                 break;
385         }
386
387         if( strstr( psz_userauthlist, "password" ) != NULL
388          && credential.psz_password != NULL
389          && libssh2_userauth_password( p_sys->ssh_session,
390                                        credential.psz_username,
391                                        credential.psz_password ) == 0 )
392         {
393             vlc_credential_store( &credential, p_access );
394             break;
395         }
396
397         msg_Warn( p_access, "sftp auth failed for %s", credential.psz_username );
398     } while( vlc_credential_get( &credential, p_access, "sftp-user", "sftp-pwd",
399                                 _("SFTP authentication"),
400                                 _("Please enter a valid login and password for "
401                                 "the sftp connexion to %s"), url.psz_host ) );
402
403     /* Create the sftp session */
404     p_sys->sftp_session = libssh2_sftp_init( p_sys->ssh_session );
405
406     if( !p_sys->sftp_session )
407     {
408         msg_Err( p_access, "Unable to initialize the SFTP session" );
409         goto error;
410     }
411
412     /* No path, default to user Home */
413     if( !psz_path )
414     {
415         const size_t i_size = 1024;
416         int i_read;
417
418         char* psz_remote_home = malloc( i_size );
419         if( !psz_remote_home )
420             goto error;
421
422         i_read = libssh2_sftp_symlink_ex( p_sys->sftp_session, ".", 1,
423                                           psz_remote_home, i_size - 1,
424                                           LIBSSH2_SFTP_REALPATH );
425         if( i_read <= 0 )
426         {
427             msg_Err( p_access, "Impossible to get the Home directory" );
428             free( psz_remote_home );
429             goto error;
430         }
431         psz_remote_home[i_read] = '\0';
432         psz_path = psz_remote_home;
433
434         /* store base url for directory read */
435         char *base = vlc_path2uri( psz_path, "sftp" );
436         if( !base )
437             goto error;
438         if( -1 == asprintf( &p_sys->psz_base_url, "sftp://%s%s", p_access->psz_location, base + 7 ) )
439         {
440             free( base );
441             goto error;
442         }
443         free( base );
444     }
445
446     /* Get some information */
447     LIBSSH2_SFTP_ATTRIBUTES attributes;
448     if( libssh2_sftp_stat( p_sys->sftp_session, psz_path, &attributes ) )
449     {
450         msg_Err( p_access, "Impossible to get information about the remote path %s", psz_path );
451         goto error;
452     }
453
454     if( !LIBSSH2_SFTP_S_ISDIR( attributes.permissions ))
455     {
456         /* Open the given file */
457         p_sys->file = libssh2_sftp_open( p_sys->sftp_session, psz_path, LIBSSH2_FXF_READ, 0 );
458         p_sys->filesize = attributes.filesize;
459
460         ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
461     }
462     else
463     {
464         /* Open the given directory */
465         p_sys->file = libssh2_sftp_opendir( p_sys->sftp_session, psz_path );
466
467         p_access->pf_readdir = DirRead;
468         p_access->pf_control = access_vaDirectoryControlHelper;
469
470         if( !p_sys->psz_base_url )
471         {
472             if( asprintf( &p_sys->psz_base_url, "sftp://%s", p_access->psz_location ) == -1 )
473                 goto error;
474
475             /* trim trailing '/' */
476             size_t len = strlen( p_sys->psz_base_url );
477             if( len > 0 && p_sys->psz_base_url[ len - 1 ] == '/' )
478                 p_sys->psz_base_url[ len - 1 ] = 0;
479         }
480     }
481
482     if( !p_sys->file )
483     {
484         msg_Err( p_access, "Unable to open the remote path %s", psz_path );
485         goto error;
486     }
487
488     i_result = VLC_SUCCESS;
489
490 error:
491     free( psz_home );
492     free( psz_session_username );
493     free( psz_path );
494     vlc_credential_clean( &credential );
495     vlc_UrlClean( &url );
496     if( i_result != VLC_SUCCESS ) {
497         Close( p_this );
498     }
499     return i_result;
500 }
501
502
503 /* Close: quit the module */
504 static void Close( vlc_object_t* p_this )
505 {
506     stream_t*   p_access = (stream_t*)p_this;
507     access_sys_t* p_sys = p_access->p_sys;
508
509     if( p_sys->file )
510         libssh2_sftp_close_handle( p_sys->file );
511     if( p_sys->sftp_session )
512         libssh2_sftp_shutdown( p_sys->sftp_session );
513     SSHSessionDestroy( p_access );
514
515     free( p_sys->psz_base_url );
516 }
517
518
519 static ssize_t Read( stream_t *p_access, void *buf, size_t len )
520 {
521     access_sys_t *p_sys = p_access->p_sys;
522
523     ssize_t val = libssh2_sftp_read(  p_sys->file, buf, len );
524     if( val < 0 )
525     {
526         msg_Err( p_access, "read failed" );
527         return 0;
528     }
529
530     return val;
531 }
532
533
534 static int Seek( stream_t* p_access, uint64_t i_pos )
535 {
536     access_sys_t *sys = p_access->p_sys;
537
538     libssh2_sftp_seek( sys->file, i_pos );
539     return VLC_SUCCESS;
540 }
541
542
543 static int Control( stream_t* p_access, int i_query, va_list args )
544 {
545     access_sys_t *sys = p_access->p_sys;
546     bool*       pb_bool;
547     int64_t*    pi_64;
548
549     switch( i_query )
550     {
551     case STREAM_CAN_SEEK:
552         pb_bool = va_arg( args, bool * );
553         *pb_bool = true;
554         break;
555
556     case STREAM_CAN_FASTSEEK:
557         pb_bool = va_arg( args, bool * );
558         *pb_bool = false;
559         break;
560
561     case STREAM_CAN_PAUSE:
562     case STREAM_CAN_CONTROL_PACE:
563         pb_bool = va_arg( args, bool * );
564         *pb_bool = true;
565         break;
566
567     case STREAM_GET_SIZE:
568         if( p_access->pf_readdir != NULL )
569             return VLC_EGENERIC;
570         *va_arg( args, uint64_t * ) = sys->filesize;
571         break;
572
573     case STREAM_GET_PTS_DELAY:
574         pi_64 = va_arg( args, int64_t * );
575         *pi_64 = INT64_C(1000)
576                * var_InheritInteger( p_access, "network-caching" );
577         break;
578
579     case STREAM_SET_PAUSE_STATE:
580         break;
581
582     default:
583         return VLC_EGENERIC;
584     }
585
586     return VLC_SUCCESS;
587 }
588
589
590 /*****************************************************************************
591  * Directory access
592  *****************************************************************************/
593
594 static int DirRead (stream_t *p_access, input_item_node_t *p_current_node)
595 {
596     access_sys_t *p_sys = p_access->p_sys;
597     LIBSSH2_SFTP_ATTRIBUTES attrs;
598     int i_ret = VLC_SUCCESS;
599     int err;
600     /* Allocate 1024 bytes for file name. Longer names are skipped.
601      * libssh2 does not support seeking in directory streams.
602      * Retrying with larger buffer is possible, but would require
603      * re-opening the directory stream.
604      */
605     size_t i_size = 1024;
606     char *psz_file = malloc( i_size );
607
608     if( !psz_file )
609         return VLC_ENOMEM;
610
611     struct vlc_readdir_helper rdh;
612     vlc_readdir_helper_init( &rdh, p_access, p_current_node );
613
614     while( i_ret == VLC_SUCCESS
615         && 0 != ( err = libssh2_sftp_readdir( p_sys->file, psz_file, i_size, &attrs ) ) )
616     {
617         if( err < 0 )
618         {
619             if( err == LIBSSH2_ERROR_BUFFER_TOO_SMALL )
620             {
621                 /* seeking back is not possible ... */
622                 msg_Dbg( p_access, "skipped too long file name" );
623                 continue;
624             }
625             if( err == LIBSSH2_ERROR_EAGAIN )
626             {
627                 continue;
628             }
629             msg_Err( p_access, "directory read failed" );
630             break;
631         }
632
633         /* Create an input item for the current entry */
634
635         char *psz_full_uri, *psz_uri;
636
637         psz_uri = vlc_uri_encode( psz_file );
638         if( psz_uri == NULL )
639         {
640             i_ret = VLC_ENOMEM;
641             break;
642         }
643
644         if( asprintf( &psz_full_uri, "%s/%s", p_sys->psz_base_url, psz_uri ) == -1 )
645         {
646             free( psz_uri );
647             i_ret = VLC_ENOMEM;
648             break;
649         }
650         free( psz_uri );
651
652         int i_type = LIBSSH2_SFTP_S_ISDIR( attrs.permissions ) ? ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
653         i_ret = vlc_readdir_helper_additem( &rdh, psz_full_uri, NULL, psz_file,
654                                             i_type, ITEM_NET );
655         free( psz_full_uri );
656     }
657
658     vlc_readdir_helper_finish( &rdh, i_ret == VLC_SUCCESS );
659     free( psz_file );
660     return i_ret;
661 }