]> git.sesse.net Git - vlc/blob - modules/access/dsm/access.c
1ea8e75508afcc77c1d773e3576821ec69a37958
[vlc] / 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_dialog.h>
37
38 #include <string.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <netdb.h>
43
44 #include <bdsm/bdsm.h>
45
46 /*****************************************************************************
47  * Module descriptor
48  *****************************************************************************/
49 int bdsm_SdOpen( vlc_object_t * );
50 void bdsm_SdClose( vlc_object_t * );
51 int bdsm_sd_probe_Open( vlc_object_t * );
52
53 static int Open( vlc_object_t * );
54 static void Close( vlc_object_t * );
55
56 #define vlc_sd_probe_Open bdsm_sd_probe_Open
57
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.")
67
68 #define BDSM_LOGIN_DIALOG_RETRY 1
69
70 #define BDSM_HELP N_("libdsm's SMB (Windows network shares) input and browser")
71
72 vlc_module_begin ()
73     set_shortname( "dsm" )
74     set_description( N_("libdsm SMB input") )
75     set_help(BDSM_HELP)
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 )
84
85     add_submodule()
86         set_category( CAT_PLAYLIST )
87         set_subcategory( SUBCAT_PLAYLIST_SD )
88         set_capability( "services_discovery", 0 )
89         set_callbacks( bdsm_SdOpen, bdsm_SdClose )
90
91         VLC_SD_PROBE_SUBMODULE
92
93 vlc_module_end ()
94
95 /*****************************************************************************
96  * Local prototypes
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 );
102
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 );
112
113 struct access_sys_t
114 {
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 */
118
119     vlc_url_t           url;
120     char               *psz_share;
121     char               *psz_path;
122
123     char                netbios_name[16];
124     struct in_addr      addr;
125
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 */
128     bool                b_is_browsing;
129 };
130
131 /*****************************************************************************
132  * Dialog strings
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." )
138
139 /*****************************************************************************
140  * Open: Initialize module's data structures and libdsm
141  *****************************************************************************/
142 static int Open( vlc_object_t *p_this )
143 {
144     access_t     *p_access = (access_t*)p_this;
145     access_sys_t *p_sys;
146     smb_stat st;
147
148     /* Init p_access */
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 )
152         return VLC_ENOMEM;
153
154     p_sys->p_ns = netbios_ns_new();
155     if( p_sys->p_ns == NULL )
156         goto error;
157
158     p_sys->p_session = smb_session_new();
159     if( p_sys->p_session == NULL )
160         goto error;
161
162     vlc_UrlParse( &p_sys->url, p_access->psz_location, 0 );
163     get_credentials( p_access );
164     if( get_address( p_access ) != VLC_SUCCESS )
165         goto error;
166
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 ) );
172
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 ) )
176     {
177         msg_Err( p_access, "Unable to connect/negotiate SMB session");
178         goto error;
179     }
180
181     if( login( p_access ) != VLC_SUCCESS )
182         goto error;
183
184     if( !get_path( p_access ) )
185     {
186         p_sys->b_is_browsing = true;
187         return BrowserInit( p_access );
188     }
189
190     msg_Dbg( p_access, "Path: Share name = %s, path = %s", p_sys->psz_share,
191              p_sys->psz_path );
192
193     /* Connect to the share */
194     p_sys->i_tid = smb_tree_connect( p_sys->p_session, p_sys->psz_share );
195     if( !p_sys->i_tid )
196     {
197         msg_Err( p_access, "Unable to connect to share %s", p_sys->psz_share );
198         goto error;
199     }
200
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,
203                              SMB_MOD_RO );
204     if( !p_sys->i_fd )
205     {
206         msg_Err( p_access, "Unable to open file with path %s (in share %s)",
207                  p_sys->psz_path, p_sys->psz_share );
208         goto error;
209     }
210
211     st = smb_stat_fd( p_sys->p_session, p_sys->i_fd );
212     if( smb_stat_get( st, SMB_STAT_ISDIR ) )
213     {
214         smb_fclose( p_sys->p_session, p_sys->i_fd );
215         p_sys->b_is_browsing = true;
216         return BrowserInit( p_access );
217     }
218
219     msg_Dbg( p_access, "Successfully opened smb://%s", p_access->psz_location );
220
221     ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
222     return VLC_SUCCESS;
223
224     error:
225         Close( p_this );
226         return VLC_EGENERIC;
227 }
228
229 /*****************************************************************************
230  * Close: free unused data structures
231  *****************************************************************************/
232 static void Close( vlc_object_t *p_this )
233 {
234     access_t     *p_access = (access_t*)p_this;
235     access_sys_t *p_sys = p_access->p_sys;
236
237     if( p_sys->p_ns )
238         netbios_ns_destroy( p_sys->p_ns );
239     if( p_sys->i_fd )
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 );
249     free( p_sys );
250 }
251
252 /*****************************************************************************
253  * Local functions
254  *****************************************************************************/
255
256 /* Split DOMAIN\User if it finds a '\' in psz_login. */
257 static void split_domain_login( char **psz_login, char **psz_domain )
258 {
259     char *user = strchr( *psz_login, '\\' );
260
261     if( user != NULL )
262     {
263         *psz_domain = *psz_login;
264         *user = '\0';
265         *psz_login = strdup( user + 1 );
266     }
267 }
268
269 /* Get credentials from uri or variables. */
270 static void get_credentials( access_t *p_access )
271 {
272     access_sys_t *p_sys = p_access->p_sys;
273
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" );
277     else
278         p_sys->creds.password = strdup( p_sys->url.psz_password );
279
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 )
283     {
284         p_sys->creds.login = strdup( p_sys->url.psz_username );
285         split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
286     }
287     else
288     {
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" );
292     }
293
294     if( p_sys->creds.domain == NULL )
295         p_sys->creds.domain = var_InheritString( p_access, "smb-domain" );
296 }
297
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 )
300 {
301     access_sys_t *p_sys = p_access->p_sys;
302
303     if( !inet_aton( p_sys->url.psz_host, &p_sys->addr ) )
304     {
305         /* This is not an ip address, let's try netbios/dns resolve */
306         struct addrinfo *p_info = NULL;
307
308         /* Is this a netbios name on this LAN ? */
309         if( netbios_ns_resolve( p_sys->p_ns, p_sys->url.psz_host,
310                                 NETBIOS_FILESERVER,
311                                 &p_sys->addr.s_addr) )
312         {
313             strlcpy( p_sys->netbios_name, p_sys->url.psz_host, 16);
314             return VLC_SUCCESS;
315         }
316         /* or is it an existing dns name ? */
317         else if( getaddrinfo( p_sys->url.psz_host, NULL, NULL, &p_info ) == 0 )
318         {
319             if( p_info->ai_family == AF_INET )
320             {
321                 struct sockaddr_in *in = (struct sockaddr_in *)p_info->ai_addr;
322                 p_sys->addr.s_addr = in->sin_addr.s_addr;
323             }
324             freeaddrinfo( p_info );
325             if( p_info->ai_family != AF_INET )
326                 return VLC_EGENERIC;
327         }
328         else
329             return VLC_EGENERIC;
330     }
331
332     /* We have an IP address, let's find the NETBIOS name */
333     const char *psz_nbt = netbios_ns_inverse( p_sys->p_ns, p_sys->addr.s_addr );
334     if( psz_nbt != NULL )
335         strlcpy( p_sys->netbios_name, psz_nbt, 16 );
336     else
337     {
338         msg_Warn( p_access, "Unable to get netbios name of %s",
339             p_sys->url.psz_host );
340         p_sys->netbios_name[0] = '\0';
341     }
342
343     /* If no domain was explicitly specified, let's use the machine name */
344     if( p_sys->creds.domain == NULL && p_sys->netbios_name[0] )
345         p_sys->creds.domain = strdup( p_sys->netbios_name );
346
347     return VLC_SUCCESS;
348 }
349
350 /* Displays a dialog for the user to enter his/her credentials */
351 static void login_dialog( access_t *p_access )
352 {
353     access_sys_t *p_sys = p_access->p_sys;
354
355     char *psz_login = NULL, *psz_pass = NULL, *psz_title;
356     int i_ret;
357
358     i_ret = asprintf( &psz_title, BDSM_LOGIN_DIALOG_TITLE, p_sys->netbios_name );
359     if( i_ret != -1 )
360         dialog_Login( p_access, &psz_login, &psz_pass, psz_title,
361                       BDSM_LOGIN_DIALOG_TEXT );
362     else
363         dialog_Login( p_access, &psz_login, &psz_pass, BDSM_LOGIN_DIALOG_TITLE,
364                       BDSM_LOGIN_DIALOG_TEXT );
365     free( psz_title );
366
367     if( psz_login != NULL )
368     {
369         if( p_sys->creds.login != NULL )
370             free( p_sys->creds.login );
371         p_sys->creds.login = psz_login;
372         split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
373     }
374
375     if( psz_pass != NULL )
376     {
377         if( p_sys->creds.password != NULL )
378             free( p_sys->creds.password );
379         p_sys->creds.password = psz_pass;
380     }
381 }
382
383 /* Performs login with existing credentials and ask the user for new ones on
384    failure */
385 static int login( access_t *p_access )
386 {
387     access_sys_t *p_sys = p_access->p_sys;
388
389     if( p_sys->creds.login == NULL )
390         p_sys->creds.login = strdup( "Guest" );
391     if( p_sys->creds.password == NULL )
392         p_sys->creds.password = strdup( "Guest" );
393     if( p_sys->creds.domain == NULL )
394         p_sys->creds.domain = strdup( "WORKGROUP" );
395
396     /* Try to authenticate on the remote machine */
397     smb_session_set_creds( p_sys->p_session, p_sys->creds.domain,
398                            p_sys->creds.login, p_sys->creds.password );
399     if( !smb_session_login( p_sys->p_session ) )
400     {
401         for( int i = 0; i < BDSM_LOGIN_DIALOG_RETRY; i++ )
402         {
403             login_dialog( p_access );
404             smb_session_set_creds( p_sys->p_session, p_sys->creds.domain,
405                                    p_sys->creds.login, p_sys->creds.password );
406             if( smb_session_login( p_sys->p_session ) )
407                 return VLC_SUCCESS;
408         }
409
410         /* FIXME, Try to force netbios name as domain then WORKGROUP here */
411         msg_Err( p_access, "Unable to login with username = %s, password = %s, domain = %s",
412                    p_sys->creds.login, p_sys->creds.password,
413                    p_sys->creds.domain );
414         return VLC_EGENERIC;
415     }
416     else if( smb_session_is_guest( p_sys->p_session ) == 1 )
417         msg_Warn( p_access, "Login failure but you were logged in as a Guest");
418
419     return VLC_SUCCESS;
420 }
421
422 static void backslash_path( vlc_url_t *p_url )
423 {
424     char *iter = p_url->psz_path;
425
426     /* Let's switch the path delimiters from / to \ */
427     while( *iter != '\0' )
428     {
429         if( *iter == '/' )
430             *iter = '\\';
431         iter++;
432     }
433 }
434
435 /* Get the share and filepath from uri (also replace all / by \ in url.psz_path) */
436 static bool get_path( access_t *p_access )
437 {
438     access_sys_t *p_sys = p_access->p_sys;
439     char *iter;
440
441     if( p_sys->url.psz_path == NULL )
442         return false;
443
444     backslash_path( &p_sys->url );
445
446     /* Is path longer than just "/" ? */
447     if( strlen( p_sys->url.psz_path ) > 1 )
448     {
449         iter = p_sys->url.psz_path;
450         while( *iter == '\\' ) iter++; /* Handle smb://Host/////Share/ */
451
452         p_sys->psz_share = strdup( iter );
453         if ( p_sys->psz_share == NULL )
454             return false;
455     }
456     else
457     {
458         msg_Dbg( p_access, "no share, nor file path provided, will switch to browser");
459         return false;
460     }
461
462
463     iter = strchr( p_sys->psz_share, '\\' );
464     if( iter == NULL || strlen(iter + 1) == 0 )
465     {
466         if( iter != NULL ) /* Remove the trailing \ */
467             *iter = '\0';
468         p_sys->psz_path = strdup( "" );
469
470         msg_Dbg( p_access, "no file path provided, will switch to browser ");
471         return true;
472     }
473
474     p_sys->psz_path = strdup( iter + 1); /* Skip the first \ */
475     *iter = '\0';
476     if( p_sys->psz_path == NULL )
477         return false;
478
479     return true;
480 }
481
482 /*****************************************************************************
483  * Seek: try to go at the right place
484  *****************************************************************************/
485 static int Seek( access_t *p_access, uint64_t i_pos )
486 {
487     access_sys_t *p_sys = p_access->p_sys;
488     int64_t      i_ret;
489
490     if( i_pos >= INT64_MAX )
491         return VLC_EGENERIC;
492
493     msg_Dbg( p_access, "seeking to %"PRId64, i_pos );
494
495     /* seek cannot fail in bdsm, but the subsequent read can */
496     i_ret = smb_fseek(p_sys->p_session, p_sys->i_fd, i_pos, SMB_SEEK_SET);
497
498     p_access->info.b_eof = false;
499     p_access->info.i_pos = i_ret;
500
501     return VLC_SUCCESS;
502 }
503
504 /*****************************************************************************
505  * Read:
506  *****************************************************************************/
507 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
508 {
509     access_sys_t *p_sys = p_access->p_sys;
510     int i_read;
511
512     if( p_access->info.b_eof ) return 0;
513
514     i_read = smb_fread( p_sys->p_session, p_sys->i_fd, p_buffer, i_len );
515     if( i_read < 0 )
516     {
517         msg_Err( p_access, "read failed" );
518         return -1;
519     }
520
521     if( i_read == 0 ) p_access->info.b_eof = true;
522     else if( i_read > 0 ) p_access->info.i_pos += i_read;
523
524     return i_read;
525 }
526
527 /*****************************************************************************
528  * Control:
529  *****************************************************************************/
530 static int Control( access_t *p_access, int i_query, va_list args )
531 {
532     switch( i_query )
533     {
534     case ACCESS_CAN_SEEK:
535     case ACCESS_CAN_FASTSEEK:
536     case ACCESS_CAN_PAUSE:
537     case ACCESS_CAN_CONTROL_PACE:
538         *va_arg( args, bool* ) = true;
539         break;
540
541     case ACCESS_GET_SIZE:
542     {
543         smb_stat st = smb_stat_fd( p_access->p_sys->p_session,
544                                    p_access->p_sys->i_fd );
545         *va_arg( args, uint64_t * ) = smb_stat_get( st, SMB_STAT_SIZE );
546         break;
547     }
548     case ACCESS_GET_PTS_DELAY:
549         *va_arg( args, int64_t * ) = INT64_C(1000)
550             * var_InheritInteger( p_access, "network-caching" );
551         break;
552
553     case ACCESS_SET_PAUSE_STATE:
554         /* Nothing to do */
555         break;
556
557     default:
558         return VLC_EGENERIC;
559     }
560
561     return VLC_SUCCESS;
562 }
563
564 static int add_item( access_t *p_access, input_item_node_t *p_node,
565                      const char *psz_name )
566 {
567     access_sys_t *p_sys = p_access->p_sys;
568     input_item_t *p_item;
569     char         *psz_uri, *psz_option;
570     int           i_ret;
571
572     i_ret = asprintf( &psz_uri, "%s/%s", p_node->p_item->psz_uri, psz_name );
573     if( i_ret == -1 )
574         return VLC_ENOMEM;
575
576     p_item = input_item_New( psz_uri, psz_name );
577     free( psz_uri );
578     if( p_item == NULL )
579         return VLC_ENOMEM;
580
581     /* Here we save on the node the credentials that allowed us to login.
582      * That way the user isn't prompted more than once for credentials */
583     i_ret = asprintf( &psz_option, "smb-user=%s", p_sys->creds.login );
584     if( i_ret == -1 )
585         return VLC_ENOMEM;
586     input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
587     free( psz_option );
588
589     i_ret = asprintf( &psz_option, "smb-pwd=%s", p_sys->creds.password );
590     if( i_ret == -1 )
591         return VLC_ENOMEM;
592     input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
593     free( psz_option );
594
595     i_ret = asprintf( &psz_option, "smb-domain=%s", p_sys->creds.domain );
596     if( i_ret == -1 )
597         return VLC_ENOMEM;
598     input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
599     free( psz_option );
600
601     input_item_CopyOptions( p_node->p_item, p_item );
602     i_ret = input_item_node_AppendItem( p_node, p_item ) != NULL ? VLC_SUCCESS
603                                                                  : VLC_EGENERIC;
604
605     input_item_Release( p_item );
606     return i_ret;
607 }
608
609 static int BrowseShare( access_t *p_access, input_item_node_t *p_node )
610 {
611     access_sys_t *p_sys = p_access->p_sys;
612     smb_share_list  shares;
613     const char     *psz_name;
614     size_t          share_count;
615     int             i_ret;
616
617     share_count = smb_share_get_list( p_sys->p_session, &shares );
618     if( !share_count )
619         return VLC_ENOITEM;
620
621     for( size_t i = 0; i < share_count; i++ )
622     {
623         psz_name = smb_share_list_at( shares, i );
624
625         if( psz_name[strlen( psz_name ) - 1] == '$')
626             continue;
627
628         i_ret = add_item( p_access, p_node, psz_name );
629         if( i_ret != VLC_SUCCESS )
630             goto error;
631     }
632
633     smb_share_list_destroy( shares );
634     return VLC_SUCCESS;
635     error:
636         smb_share_list_destroy( shares );
637         return i_ret;
638 }
639
640 static int BrowseDirectory( access_t *p_access, input_item_node_t *p_node )
641 {
642     access_sys_t *p_sys = p_access->p_sys;
643     smb_stat_list   files;
644     smb_stat        st;
645     char           *psz_query;
646     const char     *psz_name;
647     size_t          files_count;
648     int             i_ret;
649
650     if( p_sys->psz_path != NULL )
651     {
652         i_ret = asprintf( &psz_query, "%s\\*", p_sys->psz_path );
653         if( i_ret == -1 )
654             return VLC_ENOMEM;
655         files = smb_find( p_sys->p_session, p_sys->i_tid, psz_query );
656         free( psz_query );
657     }
658     else
659         files = smb_find( p_sys->p_session, p_sys->i_tid, "\\*" );
660
661     if( files == NULL )
662         return VLC_ENOITEM;
663
664     files_count = smb_stat_list_count( files );
665     for( size_t i = 0; i < files_count; i++ )
666     {
667         st = smb_stat_list_at( files, i );
668
669         if( st == NULL ) {
670             i_ret = VLC_ENOITEM;
671             goto error;
672         }
673
674         psz_name = smb_stat_name( st );
675
676         /* Avoid infinite loop */
677         if( !strcmp( psz_name, ".") || !strcmp( psz_name, "..") )
678             continue;
679
680         i_ret = add_item( p_access, p_node, psz_name );
681         if( i_ret != VLC_SUCCESS )
682             goto error;
683     }
684
685     smb_stat_list_destroy( files );
686     return VLC_SUCCESS;
687
688     error:
689         smb_stat_list_destroy( files );
690         return i_ret;
691 }
692
693 static int BrowserControl( access_t *p_access, int i_query, va_list args )
694 {
695     VLC_UNUSED( p_access );
696
697     switch( i_query )
698     {
699         case ACCESS_CAN_SEEK:
700         case ACCESS_CAN_FASTSEEK:
701             *va_arg( args, bool* ) = false;
702             break;
703
704         case ACCESS_CAN_PAUSE:
705         case ACCESS_CAN_CONTROL_PACE:
706             *va_arg( args, bool* ) = true;
707             break;
708
709         case ACCESS_GET_PTS_DELAY:
710             *va_arg( args, int64_t * ) = DEFAULT_PTS_DELAY * 1000;
711             break;
712
713         default:
714             return VLC_EGENERIC;
715      }
716      return VLC_SUCCESS;
717 }
718
719 static int BrowserInit( access_t *p_access )
720 {
721     access_sys_t *p_sys = p_access->p_sys;
722
723     if( p_sys->psz_share == NULL )
724         p_access->pf_readdir = BrowseShare;
725     else
726         p_access->pf_readdir = BrowseDirectory;
727     p_access->pf_control = BrowserControl;
728
729     return VLC_SUCCESS;
730 }