config: remove advanced from add_password()
[vlc.git] / modules / access / dsm / access.c
1 /*****************************************************************************
2  * bdsm/access.c: liBDSM based SMB/CIFS access module
3  *****************************************************************************
4  * Copyright (C) 2001-2014 VLC authors and VideoLAN
5  *
6  * Authors: Julien 'Lta' BALLET <contact # lta 'dot' io>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_services_discovery.h>
33 #include <vlc_url.h>
34 #include <vlc_access.h>
35 #include <vlc_variables.h>
36 #include <vlc_keystore.h>
37 #include <vlc_network.h>
38
39 #include <assert.h>
40 #include <string.h>
41 #ifdef HAVE_SYS_SOCKET_H
42 # include <sys/socket.h>
43 # include <netinet/in.h>
44 # include <netdb.h>
45 #endif
46 #ifdef HAVE_ARPA_INET_H
47 #include <arpa/inet.h>
48 #endif
49
50 #include <bdsm/bdsm.h>
51 #include "../smb_common.h"
52
53 /*****************************************************************************
54  * Module descriptor
55  *****************************************************************************/
56 int bdsm_SdOpen( vlc_object_t * );
57 void bdsm_SdClose( vlc_object_t * );
58 int bdsm_sd_probe_Open( vlc_object_t * );
59
60 static int Open( vlc_object_t * );
61 static void Close( vlc_object_t * );
62
63 VLC_SD_PROBE_HELPER( "dsm", N_("Windows networks"), SD_CAT_LAN )
64
65 #define BDSM_HELP N_("libdsm's SMB (Windows network shares) input and browser")
66
67 vlc_module_begin ()
68     set_shortname( "dsm" )
69     set_description( N_("libdsm SMB input") )
70     set_help(BDSM_HELP)
71     set_capability( "access", 20 )
72     set_category( CAT_INPUT )
73     set_subcategory( SUBCAT_INPUT_ACCESS )
74     add_string( "smb-user", NULL, SMB_USER_TEXT, SMB_USER_LONGTEXT, false )
75     add_password("smb-pwd", NULL, SMB_PASS_TEXT, SMB_PASS_LONGTEXT)
76     add_string( "smb-domain", NULL, SMB_DOMAIN_TEXT, SMB_DOMAIN_LONGTEXT, false )
77     add_shortcut( "smb", "cifs" )
78     set_callbacks( Open, Close )
79
80     add_submodule()
81         add_shortcut( "dsm-sd" )
82         set_description( N_("libdsm NETBIOS discovery module") )
83         set_category( CAT_PLAYLIST )
84         set_subcategory( SUBCAT_PLAYLIST_SD )
85         set_capability( "services_discovery", 0 )
86         set_callbacks( bdsm_SdOpen, bdsm_SdClose )
87
88         VLC_SD_PROBE_SUBMODULE
89
90 vlc_module_end ()
91
92 /*****************************************************************************
93  * Local prototypes
94  *****************************************************************************/
95 static ssize_t Read( stream_t *, void *, size_t );
96 static int Seek( stream_t *, uint64_t );
97 static int Control( stream_t *, int, va_list );
98 static int BrowserInit( stream_t *p_access );
99
100 static int get_address( stream_t *p_access );
101 static int login( stream_t *p_access );
102 static bool get_path( stream_t *p_access );
103 static int add_item( stream_t *p_access,  struct vlc_readdir_helper *p_rdh,
104                      const char *psz_name, int i_type );
105
106 struct access_sys_t
107 {
108     netbios_ns         *p_ns;               /**< Netbios name service */
109     smb_session        *p_session;          /**< bdsm SMB Session object */
110
111     vlc_url_t           url;
112     char               *psz_fullpath;
113     const char         *psz_share;
114     const char         *psz_path;
115
116     char                netbios_name[16];
117     struct in_addr      addr;
118
119     smb_fd              i_fd;               /**< SMB fd for the file we're reading */
120     smb_tid             i_tid;              /**< SMB Tree ID we're connected to */
121 };
122
123 /*****************************************************************************
124  * Open: Initialize module's data structures and libdsm
125  *****************************************************************************/
126 static int Open( vlc_object_t *p_this )
127 {
128     stream_t     *p_access = (stream_t*)p_this;
129     access_sys_t *p_sys;
130     smb_stat st;
131
132     /* Init p_access */
133     p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
134     if( p_access->p_sys == NULL )
135         return VLC_ENOMEM;
136
137     p_sys->p_ns = netbios_ns_new();
138     if( p_sys->p_ns == NULL )
139         goto error;
140
141     p_sys->p_session = smb_session_new();
142     if( p_sys->p_session == NULL )
143         goto error;
144
145     if( vlc_UrlParseFixup( &p_sys->url, p_access->psz_url ) != 0 )
146         goto error;
147
148     if( get_address( p_access ) != VLC_SUCCESS )
149         goto error;
150
151     msg_Dbg( p_access, "Session: Host name = %s, ip = %s", p_sys->netbios_name,
152              inet_ntoa( p_sys->addr ) );
153
154     /* Now that we have the required data, let's establish a session */
155     if( smb_session_connect( p_sys->p_session, p_sys->netbios_name,
156                              p_sys->addr.s_addr, SMB_TRANSPORT_TCP )
157                              != DSM_SUCCESS )
158     {
159         msg_Err( p_access, "Unable to connect/negotiate SMB session");
160         goto error;
161     }
162
163     get_path( p_access );
164
165     if( login( p_access ) != VLC_SUCCESS )
166     {
167         msg_Err( p_access, "Unable to open file with path %s (in share %s)",
168                  p_sys->psz_path, p_sys->psz_share );
169         goto error;
170     }
171
172     /* If there is no shares, browse them */
173     if( !p_sys->psz_share )
174         return BrowserInit( p_access );
175
176     assert(p_sys->i_fd > 0);
177
178     msg_Dbg( p_access, "Path: Share name = %s, path = %s", p_sys->psz_share,
179              p_sys->psz_path );
180
181     st = smb_stat_fd( p_sys->p_session, p_sys->i_fd );
182     if( smb_stat_get( st, SMB_STAT_ISDIR ) )
183     {
184         smb_fclose( p_sys->p_session, p_sys->i_fd );
185         return BrowserInit( p_access );
186     }
187
188     msg_Dbg( p_access, "Successfully opened smb://%s", p_access->psz_location );
189
190     ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
191     return VLC_SUCCESS;
192
193     error:
194         Close( p_this );
195         return VLC_EGENERIC;
196 }
197
198 /*****************************************************************************
199  * Close: free unused data structures
200  *****************************************************************************/
201 static void Close( vlc_object_t *p_this )
202 {
203     stream_t     *p_access = (stream_t*)p_this;
204     access_sys_t *p_sys = p_access->p_sys;
205
206     if( p_sys->p_ns )
207         netbios_ns_destroy( p_sys->p_ns );
208     if( p_sys->i_fd )
209         smb_fclose( p_sys->p_session, p_sys->i_fd );
210     if( p_sys->p_session )
211         smb_session_destroy( p_sys->p_session );
212     vlc_UrlClean( &p_sys->url );
213     free( p_sys->psz_fullpath );
214
215     free( p_sys );
216 }
217
218 /*****************************************************************************
219  * Local functions
220  *****************************************************************************/
221
222 /* Returns VLC_EGENERIC if it wasn't able to get an ip address to connect to */
223 static int get_address( stream_t *p_access )
224 {
225     access_sys_t *p_sys = p_access->p_sys;
226
227     if( p_sys->url.psz_host != NULL &&
228         !inet_pton( AF_INET, p_sys->url.psz_host, &p_sys->addr ) )
229     {
230         /* This is not an ip address, let's try netbios/dns resolve */
231         struct addrinfo *p_info = NULL;
232
233         /* Is this a netbios name on this LAN ? */
234         if( netbios_ns_resolve( p_sys->p_ns, p_sys->url.psz_host,
235                                 NETBIOS_FILESERVER,
236                                 &p_sys->addr.s_addr) == 0 )
237         {
238             strlcpy( p_sys->netbios_name, p_sys->url.psz_host, 16);
239             return VLC_SUCCESS;
240         }
241         /* or is it an existing dns name ? */
242         else if( getaddrinfo( p_sys->url.psz_host, NULL, NULL, &p_info ) == 0 )
243         {
244             if( p_info->ai_family == AF_INET )
245             {
246                 struct sockaddr_in *in = (struct sockaddr_in *)p_info->ai_addr;
247                 p_sys->addr.s_addr = in->sin_addr.s_addr;
248             }
249             if( p_info->ai_family != AF_INET )
250             {
251                 freeaddrinfo( p_info );
252                 return VLC_EGENERIC;
253             }
254             freeaddrinfo( p_info );
255         }
256         else
257             return VLC_EGENERIC;
258     }
259
260     /* We have an IP address, let's find the NETBIOS name */
261     const char *psz_nbt = netbios_ns_inverse( p_sys->p_ns, p_sys->addr.s_addr );
262     if( psz_nbt != NULL )
263         strlcpy( p_sys->netbios_name, psz_nbt, 16 );
264     else
265     {
266         msg_Warn( p_access, "Unable to get netbios name of %s",
267             p_sys->url.psz_host );
268         p_sys->netbios_name[0] = '\0';
269     }
270
271     return VLC_SUCCESS;
272 }
273
274 static int smb_connect( stream_t *p_access, const char *psz_login,
275                         const char *psz_password, const char *psz_domain)
276 {
277     access_sys_t *p_sys = p_access->p_sys;
278
279     smb_session_set_creds( p_sys->p_session, psz_domain,
280                            psz_login, psz_password );
281     if( smb_session_login( p_sys->p_session ) == DSM_SUCCESS )
282     {
283         if( p_sys->psz_share )
284         {
285             /* Connect to the share */
286             if( smb_tree_connect( p_sys->p_session, p_sys->psz_share,
287                                   &p_sys->i_tid ) != DSM_SUCCESS )
288                 return VLC_EGENERIC;
289
290             /* Let's finally ask a handle to the file we wanna read ! */
291             return smb_fopen( p_sys->p_session, p_sys->i_tid, p_sys->psz_path,
292                               SMB_MOD_RO, &p_sys->i_fd )
293                               == DSM_SUCCESS ? VLC_SUCCESS : VLC_EGENERIC;
294         }
295         else
296             return VLC_SUCCESS;
297     }
298     else
299         return VLC_EGENERIC;
300 }
301
302 /* Performs login with existing credentials and ask the user for new ones on
303    failure */
304 static int login( stream_t *p_access )
305 {
306     int i_ret = VLC_EGENERIC;
307     access_sys_t *p_sys = p_access->p_sys;
308     vlc_credential credential;
309     char *psz_var_domain;
310     const char *psz_login, *psz_password, *psz_domain;
311     bool b_guest = false;
312
313     vlc_credential_init( &credential, &p_sys->url );
314     psz_var_domain = var_InheritString( p_access, "smb-domain" );
315     credential.psz_realm = psz_var_domain ? psz_var_domain : NULL;
316
317     vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
318                         NULL, NULL );
319
320     if( !credential.psz_username )
321     {
322         psz_login = "Guest";
323         psz_password = "";
324         b_guest = true;
325     }
326     else
327     {
328         psz_login = credential.psz_username;
329         psz_password = credential.psz_password;
330     }
331     psz_domain = credential.psz_realm ? credential.psz_realm : p_sys->netbios_name;
332
333     /* Try to authenticate on the remote machine */
334     if( smb_connect( p_access, psz_login, psz_password, psz_domain )
335                      != VLC_SUCCESS )
336     {
337         while( vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
338                                    SMB_LOGIN_DIALOG_TITLE,
339                                    SMB_LOGIN_DIALOG_TEXT, p_sys->netbios_name ) )
340         {
341             b_guest = false;
342             psz_login = credential.psz_username;
343             psz_password = credential.psz_password;
344             psz_domain = credential.psz_realm ? credential.psz_realm
345                                               : p_sys->netbios_name;
346             if( smb_connect( p_access, psz_login, psz_password, psz_domain )
347                              == VLC_SUCCESS )
348                 goto success;
349         }
350
351         msg_Err( p_access, "Unable to login" );
352         goto error;
353     }
354     else if( smb_session_is_guest( p_sys->p_session ) == 1 )
355     {
356         msg_Warn( p_access, "Login failure but you were logged in as a Guest");
357         b_guest = true;
358     }
359
360 success:
361     msg_Warn( p_access, "Creds: username = '%s', domain = '%s'",
362              psz_login, psz_domain );
363     if( !b_guest )
364         vlc_credential_store( &credential, p_access );
365
366     i_ret = VLC_SUCCESS;
367 error:
368     vlc_credential_clean( &credential );
369     free( psz_var_domain );
370     return i_ret;
371 }
372
373 static void backslash_path( char *psz_path )
374 {
375     char *iter = psz_path;
376
377     /* Let's switch the path delimiters from / to \ */
378     while( *iter != '\0' )
379     {
380         if( *iter == '/' )
381             *iter = '\\';
382         iter++;
383     }
384 }
385
386 /* Get the share and filepath from uri (also replace all / by \ in url.psz_path) */
387 static bool get_path( stream_t *p_access )
388 {
389     access_sys_t *p_sys = p_access->p_sys;
390     char *iter;
391
392     if( p_sys->url.psz_path == NULL )
393         return false;
394
395     p_sys->psz_fullpath = vlc_uri_decode_duplicate( p_sys->url.psz_path );
396     if( p_sys->psz_fullpath == NULL )
397         return false;
398
399     backslash_path( p_sys->psz_fullpath );
400
401     /* Is path longer than just "/" ? */
402     if( strlen( p_sys->psz_fullpath ) > 1 )
403     {
404         iter = p_sys->psz_fullpath;
405         while( *iter == '\\' ) iter++; /* Handle smb://Host/////Share/ */
406
407         p_sys->psz_share = iter;
408     }
409     else
410     {
411         msg_Dbg( p_access, "no share, nor file path provided, will switch to browser");
412         return false;
413     }
414
415     iter = strchr( p_sys->psz_share, '\\' );
416     if( iter == NULL || strlen(iter + 1) == 0 )
417     {
418         if( iter != NULL ) /* Remove the trailing \ */
419             *iter = '\0';
420         p_sys->psz_path = "";
421
422         msg_Dbg( p_access, "no file path provided, will switch to browser ");
423         return true;
424     }
425
426     p_sys->psz_path = iter + 1; /* Skip the first \ */
427     *iter = '\0';
428
429     return true;
430 }
431
432 /*****************************************************************************
433  * Seek: try to go at the right place
434  *****************************************************************************/
435 static int Seek( stream_t *p_access, uint64_t i_pos )
436 {
437     access_sys_t *p_sys = p_access->p_sys;
438
439     if( i_pos >= INT64_MAX )
440         return VLC_EGENERIC;
441
442     msg_Dbg( p_access, "seeking to %"PRId64, i_pos );
443
444     if (smb_fseek(p_sys->p_session, p_sys->i_fd, i_pos, SMB_SEEK_SET) == -1)
445         return VLC_EGENERIC;
446
447     return VLC_SUCCESS;
448 }
449
450 /*****************************************************************************
451  * Read:
452  *****************************************************************************/
453 static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len )
454 {
455     access_sys_t *p_sys = p_access->p_sys;
456     int i_read;
457
458     i_read = smb_fread( p_sys->p_session, p_sys->i_fd, p_buffer, i_len );
459     if( i_read < 0 )
460     {
461         msg_Err( p_access, "read failed" );
462         return -1;
463     }
464
465     return i_read;
466 }
467
468 /*****************************************************************************
469  * Control:
470  *****************************************************************************/
471 static int Control( stream_t *p_access, int i_query, va_list args )
472 {
473     access_sys_t *p_sys = p_access->p_sys;
474
475     switch( i_query )
476     {
477     case STREAM_CAN_SEEK:
478     case STREAM_CAN_PAUSE:
479     case STREAM_CAN_CONTROL_PACE:
480         *va_arg( args, bool* ) = true;
481         break;
482
483     case STREAM_CAN_FASTSEEK:
484         *va_arg( args, bool* ) = false;
485         break;
486
487     case STREAM_GET_SIZE:
488     {
489         smb_stat st = smb_stat_fd( p_sys->p_session, p_sys->i_fd );
490         *va_arg( args, uint64_t * ) = smb_stat_get( st, SMB_STAT_SIZE );
491         break;
492     }
493     case STREAM_GET_PTS_DELAY:
494         *va_arg( args, int64_t * ) = INT64_C(1000)
495             * var_InheritInteger( p_access, "network-caching" );
496         break;
497
498     case STREAM_SET_PAUSE_STATE:
499         /* Nothing to do */
500         break;
501
502     default:
503         return VLC_EGENERIC;
504     }
505
506     return VLC_SUCCESS;
507 }
508
509 static int add_item( stream_t *p_access, struct vlc_readdir_helper *p_rdh,
510                      const char *psz_name, int i_type )
511 {
512     char         *psz_uri;
513     int           i_ret;
514
515     char *psz_encoded_name = vlc_uri_encode( psz_name );
516     if( psz_encoded_name == NULL )
517         return VLC_ENOMEM;
518     const char *psz_sep = p_access->psz_location[0] != '\0'
519         && p_access->psz_location[strlen(p_access->psz_location) -1] != '/'
520         ? "/" : "";
521     i_ret = asprintf( &psz_uri, "smb://%s%s%s", p_access->psz_location,
522                       psz_sep, psz_encoded_name );
523     free( psz_encoded_name );
524     if( i_ret == -1 )
525         return VLC_ENOMEM;
526
527     return vlc_readdir_helper_additem( p_rdh, psz_uri, NULL, psz_name, i_type,
528                                        ITEM_NET );
529 }
530
531 static int BrowseShare( stream_t *p_access, input_item_node_t *p_node )
532 {
533     access_sys_t *p_sys = p_access->p_sys;
534     smb_share_list  shares;
535     const char     *psz_name;
536     size_t          share_count;
537     int             i_ret = VLC_SUCCESS;
538
539     if( smb_share_get_list( p_sys->p_session, &shares, &share_count )
540         != DSM_SUCCESS )
541         return VLC_EGENERIC;
542
543     struct vlc_readdir_helper rdh;
544     vlc_readdir_helper_init( &rdh, p_access, p_node );
545
546     for( size_t i = 0; i < share_count && i_ret == VLC_SUCCESS; i++ )
547     {
548         psz_name = smb_share_list_at( shares, i );
549
550         if( psz_name[strlen( psz_name ) - 1] == '$')
551             continue;
552
553         i_ret = add_item( p_access, &rdh, psz_name, ITEM_TYPE_DIRECTORY );
554     }
555
556     vlc_readdir_helper_finish( &rdh, i_ret == VLC_SUCCESS );
557
558     smb_share_list_destroy( shares );
559     return i_ret;
560 }
561
562 static int BrowseDirectory( stream_t *p_access, input_item_node_t *p_node )
563 {
564     access_sys_t *p_sys = p_access->p_sys;
565     smb_stat_list   files;
566     smb_stat        st;
567     char           *psz_query;
568     const char     *psz_name;
569     size_t          files_count;
570     int             i_ret = VLC_SUCCESS;
571
572     if( p_sys->psz_path != NULL )
573     {
574         if( asprintf( &psz_query, "%s\\*", p_sys->psz_path ) == -1 )
575             return VLC_ENOMEM;
576         files = smb_find( p_sys->p_session, p_sys->i_tid, psz_query );
577         free( psz_query );
578     }
579     else
580         files = smb_find( p_sys->p_session, p_sys->i_tid, "\\*" );
581
582     if( files == NULL )
583         return VLC_EGENERIC;
584
585     struct vlc_readdir_helper rdh;
586     vlc_readdir_helper_init( &rdh, p_access, p_node );
587
588     files_count = smb_stat_list_count( files );
589     for( size_t i = 0; i < files_count && i_ret == VLC_SUCCESS; i++ )
590     {
591         int i_type;
592
593         st = smb_stat_list_at( files, i );
594
595         if( st == NULL )
596         {
597             continue;
598         }
599
600         psz_name = smb_stat_name( st );
601
602         i_type = smb_stat_get( st, SMB_STAT_ISDIR ) ?
603                  ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
604         i_ret = add_item( p_access, &rdh, psz_name, i_type );
605     }
606
607     vlc_readdir_helper_finish( &rdh, i_ret == VLC_SUCCESS );
608
609     smb_stat_list_destroy( files );
610     return i_ret;
611 }
612
613 static int BrowserInit( stream_t *p_access )
614 {
615     access_sys_t *p_sys = p_access->p_sys;
616
617     if( p_sys->psz_share == NULL )
618         p_access->pf_readdir = BrowseShare;
619     else
620         p_access->pf_readdir = BrowseDirectory;
621     p_access->pf_control = access_vaDirectoryControlHelper;
622
623     return VLC_SUCCESS;
624 }