url: remove vlc_UrlParse() option parameter
[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_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_("Username")
59 #define USER_LONGTEXT N_("Username that will be used for the connection, " \
60         "if no username is set in the URL.")
61 #define PASS_TEXT N_("Password")
62 #define PASS_LONGTEXT N_("Password that will be used for the connection, " \
63         "if no username or password are set in URL.")
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         add_shortcut( "dsm-sd" )
87         set_description( N_("libdsm NETBIOS discovery module") )
88         set_category( CAT_PLAYLIST )
89         set_subcategory( SUBCAT_PLAYLIST_SD )
90         set_capability( "services_discovery", 0 )
91         set_callbacks( bdsm_SdOpen, bdsm_SdClose )
92
93         VLC_SD_PROBE_SUBMODULE
94
95 vlc_module_end ()
96
97 /*****************************************************************************
98  * Local prototypes
99  *****************************************************************************/
100 static ssize_t Read( access_t *, uint8_t *, size_t );
101 static int Seek( access_t *, uint64_t );
102 static int Control( access_t *, int, va_list );
103 static int BrowserInit( access_t *p_access );
104
105 static void split_domain_login( char **psz_login, char **psz_domain );
106 static void get_credentials( access_t *p_access );
107 static int get_address( access_t *p_access );
108 static void login_dialog( access_t *p_access );
109 static int login( access_t *p_access );
110 static void backslash_path( vlc_url_t *p_url );
111 static bool get_path( access_t *p_access );
112 static input_item_t* new_item( access_t *p_access, const char *psz_name, int i_type );
113
114 struct access_sys_t
115 {
116     netbios_ns         *p_ns;               /**< Netbios name service */
117     smb_session        *p_session;          /**< bdsm SMB Session object */
118     smb_creds           creds;              /**< Credentials used to connect */
119
120     vlc_url_t           url;
121     char               *psz_share;
122     char               *psz_path;
123
124     char                netbios_name[16];
125     struct in_addr      addr;
126
127     smb_fd              i_fd;               /**< SMB fd for the file we're reading */
128     smb_tid             i_tid;              /**< SMB Tree ID we're connected to */
129
130     size_t              i_browse_count;
131     size_t              i_browse_idx;
132     smb_share_list      shares;
133     smb_stat_list       files;
134 };
135
136 /*****************************************************************************
137  * Dialog strings
138  *****************************************************************************/
139 #define BDSM_LOGIN_DIALOG_TITLE N_( "%s: Authentication required" )
140 #define BDSM_LOGIN_DIALOG_TEXT N_( "The computer you are trying to connect "   \
141     "to requires authentication.\n Please provide a username (and ideally a "  \
142     "domain name using the format DOMAIN\\username)\n and a password." )
143
144 /*****************************************************************************
145  * Open: Initialize module's data structures and libdsm
146  *****************************************************************************/
147 static int Open( vlc_object_t *p_this )
148 {
149     access_t     *p_access = (access_t*)p_this;
150     access_sys_t *p_sys;
151     smb_stat st;
152
153     /* Init p_access */
154     access_InitFields( p_access );
155     p_sys = p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
156     if( p_access->p_sys == NULL )
157         return VLC_ENOMEM;
158
159     p_sys->p_ns = netbios_ns_new();
160     if( p_sys->p_ns == NULL )
161         goto error;
162
163     p_sys->p_session = smb_session_new();
164     if( p_sys->p_session == NULL )
165         goto error;
166
167     vlc_UrlParse( &p_sys->url, p_access->psz_location );
168     get_credentials( p_access );
169     if( get_address( p_access ) != VLC_SUCCESS )
170         goto error;
171
172     msg_Dbg( p_access, "Creds: username = %s, domain = %s",
173              p_sys->creds.login, p_sys->creds.domain );
174     msg_Dbg( p_access, "Session: Host name = %s, ip = %s", p_sys->netbios_name,
175              inet_ntoa( p_sys->addr ) );
176
177     /* Now that we have the required data, let's establish a session */
178     if( !smb_session_connect( p_sys->p_session, p_sys->netbios_name,
179                               p_sys->addr.s_addr, SMB_TRANSPORT_TCP ) )
180     {
181         msg_Err( p_access, "Unable to connect/negotiate SMB session");
182         goto error;
183     }
184
185     get_path( p_access );
186
187     if( login( p_access ) != VLC_SUCCESS )
188     {
189         msg_Err( p_access, "Unable to connect to share %s", p_sys->psz_share );
190         goto error;
191     }
192
193     /* If there is no shares, browse them */
194     if( !p_sys->psz_share )
195         return BrowserInit( p_access );
196
197     msg_Dbg( p_access, "Path: Share name = %s, path = %s", p_sys->psz_share,
198              p_sys->psz_path );
199
200     /* Let's finally ask a handle to the file we wanna read ! */
201     p_sys->i_fd = smb_fopen( p_sys->p_session, p_sys->i_tid, p_sys->psz_path,
202                              SMB_MOD_RO );
203     if( !p_sys->i_fd )
204     {
205         msg_Err( p_access, "Unable to open file with path %s (in share %s)",
206                  p_sys->psz_path, p_sys->psz_share );
207         goto error;
208     }
209
210     st = smb_stat_fd( p_sys->p_session, p_sys->i_fd );
211     if( smb_stat_get( st, SMB_STAT_ISDIR ) )
212     {
213         smb_fclose( p_sys->p_session, p_sys->i_fd );
214         return BrowserInit( p_access );
215     }
216
217     msg_Dbg( p_access, "Successfully opened smb://%s", p_access->psz_location );
218
219     ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
220     return VLC_SUCCESS;
221
222     error:
223         Close( p_this );
224         return VLC_EGENERIC;
225 }
226
227 /*****************************************************************************
228  * Close: free unused data structures
229  *****************************************************************************/
230 static void Close( vlc_object_t *p_this )
231 {
232     access_t     *p_access = (access_t*)p_this;
233     access_sys_t *p_sys = p_access->p_sys;
234
235     if( p_sys->p_ns )
236         netbios_ns_destroy( p_sys->p_ns );
237     if( p_sys->i_fd )
238         smb_fclose( p_sys->p_session, p_sys->i_fd );
239     if( p_sys->p_session )
240         smb_session_destroy( p_sys->p_session );
241     vlc_UrlClean( &p_sys->url );
242     if( p_sys->shares )
243         smb_share_list_destroy( p_sys->shares );
244     if( p_sys->files )
245         smb_stat_list_destroy( p_sys->files );
246     free( p_sys->creds.login );
247     free( p_sys->creds.password );
248     free( p_sys->creds.domain );
249     free( p_sys->psz_share );
250     free( p_sys->psz_path );
251     free( p_sys );
252 }
253
254 /*****************************************************************************
255  * Local functions
256  *****************************************************************************/
257
258 /* Split DOMAIN\User if it finds a '\' in psz_login. */
259 static void split_domain_login( char **psz_login, char **psz_domain )
260 {
261     char *user = strchr( *psz_login, '\\' );
262
263     if( user != NULL )
264     {
265         *psz_domain = *psz_login;
266         *user = '\0';
267         *psz_login = strdup( user + 1 );
268     }
269 }
270
271 /* Get credentials from uri or variables. */
272 static void get_credentials( access_t *p_access )
273 {
274     access_sys_t *p_sys = p_access->p_sys;
275
276     /* Fetch credentials, either from URI or from options if not provided */
277     if( p_sys->url.psz_password == NULL )
278         p_sys->creds.password = var_InheritString( p_access, "smb-pwd" );
279     else
280         p_sys->creds.password = strdup( p_sys->url.psz_password );
281
282     /* Here we support smb://DOMAIN\User:password@XXX, get user from options
283        or default to "Guest" as last resort */
284     if( p_sys->url.psz_username != NULL )
285     {
286         p_sys->creds.login = strdup( p_sys->url.psz_username );
287         split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
288     }
289     else
290         p_sys->creds.login = var_InheritString( p_access, "smb-user" );
291
292     if( p_sys->creds.domain == NULL )
293         p_sys->creds.domain = var_InheritString( p_access, "smb-domain" );
294 }
295
296 /* Returns VLC_EGENERIC if it wasn't able to get an ip address to connect to */
297 static int get_address( access_t *p_access )
298 {
299     access_sys_t *p_sys = p_access->p_sys;
300
301     if( !inet_aton( p_sys->url.psz_host, &p_sys->addr ) )
302     {
303         /* This is not an ip address, let's try netbios/dns resolve */
304         struct addrinfo *p_info = NULL;
305
306         /* Is this a netbios name on this LAN ? */
307         if( netbios_ns_resolve( p_sys->p_ns, p_sys->url.psz_host,
308                                 NETBIOS_FILESERVER,
309                                 &p_sys->addr.s_addr) )
310         {
311             strlcpy( p_sys->netbios_name, p_sys->url.psz_host, 16);
312             return VLC_SUCCESS;
313         }
314         /* or is it an existing dns name ? */
315         else if( getaddrinfo( p_sys->url.psz_host, NULL, NULL, &p_info ) == 0 )
316         {
317             if( p_info->ai_family == AF_INET )
318             {
319                 struct sockaddr_in *in = (struct sockaddr_in *)p_info->ai_addr;
320                 p_sys->addr.s_addr = in->sin_addr.s_addr;
321             }
322             freeaddrinfo( p_info );
323             if( p_info->ai_family != AF_INET )
324                 return VLC_EGENERIC;
325         }
326         else
327             return VLC_EGENERIC;
328     }
329
330     /* We have an IP address, let's find the NETBIOS name */
331     const char *psz_nbt = netbios_ns_inverse( p_sys->p_ns, p_sys->addr.s_addr );
332     if( psz_nbt != NULL )
333         strlcpy( p_sys->netbios_name, psz_nbt, 16 );
334     else
335     {
336         msg_Warn( p_access, "Unable to get netbios name of %s",
337             p_sys->url.psz_host );
338         p_sys->netbios_name[0] = '\0';
339     }
340
341     /* If no domain was explicitly specified, let's use the machine name */
342     if( p_sys->creds.domain == NULL && p_sys->netbios_name[0] )
343         p_sys->creds.domain = strdup( p_sys->netbios_name );
344
345     return VLC_SUCCESS;
346 }
347
348 /* Displays a dialog for the user to enter his/her credentials */
349 static void login_dialog( access_t *p_access )
350 {
351     access_sys_t *p_sys = p_access->p_sys;
352
353     char *psz_login = NULL, *psz_pass = NULL, *psz_title;
354     int i_ret;
355
356     i_ret = asprintf( &psz_title, BDSM_LOGIN_DIALOG_TITLE, p_sys->netbios_name );
357     if( i_ret != -1 )
358         dialog_Login( p_access, &psz_login, &psz_pass, psz_title,
359                       BDSM_LOGIN_DIALOG_TEXT );
360     else
361         dialog_Login( p_access, &psz_login, &psz_pass, BDSM_LOGIN_DIALOG_TITLE,
362                       BDSM_LOGIN_DIALOG_TEXT );
363     free( psz_title );
364
365     if( psz_login != NULL )
366     {
367         free( p_sys->creds.login );
368         p_sys->creds.login = psz_login;
369         split_domain_login( &p_sys->creds.login, &p_sys->creds.domain );
370     }
371
372     if( psz_pass != NULL )
373     {
374         free( p_sys->creds.password );
375         p_sys->creds.password = psz_pass;
376     }
377 }
378
379 static int smb_connect( access_t *p_access )
380 {
381     access_sys_t *p_sys = p_access->p_sys;
382     const char *psz_login = p_sys->creds.login ?
383                             p_sys->creds.login : "Guest";
384     const char *psz_password = p_sys->creds.password ?
385                                p_sys->creds.password : "Guest";
386     const char *psz_domain = p_sys->creds.domain ?
387                              p_sys->creds.domain : p_sys->netbios_name;
388
389     smb_session_set_creds( p_sys->p_session, psz_domain,
390                            psz_login, psz_password );
391     if( smb_session_login( p_sys->p_session ) )
392     {
393         if( p_sys->psz_share )
394         {
395             /* Connect to the share */
396             p_sys->i_tid = smb_tree_connect( p_sys->p_session, p_sys->psz_share );
397             if( !p_sys->i_tid )
398                 return VLC_EGENERIC;
399         }
400         return VLC_SUCCESS;
401     }
402     else
403         return VLC_EGENERIC;
404 }
405
406 /* Performs login with existing credentials and ask the user for new ones on
407    failure */
408 static int login( access_t *p_access )
409 {
410     access_sys_t *p_sys = p_access->p_sys;
411
412     /* Try to authenticate on the remote machine */
413     if( smb_connect( p_access ) != VLC_SUCCESS )
414     {
415         for( int i = 0; i < BDSM_LOGIN_DIALOG_RETRY; i++ )
416         {
417             login_dialog( p_access );
418             if( smb_connect( p_access ) == VLC_SUCCESS )
419                 return VLC_SUCCESS;
420         }
421
422         msg_Err( p_access, "Unable to login with username = %s, domain = %s",
423                    p_sys->creds.login, p_sys->creds.domain );
424         return VLC_EGENERIC;
425     }
426     else if( smb_session_is_guest( p_sys->p_session )  )
427         msg_Warn( p_access, "Login failure but you were logged in as a Guest");
428
429     return VLC_SUCCESS;
430 }
431
432 static void backslash_path( vlc_url_t *p_url )
433 {
434     char *iter = p_url->psz_path;
435
436     /* Let's switch the path delimiters from / to \ */
437     while( *iter != '\0' )
438     {
439         if( *iter == '/' )
440             *iter = '\\';
441         iter++;
442     }
443 }
444
445 /* Get the share and filepath from uri (also replace all / by \ in url.psz_path) */
446 static bool get_path( access_t *p_access )
447 {
448     access_sys_t *p_sys = p_access->p_sys;
449     char *iter;
450
451     if( p_sys->url.psz_path == NULL )
452         return false;
453
454     backslash_path( &p_sys->url );
455
456     /* Is path longer than just "/" ? */
457     if( strlen( p_sys->url.psz_path ) > 1 )
458     {
459         iter = p_sys->url.psz_path;
460         while( *iter == '\\' ) iter++; /* Handle smb://Host/////Share/ */
461
462         p_sys->psz_share = strdup( iter );
463         if ( p_sys->psz_share == NULL )
464             return false;
465     }
466     else
467     {
468         msg_Dbg( p_access, "no share, nor file path provided, will switch to browser");
469         return false;
470     }
471
472
473     iter = strchr( p_sys->psz_share, '\\' );
474     if( iter == NULL || strlen(iter + 1) == 0 )
475     {
476         if( iter != NULL ) /* Remove the trailing \ */
477             *iter = '\0';
478         p_sys->psz_path = strdup( "" );
479
480         msg_Dbg( p_access, "no file path provided, will switch to browser ");
481         return true;
482     }
483
484     p_sys->psz_path = strdup( iter + 1); /* Skip the first \ */
485     *iter = '\0';
486     if( p_sys->psz_path == NULL )
487         return false;
488
489     return true;
490 }
491
492 /*****************************************************************************
493  * Seek: try to go at the right place
494  *****************************************************************************/
495 static int Seek( access_t *p_access, uint64_t i_pos )
496 {
497     access_sys_t *p_sys = p_access->p_sys;
498     int64_t      i_ret;
499
500     if( i_pos >= INT64_MAX )
501         return VLC_EGENERIC;
502
503     msg_Dbg( p_access, "seeking to %"PRId64, i_pos );
504
505     /* seek cannot fail in bdsm, but the subsequent read can */
506     i_ret = smb_fseek(p_sys->p_session, p_sys->i_fd, i_pos, SMB_SEEK_SET);
507
508     p_access->info.b_eof = false;
509     p_access->info.i_pos = i_ret;
510
511     return VLC_SUCCESS;
512 }
513
514 /*****************************************************************************
515  * Read:
516  *****************************************************************************/
517 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
518 {
519     access_sys_t *p_sys = p_access->p_sys;
520     int i_read;
521
522     if( p_access->info.b_eof ) return 0;
523
524     i_read = smb_fread( p_sys->p_session, p_sys->i_fd, p_buffer, i_len );
525     if( i_read < 0 )
526     {
527         msg_Err( p_access, "read failed" );
528         return -1;
529     }
530
531     if( i_read == 0 ) p_access->info.b_eof = true;
532     else if( i_read > 0 ) p_access->info.i_pos += i_read;
533
534     return i_read;
535 }
536
537 /*****************************************************************************
538  * Control:
539  *****************************************************************************/
540 static int Control( access_t *p_access, int i_query, va_list args )
541 {
542     switch( i_query )
543     {
544     case ACCESS_CAN_SEEK:
545     case ACCESS_CAN_FASTSEEK:
546     case ACCESS_CAN_PAUSE:
547     case ACCESS_CAN_CONTROL_PACE:
548         *va_arg( args, bool* ) = true;
549         break;
550
551     case ACCESS_GET_SIZE:
552     {
553         smb_stat st = smb_stat_fd( p_access->p_sys->p_session,
554                                    p_access->p_sys->i_fd );
555         *va_arg( args, uint64_t * ) = smb_stat_get( st, SMB_STAT_SIZE );
556         break;
557     }
558     case ACCESS_GET_PTS_DELAY:
559         *va_arg( args, int64_t * ) = INT64_C(1000)
560             * var_InheritInteger( p_access, "network-caching" );
561         break;
562
563     case ACCESS_SET_PAUSE_STATE:
564         /* Nothing to do */
565         break;
566
567     default:
568         return VLC_EGENERIC;
569     }
570
571     return VLC_SUCCESS;
572 }
573
574 static input_item_t *new_item( access_t *p_access, const char *psz_name,
575                                int i_type )
576 {
577     access_sys_t *p_sys = p_access->p_sys;
578     input_item_t *p_item;
579     char         *psz_uri, *psz_option = NULL;
580     int           i_ret;
581
582     i_ret = asprintf( &psz_uri, "smb://%s/%s", p_access->psz_location, psz_name );
583     if( i_ret == -1 )
584         return NULL;
585
586     p_item = input_item_NewWithTypeExt( psz_uri, psz_name, 0, NULL, 0, -1,
587                                         i_type, 1 );
588     free( psz_uri );
589     if( p_item == NULL )
590         return NULL;
591
592     /* Here we save on the node the credentials that allowed us to login.
593      * That way the user isn't prompted more than once for credentials */
594     if( p_sys->creds.login )
595     {
596         i_ret = asprintf( &psz_option, "smb-user=%s", p_sys->creds.login );
597         if( i_ret == -1 )
598             goto bailout;
599         input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
600         free( psz_option );
601     }
602
603     if( p_sys->creds.password )
604     {
605         i_ret = asprintf( &psz_option, "smb-pwd=%s", p_sys->creds.password );
606         if( i_ret == -1 )
607             goto bailout;
608         input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
609         free( psz_option );
610     }
611
612     if( p_sys->creds.domain )
613     {
614         i_ret = asprintf( &psz_option, "smb-domain=%s", p_sys->creds.domain );
615         if( i_ret == -1 )
616             goto bailout;
617         input_item_AddOption( p_item, psz_option, VLC_INPUT_OPTION_TRUSTED );
618         free( psz_option );
619     }
620
621     return p_item;
622 bailout:
623     if( p_item )
624         input_item_Release( p_item );
625     free( psz_option );
626     return NULL;
627 }
628
629 static input_item_t* BrowseShare( access_t *p_access )
630 {
631     access_sys_t *p_sys = p_access->p_sys;
632     const char     *psz_name;
633     input_item_t   *p_item = NULL;
634
635     if( !p_sys->i_browse_count )
636         p_sys->i_browse_count = smb_share_get_list( p_sys->p_session,
637                                                     &p_sys->shares );
638     for( ; !p_item && p_sys->i_browse_idx < p_sys->i_browse_count
639          ; p_sys->i_browse_idx++ )
640     {
641         psz_name = smb_share_list_at( p_sys->shares, p_sys->i_browse_idx );
642
643         if( psz_name[strlen( psz_name ) - 1] == '$')
644             continue;
645
646         p_item = new_item( p_access, psz_name, ITEM_TYPE_DIRECTORY );
647         if( !p_item )
648             return NULL;
649     }
650     return p_item;
651 }
652
653 static input_item_t* BrowseDirectory( access_t *p_access )
654 {
655     access_sys_t *p_sys = p_access->p_sys;
656     smb_stat        st;
657     input_item_t   *p_item = NULL;
658     char           *psz_query;
659     const char     *psz_name;
660     int             i_ret;
661
662     if( !p_sys->i_browse_count )
663     {
664         if( p_sys->psz_path != NULL )
665         {
666             i_ret = asprintf( &psz_query, "%s\\*", p_sys->psz_path );
667             if( i_ret == -1 )
668                 return NULL;
669             p_sys->files = smb_find( p_sys->p_session, p_sys->i_tid, psz_query );
670             free( psz_query );
671         }
672         else
673             p_sys->files = smb_find( p_sys->p_session, p_sys->i_tid, "\\*" );
674         if( p_sys->files == NULL )
675             return NULL;
676         p_sys->i_browse_count = smb_stat_list_count( p_sys->files );
677     }
678
679     if( p_sys->i_browse_idx < p_sys->i_browse_count )
680     {
681         int i_type;
682
683         st = smb_stat_list_at( p_sys->files, p_sys->i_browse_idx++ );
684
685         if( st == NULL )
686             return NULL;
687
688         psz_name = smb_stat_name( st );
689
690         i_type = smb_stat_get( st, SMB_STAT_ISDIR ) ?
691                  ITEM_TYPE_DIRECTORY : ITEM_TYPE_FILE;
692         p_item = new_item( p_access, psz_name, i_type );
693         if( !p_item )
694             return NULL;
695     }
696     return p_item;
697 }
698
699 static int BrowserInit( access_t *p_access )
700 {
701     access_sys_t *p_sys = p_access->p_sys;
702
703     if( p_sys->psz_share == NULL )
704         p_access->pf_readdir = BrowseShare;
705     else
706     {
707         p_access->pf_readdir = BrowseDirectory;
708         p_access->info.b_dir_can_loop = true;
709     }
710     p_access->pf_control = access_vaDirectoryControlHelper;
711
712     return VLC_SUCCESS;
713 }