1 /*****************************************************************************
2 * bdsm/access.c: liBDSM based SMB/CIFS access module
3 *****************************************************************************
4 * Copyright (C) 2001-2014 VLC authors and VideoLAN
6 * Authors: Julien 'Lta' BALLET <contact # lta 'dot' io>
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.
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.
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 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_services_discovery.h>
34 #include <vlc_access.h>
35 #include <vlc_variables.h>
36 #include <vlc_dialog.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
44 #include <bdsm/bdsm.h>
46 /*****************************************************************************
48 *****************************************************************************/
49 int bdsm_SdOpen( vlc_object_t * );
50 void bdsm_SdClose( vlc_object_t * );
51 int bdsm_sd_probe_Open( vlc_object_t * );
53 static int Open( vlc_object_t * );
54 static void Close( vlc_object_t * );
56 #define vlc_sd_probe_Open bdsm_sd_probe_Open
58 #define USER_TEXT N_("SMB user name")
59 #define USER_LONGTEXT N_("User name that will " \
60 "be used for the connection. username in uri take precedence over this")
61 #define PASS_TEXT N_("SMB password")
62 #define PASS_LONGTEXT N_("Password that will be " \
63 "used for the connection. Password in uri take precedence over this.")
64 #define DOMAIN_TEXT N_("SMB domain")
65 #define DOMAIN_LONGTEXT N_("Domain/Workgroup that " \
66 "will be used for the connection. Domain of uri will also be tried.")
68 #define BDSM_LOGIN_DIALOG_RETRY 1
70 #define BDSM_HELP N_("libdsm's SMB (Windows network shares) input and browser")
73 set_shortname( "dsm" )
74 set_description( N_("libdsm SMB input") )
76 set_capability( "access", 20 )
77 set_category( CAT_INPUT )
78 set_subcategory( SUBCAT_INPUT_ACCESS )
79 add_string( "smb-user", NULL, USER_TEXT, USER_LONGTEXT, false )
80 add_password( "smb-pwd", NULL, PASS_TEXT, PASS_LONGTEXT, false )
81 add_string( "smb-domain", NULL, DOMAIN_TEXT, DOMAIN_LONGTEXT, false )
82 add_shortcut( "smb", "cifs" )
83 set_callbacks( Open, Close )
86 set_category( CAT_PLAYLIST )
87 set_subcategory( SUBCAT_PLAYLIST_SD )
88 set_capability( "services_discovery", 0 )
89 set_callbacks( bdsm_SdOpen, bdsm_SdClose )
91 VLC_SD_PROBE_SUBMODULE
95 /*****************************************************************************
97 *****************************************************************************/
98 static ssize_t Read( access_t *, uint8_t *, size_t );
99 static int Seek( access_t *, uint64_t );
100 static int Control( access_t *, int, va_list );
101 static int BrowserInit( access_t *p_access );
103 static void split_domain_login( char **psz_login, char **psz_domain );
104 static void get_credentials( access_t *p_access );
105 static int get_address( access_t *p_access );
106 static void login_dialog( access_t *p_access );
107 static int login( access_t *p_access );
108 static void backslash_path( vlc_url_t *p_url );
109 static bool get_path( access_t *p_access );
110 static int add_item( access_t *p_access, input_item_node_t *p_node,
111 const char *psz_name );
115 netbios_ns *p_ns; /**< Netbios name service */
116 smb_session *p_session; /**< bdsm SMB Session object */
117 smb_creds creds; /**< Credentials used to connect */
123 char netbios_name[16];
126 smb_fd i_fd; /**< SMB fd for the file we're reading */
127 smb_tid i_tid; /**< SMB Tree ID we're connected to */
131 /*****************************************************************************
133 *****************************************************************************/
134 #define BDSM_LOGIN_DIALOG_TITLE N_( "%s: Authentication required" )
135 #define BDSM_LOGIN_DIALOG_TEXT N_( "The computer you are trying to connect " \
136 "to requires authentication.\n Please provide a username (and ideally a " \
137 "domain name using the format DOMAIN\\username)\n and a password." )
139 /*****************************************************************************
140 * Open: Initialize module's data structures and libdsm
141 *****************************************************************************/
142 static int Open( vlc_object_t *p_this )
144 access_t *p_access = (access_t*)p_this;
149 access_InitFields( p_access );
150 p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
151 if( p_access->p_sys == NULL )
154 p_sys->p_ns = netbios_ns_new();
155 if( p_sys->p_ns == NULL )
158 p_sys->p_session = smb_session_new();
159 if( p_sys->p_session == NULL )
162 vlc_UrlParse( &p_sys->url, p_access->psz_location, 0 );
163 get_credentials( p_access );
164 if( get_address( p_access ) != VLC_SUCCESS )
167 msg_Dbg( p_access, "Creds: username = %s, password = %s, domain = %s",
168 p_sys->creds.login, p_sys->creds.password,
169 p_sys->creds.domain );
170 msg_Dbg( p_access, "Session: Host name = %s, ip = %s", p_sys->netbios_name,
171 inet_ntoa( p_sys->addr ) );
173 /* Now that we have the required data, let's establish a session */
174 if( !smb_session_connect( p_sys->p_session, p_sys->netbios_name,
175 p_sys->addr.s_addr, SMB_TRANSPORT_TCP ) )
177 msg_Err( p_access, "Unable to connect/negotiate SMB session");
181 if( login( p_access ) != VLC_SUCCESS )
184 if( !get_path( p_access ) )
186 p_sys->b_is_browsing = true;
187 return BrowserInit( p_access );
190 msg_Dbg( p_access, "Path: Share name = %s, path = %s", p_sys->psz_share,
193 /* Connect to the share */
194 p_sys->i_tid = smb_tree_connect( p_sys->p_session, p_sys->psz_share );
197 msg_Err( p_access, "Unable to connect to share %s", p_sys->psz_share );
201 /* Let's finally ask a handle to the file we wanna read ! */
202 p_sys->i_fd = smb_fopen( p_sys->p_session, p_sys->i_tid, p_sys->psz_path,
206 msg_Err( p_access, "Unable to open file with path %s (in share %s)",
207 p_sys->psz_path, p_sys->psz_share );
211 st = smb_stat_fd( p_sys->p_session, p_sys->i_fd );
212 if( smb_stat_get( st, SMB_STAT_ISDIR ) )
214 smb_fclose( p_sys->p_session, p_sys->i_fd );
215 p_sys->b_is_browsing = true;
216 return BrowserInit( p_access );
219 msg_Dbg( p_access, "Successfully opened smb://%s", p_access->psz_location );
221 ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
229 /*****************************************************************************
230 * Close: free unused data structures
231 *****************************************************************************/
232 static void Close( vlc_object_t *p_this )
234 access_t *p_access = (access_t*)p_this;
235 access_sys_t *p_sys = p_access->p_sys;
238 netbios_ns_destroy( p_sys->p_ns );
240 smb_fclose( p_sys->p_session, p_sys->i_fd );
241 if( p_sys->p_session )
242 smb_session_destroy( p_sys->p_session );
243 free( p_sys->creds.login );
244 free( p_sys->creds.password );
245 free( p_sys->creds.domain );
246 free( p_sys->url.psz_buffer );
247 free( p_sys->psz_share );
248 free( p_sys->psz_path );
252 /*****************************************************************************
254 *****************************************************************************/
256 /* Split DOMAIN\User if it finds a '\' in psz_login. */
257 static void split_domain_login( char **psz_login, char **psz_domain )
259 char *user = strchr( *psz_login, '\\' );
263 *psz_domain = *psz_login;
265 *psz_login = strdup( user + 1 );
269 /* Get credentials from uri or variables. */
270 static void get_credentials( access_t *p_access )
272 access_sys_t *p_sys = p_access->p_sys;
274 /* Fetch credentials, either from URI or from options if not provided */
275 if( p_sys->url.psz_password == NULL )
276 p_sys->creds.password = var_InheritString( p_access, "smb-pwd" );
278 p_sys->creds.password = strdup( p_sys->url.psz_password );
280 /* Here we support smb://DOMAIN\User:password@XXX, get user from options
281 or default to "Guest" as last resort */
282 if( p_sys->url.psz_username != NULL )
284 p_sys->creds.login = strdup( p_sys->url.psz_username );
285 split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
289 p_sys->creds.login = var_InheritString( p_access, "smb-user" );
290 if( p_sys->creds.login == NULL )
291 p_sys->creds.login = strdup( "Guest" );
294 if( p_sys->creds.domain == NULL )
295 p_sys->creds.domain = var_InheritString( p_access, "smb-domain" );
298 /* Returns VLC_EGENERIC if it wasn't able to get an ip address to connect to */
299 static int get_address( access_t *p_access )
301 access_sys_t *p_sys = p_access->p_sys;
303 if( !inet_aton( p_sys->url.psz_host, &p_sys->addr ) )
305 /* This is not an ip address, let's try netbios/dns resolve */
306 struct addrinfo *p_info = NULL;
308 /* Is this a netbios name on this LAN ? */
309 if( netbios_ns_resolve( p_sys->p_ns, p_sys->url.psz_host, 0x20,
310 &p_sys->addr.s_addr) )
312 strlcpy( p_sys->netbios_name, p_sys->url.psz_host, 16);
315 /* or is it an existing dns name ? */
316 else if( getaddrinfo( p_sys->url.psz_host, NULL, NULL, &p_info ) == 0 )
318 if( p_info->ai_family == AF_INET )
320 struct sockaddr_in *in = (struct sockaddr_in *)p_info->ai_addr;
321 p_sys->addr.s_addr = in->sin_addr.s_addr;
323 freeaddrinfo( p_info );
324 if( p_info->ai_family != AF_INET )
331 /* We have an IP address, let's find the NETBIOS name */
332 const char *psz_nbt = netbios_ns_inverse( p_sys->p_ns, p_sys->addr.s_addr );
333 if( psz_nbt != NULL )
334 strlcpy( p_sys->netbios_name, psz_nbt, 16 );
337 msg_Warn( p_access, "Unable to get netbios name of %s",
338 p_sys->url.psz_host );
339 p_sys->netbios_name[0] = '\0';
342 /* If no domain was explicitly specified, let's use the machine name */
343 if( p_sys->creds.domain == NULL && p_sys->netbios_name[0] )
344 p_sys->creds.domain = strdup( p_sys->netbios_name );
349 /* Displays a dialog for the user to enter his/her credentials */
350 static void login_dialog( access_t *p_access )
352 access_sys_t *p_sys = p_access->p_sys;
354 char *psz_login = NULL, *psz_pass = NULL, *psz_title;
357 i_ret = asprintf( &psz_title, BDSM_LOGIN_DIALOG_TITLE, p_sys->netbios_name );
359 dialog_Login( p_access, &psz_login, &psz_pass, psz_title,
360 BDSM_LOGIN_DIALOG_TEXT );
362 dialog_Login( p_access, &psz_login, &psz_pass, BDSM_LOGIN_DIALOG_TITLE,
363 BDSM_LOGIN_DIALOG_TEXT );
366 if( psz_login != NULL )
368 if( p_sys->creds.login != NULL )
369 free( p_sys->creds.login );
370 p_sys->creds.login = psz_login;
371 split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
374 if( psz_pass != NULL )
376 if( p_sys->creds.password != NULL )
377 free( p_sys->creds.password );
378 p_sys->creds.password = psz_pass;
382 /* Performs login with existing credentials and ask the user for new ones on
384 static int login( access_t *p_access )
386 access_sys_t *p_sys = p_access->p_sys;
388 if( p_sys->creds.login == NULL )
389 p_sys->creds.login = strdup( "Guest" );
390 if( p_sys->creds.password == NULL )
391 p_sys->creds.password = strdup( "Guest" );
392 if( p_sys->creds.domain == NULL )
393 p_sys->creds.domain = strdup( "WORKGROUP" );
395 /* Try to authenticate on the remote machine */
396 smb_session_set_creds( p_sys->p_session, p_sys->creds.domain,
397 p_sys->creds.login, p_sys->creds.password );
398 if( !smb_session_login( p_sys->p_session ) )
400 for( int i = 0; i < BDSM_LOGIN_DIALOG_RETRY; i++ )
402 login_dialog( p_access );
403 smb_session_set_creds( p_sys->p_session, p_sys->creds.domain,
404 p_sys->creds.login, p_sys->creds.password );
405 if( smb_session_login( p_sys->p_session ) )
409 /* FIXME, Try to force netbios name as domain then WORKGROUP here */
410 msg_Err( p_access, "Unable to login with username = %s, password = %s, domain = %s",
411 p_sys->creds.login, p_sys->creds.password,
412 p_sys->creds.domain );
415 else if( smb_session_is_guest( p_sys->p_session ) == 1 )
416 msg_Warn( p_access, "Login failure but you were logged in as a Guest");
421 static void backslash_path( vlc_url_t *p_url )
423 char *iter = p_url->psz_path;
425 /* Let's switch the path delimiters from / to \ */
426 while( *iter != '\0' )
434 /* Get the share and filepath from uri (also replace all / by \ in url.psz_path) */
435 static bool get_path( access_t *p_access )
437 access_sys_t *p_sys = p_access->p_sys;
440 if( p_sys->url.psz_path == NULL )
443 backslash_path( &p_sys->url );
445 /* Is path longer than just "/" ? */
446 if( strlen( p_sys->url.psz_path ) > 1 )
448 iter = p_sys->url.psz_path;
449 while( *iter == '\\' ) iter++; /* Handle smb://Host/////Share/ */
451 p_sys->psz_share = strdup( iter );
452 if ( p_sys->psz_share == NULL )
457 msg_Dbg( p_access, "no share, nor file path provided, will switch to browser");
462 iter = strchr( p_sys->psz_share, '\\' );
463 if( iter == NULL || strlen(iter + 1) == 0 )
465 if( iter != NULL ) /* Remove the trailing \ */
467 p_sys->psz_path = strdup( "" );
469 msg_Dbg( p_access, "no file path provided, will switch to browser ");
473 p_sys->psz_path = strdup( iter + 1); /* Skip the first \ */
475 if( p_sys->psz_path == NULL )
481 /*****************************************************************************
482 * Seek: try to go at the right place
483 *****************************************************************************/
484 static int Seek( access_t *p_access, uint64_t i_pos )
486 access_sys_t *p_sys = p_access->p_sys;
489 if( i_pos >= INT64_MAX )
492 msg_Dbg( p_access, "seeking to %"PRId64, i_pos );
494 /* seek cannot fail in bdsm, but the subsequent read can */
495 i_ret = smb_fseek(p_sys->p_session, p_sys->i_fd, i_pos, SMB_SEEK_SET);
497 p_access->info.b_eof = false;
498 p_access->info.i_pos = i_ret;
503 /*****************************************************************************
505 *****************************************************************************/
506 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
508 access_sys_t *p_sys = p_access->p_sys;
511 if( p_access->info.b_eof ) return 0;
513 i_read = smb_fread( p_sys->p_session, p_sys->i_fd, p_buffer, i_len );
516 msg_Err( p_access, "read failed" );
520 if( i_read == 0 ) p_access->info.b_eof = true;
521 else if( i_read > 0 ) p_access->info.i_pos += i_read;
526 /*****************************************************************************
528 *****************************************************************************/
529 static int Control( access_t *p_access, int i_query, va_list args )
533 case ACCESS_CAN_SEEK:
534 case ACCESS_CAN_FASTSEEK:
535 case ACCESS_CAN_PAUSE:
536 case ACCESS_CAN_CONTROL_PACE:
537 *va_arg( args, bool* ) = true;
540 case ACCESS_GET_SIZE:
542 smb_stat st = smb_stat_fd( p_access->p_sys->p_session,
543 p_access->p_sys->i_fd );
544 *va_arg( args, uint64_t * ) = smb_stat_get( st, SMB_STAT_SIZE );
547 case ACCESS_GET_PTS_DELAY:
548 *va_arg( args, int64_t * ) = INT64_C(1000)
549 * var_InheritInteger( p_access, "network-caching" );
552 case ACCESS_SET_PAUSE_STATE:
563 static int add_item( access_t *p_access, input_item_node_t *p_node,
564 const char *psz_name )
566 access_sys_t *p_sys = p_access->p_sys;
567 input_item_t *p_item;
568 char *psz_uri, *psz_option;
571 i_ret = asprintf( &psz_uri, "%s/%s", p_node->p_item->psz_uri, psz_name );
575 p_item = input_item_New( psz_uri, psz_name );
580 /* Here we save on the node the credentials that allowed us to login.
581 * That way the user isn't prompted more than once for credentials */
582 i_ret = asprintf( &psz_option, "smb-user=%s", p_sys->creds.login );
585 input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
588 i_ret = asprintf( &psz_option, "smb-pwd=%s", p_sys->creds.password );
591 input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
594 i_ret = asprintf( &psz_option, "smb-domain=%s", p_sys->creds.domain );
597 input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
600 input_item_CopyOptions( p_node->p_item, p_item );
601 i_ret = input_item_node_AppendItem( p_node, p_item ) != NULL ? VLC_SUCCESS
604 input_item_Release( p_item );
608 static int BrowseShare( access_t *p_access, input_item_node_t *p_node )
610 access_sys_t *p_sys = p_access->p_sys;
611 smb_share_list shares;
612 const char *psz_name;
616 share_count = smb_share_get_list( p_sys->p_session, &shares );
620 for( size_t i = 0; i < share_count; i++ )
622 psz_name = smb_share_list_at( shares, i );
624 if( psz_name[strlen( psz_name ) - 1] == '$')
627 i_ret = add_item( p_access, p_node, psz_name );
628 if( i_ret != VLC_SUCCESS )
632 smb_share_list_destroy( shares );
635 smb_share_list_destroy( shares );
639 static int BrowseDirectory( access_t *p_access, input_item_node_t *p_node )
641 access_sys_t *p_sys = p_access->p_sys;
645 const char *psz_name;
649 if( p_sys->psz_path != NULL )
651 i_ret = asprintf( &psz_query, "%s\\*", p_sys->psz_path );
654 files = smb_find( p_sys->p_session, p_sys->i_tid, psz_query );
658 files = smb_find( p_sys->p_session, p_sys->i_tid, "\\*" );
663 files_count = smb_stat_list_count( files );
664 for( size_t i = 0; i < files_count; i++ )
666 st = smb_stat_list_at( files, i );
673 psz_name = smb_stat_name( st );
675 /* Avoid infinite loop */
676 if( !strcmp( psz_name, ".") || !strcmp( psz_name, "..") )
679 i_ret = add_item( p_access, p_node, psz_name );
680 if( i_ret != VLC_SUCCESS )
684 smb_stat_list_destroy( files );
688 smb_stat_list_destroy( files );
692 static int BrowserControl( access_t *p_access, int i_query, va_list args )
694 VLC_UNUSED( p_access );
698 case ACCESS_CAN_SEEK:
699 case ACCESS_CAN_FASTSEEK:
700 *va_arg( args, bool* ) = false;
703 case ACCESS_CAN_PAUSE:
704 case ACCESS_CAN_CONTROL_PACE:
705 *va_arg( args, bool* ) = true;
708 case ACCESS_GET_PTS_DELAY:
709 *va_arg( args, int64_t * ) = DEFAULT_PTS_DELAY * 1000;
718 static int BrowserInit( access_t *p_access )
720 access_sys_t *p_sys = p_access->p_sys;
722 if( p_sys->psz_share == NULL )
723 p_access->pf_readdir = BrowseShare;
725 p_access->pf_readdir = BrowseDirectory;
726 p_access->pf_control = BrowserControl;