1 /*****************************************************************************
2  * smb.c: SMB input module
3  *****************************************************************************
4  * Copyright (C) 2001-2015 VLC authors and VideoLAN
5  * $Id: 3a299d2c6508792753934baf1838c0b7c93c11b1 $
6  *
7  * Authors: Gildas Bazin <gbazin@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #include <assert.h>
32 #include <errno.h>
33 #ifdef _WIN32
34 #   include <fcntl.h>
35 #   include <sys/stat.h>
36 #   include <io.h>
37 #   include <windows.h>
38 #   include <lm.h>
39 #   define smbc_open(a,b,c) vlc_open(a,b,c)
40 #   define smbc_stat(a,b) _stati64(a,b)
41 #   define smbc_read read
42 #   define smbc_lseek _lseeki64
43 #   define smbc_close close
44 #else
45 #   include <libsmbclient.h>
46 #endif
47 
48 #include <vlc_common.h>
49 #include <vlc_fs.h>
50 #include <vlc_plugin.h>
51 #include <vlc_access.h>
52 #include <vlc_input_item.h>
53 #include <vlc_url.h>
54 #include <vlc_keystore.h>
55 #include <vlc_charset.h>
56 
57 #include "smb_common.h"
58 
59 /*****************************************************************************
60  * Module descriptor
61  *****************************************************************************/
62 static int  Open ( vlc_object_t * );
63 static void Close( vlc_object_t * );
64 
65 #define SMB_HELP N_("Samba (Windows network shares) input")
66 vlc_module_begin ()
67     set_shortname( "SMB" )
68     set_description( N_("SMB input") )
69     set_help(SMB_HELP)
70     set_capability( "access", 0 )
71     set_category( CAT_INPUT )
72     set_subcategory( SUBCAT_INPUT_ACCESS )
73     add_string( "smb-user", NULL, SMB_USER_TEXT, SMB_USER_LONGTEXT,
74                 false )
75     add_password( "smb-pwd", NULL, SMB_PASS_TEXT,
76                   SMB_PASS_LONGTEXT, false )
77     add_string( "smb-domain", NULL, SMB_DOMAIN_TEXT,
78                 SMB_DOMAIN_LONGTEXT, false )
79     add_shortcut( "smb" )
80     set_callbacks( Open, Close )
81 vlc_module_end ()
82 
83 /*****************************************************************************
84  * Local prototypes
85  *****************************************************************************/
86 static ssize_t Read( stream_t *, void *, size_t );
87 static int Seek( stream_t *, uint64_t );
88 static int Control( stream_t *, int, va_list );
89 static int DirRead( stream_t *, input_item_node_t * );
90 
91 struct access_sys_t
92 {
93     int i_smb;
94     uint64_t size;
95     vlc_url_t url;
96 };
97 
98 #ifdef _WIN32
99 static void Win32AddConnection( stream_t *, const char *, const char *, const char *, const char *, const char * );
100 #else
smb_auth(const char * srv,const char * shr,char * wg,int wglen,char * un,int unlen,char * pw,int pwlen)101 static void smb_auth( const char *srv, const char *shr, char *wg, int wglen,
102                       char *un, int unlen, char *pw, int pwlen )
103 {
104     VLC_UNUSED(srv);
105     VLC_UNUSED(shr);
106     VLC_UNUSED(wg);
107     VLC_UNUSED(wglen);
108     VLC_UNUSED(un);
109     VLC_UNUSED(unlen);
110     VLC_UNUSED(pw);
111     VLC_UNUSED(pwlen);
112     //wglen = unlen = pwlen = 0;
113 }
114 #endif
115 
116 /* Build an SMB URI
117  * smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]] */
smb_get_uri(stream_t * p_access,char ** ppsz_uri,const char * psz_domain,const char * psz_user,const char * psz_pwd,const char * psz_server,const char * psz_share_path,const char * psz_name)118 static int smb_get_uri( stream_t *p_access, char **ppsz_uri,
119                         const char *psz_domain,
120                         const char *psz_user, const char *psz_pwd,
121                         const char *psz_server, const char *psz_share_path,
122                         const char *psz_name )
123 {
124     assert(psz_server);
125 #define PSZ_SHARE_PATH_OR_NULL psz_share_path ? psz_share_path : ""
126 #define PSZ_NAME_OR_NULL psz_name ? "/" : "", psz_name ? psz_name : ""
127 #ifdef _WIN32
128     if( psz_user )
129         Win32AddConnection( p_access, psz_server, psz_share_path,
130                             psz_user, psz_pwd, psz_domain );
131     return asprintf( ppsz_uri, "//%s%s%s%s", psz_server, PSZ_SHARE_PATH_OR_NULL,
132                      PSZ_NAME_OR_NULL );
133 #else
134     (void) p_access;
135     if( psz_user )
136         return asprintf( ppsz_uri, "smb://%s%s%s%s%s@%s%s%s%s",
137                          psz_domain ? psz_domain : "", psz_domain ? ";" : "",
138                          psz_user, psz_pwd ? ":" : "",
139                          psz_pwd ? psz_pwd : "", psz_server,
140                          PSZ_SHARE_PATH_OR_NULL, PSZ_NAME_OR_NULL );
141     else
142         return asprintf( ppsz_uri, "smb://%s%s%s%s", psz_server,
143                          PSZ_SHARE_PATH_OR_NULL, PSZ_NAME_OR_NULL );
144 #endif
145 }
146 
147 /****************************************************************************
148  * Open: connect to smb server and ask for file
149  ****************************************************************************/
Open(vlc_object_t * p_this)150 static int Open( vlc_object_t *p_this )
151 {
152     stream_t     *p_access = (stream_t*)p_this;
153     access_sys_t *p_sys;
154     struct stat  filestat;
155     vlc_url_t    url;
156     vlc_credential credential;
157     char         *psz_decoded_path = NULL, *psz_uri = NULL,
158                  *psz_var_domain = NULL;
159     int          i_ret;
160     int          i_smb;
161     uint64_t     i_size;
162     bool         b_is_dir = false;
163 
164 #ifndef _WIN32
165     if( smbc_init( smb_auth, 0 ) )
166         return VLC_EGENERIC;
167 #endif
168 
169 /*
170 ** some version of glibc defines open as a macro, causing havoc
171 ** with other macros using 'open' under the hood, such as the
172 ** following one:
173 */
174 #if defined(smbc_open) && defined(open)
175 # undef open
176 #endif
177 
178     if( vlc_UrlParseFixup( &url, p_access->psz_url ) != 0 )
179     {
180         vlc_UrlClean( &url );
181         return VLC_EGENERIC;
182     }
183     if( url.psz_path )
184     {
185         psz_decoded_path = vlc_uri_decode_duplicate( url.psz_path );
186         if( !psz_decoded_path )
187         {
188             vlc_UrlClean( &url );
189             return VLC_EGENERIC;
190         }
191     }
192 
193     vlc_credential_init( &credential, &url );
194     psz_var_domain = var_InheritString( p_access, "smb-domain" );
195     credential.psz_realm = psz_var_domain;
196     vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
197                         NULL, NULL );
198     for (;;)
199     {
200         if( smb_get_uri( p_access, &psz_uri, credential.psz_realm,
201                          credential.psz_username, credential.psz_password,
202                          url.psz_host, psz_decoded_path, NULL ) == -1 )
203         {
204             vlc_credential_clean( &credential );
205             free(psz_var_domain);
206             free( psz_decoded_path );
207             vlc_UrlClean( &url );
208             return VLC_ENOMEM;
209         }
210 
211         if( ( i_ret = smbc_stat( psz_uri, &filestat ) ) && errno == EACCES )
212         {
213             errno = 0;
214             if( vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
215                                     SMB_LOGIN_DIALOG_TITLE,
216                                     SMB_LOGIN_DIALOG_TEXT, url.psz_host) )
217                 continue;
218         }
219 
220         /* smbc_stat fails with servers or shares. Assume they are directory */
221         if( i_ret || S_ISDIR( filestat.st_mode ) )
222             b_is_dir = true;
223         break;
224     }
225 
226     vlc_credential_store( &credential, p_access );
227     vlc_credential_clean( &credential );
228     free(psz_var_domain);
229     free( psz_decoded_path );
230 
231     /* Init p_access */
232     p_sys =
233     p_access->p_sys = vlc_obj_calloc( p_this, 1, sizeof( access_sys_t ) );
234     if( !p_sys )
235     {
236         free( psz_uri );
237         vlc_UrlClean( &url );
238         return VLC_ENOMEM;
239     }
240 
241     if( b_is_dir )
242     {
243         p_sys->url = url;
244         p_access->pf_readdir = DirRead;
245         p_access->pf_control = access_vaDirectoryControlHelper;
246         i_size = 0;
247 #ifndef _WIN32
248         i_smb = smbc_opendir( psz_uri );
249         if( i_smb < 0 )
250             vlc_UrlClean( &p_sys->url );
251 #endif
252     }
253     else
254     {
255         ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek );
256         i_smb = smbc_open( psz_uri, O_RDONLY, 0 );
257         i_size = filestat.st_size;
258         vlc_UrlClean( &url );
259     }
260     free( psz_uri );
261 
262 #ifndef _WIN32
263     if( i_smb < 0 )
264     {
265         msg_Err( p_access, "open failed for '%s' (%s)",
266                  p_access->psz_location, vlc_strerror_c(errno) );
267         return VLC_EGENERIC;
268     }
269 #endif
270 
271     p_sys->size = i_size;
272     p_sys->i_smb = i_smb;
273 
274     return VLC_SUCCESS;
275 }
276 
277 /*****************************************************************************
278  * Close: free unused data structures
279  *****************************************************************************/
Close(vlc_object_t * p_this)280 static void Close( vlc_object_t *p_this )
281 {
282     stream_t     *p_access = (stream_t*)p_this;
283     access_sys_t *p_sys = p_access->p_sys;
284 
285     vlc_UrlClean( &p_sys->url );
286 
287 #ifndef _WIN32
288     if( p_access->pf_readdir )
289         smbc_closedir( p_sys->i_smb );
290     else
291 #endif
292         smbc_close( p_sys->i_smb );
293 }
294 
295 /*****************************************************************************
296  * Seek: try to go at the right place
297  *****************************************************************************/
Seek(stream_t * p_access,uint64_t i_pos)298 static int Seek( stream_t *p_access, uint64_t i_pos )
299 {
300     access_sys_t *p_sys = p_access->p_sys;
301     int64_t      i_ret;
302 
303     if( i_pos >= INT64_MAX )
304         return VLC_EGENERIC;
305 
306     msg_Dbg( p_access, "seeking to %"PRId64, i_pos );
307 
308     i_ret = smbc_lseek( p_sys->i_smb, i_pos, SEEK_SET );
309     if( i_ret == -1 )
310     {
311         msg_Err( p_access, "seek failed (%s)", vlc_strerror_c(errno) );
312         return VLC_EGENERIC;
313     }
314 
315     return VLC_SUCCESS;
316 }
317 
318 /*****************************************************************************
319  * Read:
320  *****************************************************************************/
Read(stream_t * p_access,void * p_buffer,size_t i_len)321 static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len )
322 {
323     access_sys_t *p_sys = p_access->p_sys;
324     int i_read;
325 
326     i_read = smbc_read( p_sys->i_smb, p_buffer, i_len );
327     if( i_read < 0 )
328     {
329         msg_Err( p_access, "read failed (%s)", vlc_strerror_c(errno) );
330         i_read = 0;
331     }
332 
333     return i_read;
334 }
335 
336 /*****************************************************************************
337  * DirRead:
338  *****************************************************************************/
DirRead(stream_t * p_access,input_item_node_t * p_node)339 static int DirRead (stream_t *p_access, input_item_node_t *p_node )
340 {
341     access_sys_t *p_sys = p_access->p_sys;
342     int i_ret = VLC_SUCCESS;
343 
344     struct vlc_readdir_helper rdh;
345     vlc_readdir_helper_init( &rdh, p_access, p_node );
346 
347 #ifndef _WIN32
348     struct smbc_dirent *p_entry;
349 
350     while( i_ret == VLC_SUCCESS && ( p_entry = smbc_readdir( p_sys->i_smb ) ) )
351     {
352         char *psz_uri;
353         const char *psz_server = p_sys->url.psz_host;
354         const char *psz_path = p_sys->url.psz_path;
355         const char *psz_name = p_entry->name;
356         int i_type;
357 
358         switch( p_entry->smbc_type )
359         {
360         case SMBC_SERVER:
361         case SMBC_WORKGROUP:
362             psz_server = p_sys->url.psz_host;
363             psz_path = NULL;
364             psz_name = NULL;
365         case SMBC_FILE_SHARE:
366         case SMBC_DIR:
367             i_type = ITEM_TYPE_DIRECTORY;
368             break;
369         case SMBC_FILE:
370             i_type = ITEM_TYPE_FILE;
371             break;
372         default:
373         case SMBC_PRINTER_SHARE:
374         case SMBC_COMMS_SHARE:
375         case SMBC_IPC_SHARE:
376         case SMBC_LINK:
377             continue;
378         }
379 
380         char *psz_encoded_name = NULL;
381         if( psz_name != NULL
382          && ( psz_encoded_name = vlc_uri_encode( psz_name ) ) == NULL )
383         {
384             i_ret = VLC_ENOMEM;
385             break;
386         }
387         if( smb_get_uri( p_access, &psz_uri, NULL, NULL, NULL,
388                          psz_server, psz_path, psz_encoded_name ) < 0 )
389         {
390             free(psz_encoded_name);
391             i_ret = VLC_ENOMEM;
392             break;
393         }
394         free(psz_encoded_name);
395         i_ret = vlc_readdir_helper_additem( &rdh, psz_uri, NULL, p_entry->name,
396                                             i_type, ITEM_NET );
397         free( psz_uri );
398     }
399 #else
400     // Handle share listing from here. Directory browsing is handled by the
401     // usual filesystem module.
402     SHARE_INFO_1 *p_info;
403     DWORD i_share_enum_res;
404     DWORD i_nb_elem;
405     DWORD i_resume_handle = 0;
406     DWORD i_total_elements; // Unused, but needs to be passed
407     wchar_t *wpsz_host = ToWide( p_sys->url.psz_host );
408     if( wpsz_host == NULL )
409         return VLC_ENOMEM;
410     do
411     {
412         i_share_enum_res = NetShareEnum( wpsz_host, 1, (LPBYTE*)&p_info,
413                               MAX_PREFERRED_LENGTH, &i_nb_elem,
414                               &i_total_elements, &i_resume_handle );
415         if( i_share_enum_res == ERROR_SUCCESS ||
416             i_share_enum_res == ERROR_MORE_DATA )
417         {
418             for ( DWORD i = 0; i < i_nb_elem; ++i )
419             {
420                 SHARE_INFO_1 *p_current = p_info + i;
421                 if( p_current->shi1_type & STYPE_SPECIAL )
422                     continue;
423                 char* psz_name = FromWide( p_current->shi1_netname );
424                 if( psz_name == NULL )
425                 {
426                     i_ret = VLC_ENOMEM;
427                     break;
428                 }
429 
430                 char* psz_path;
431                 if( smb_get_uri( p_access, &psz_path, NULL, NULL, NULL,
432                                  p_sys->url.psz_host, p_sys->url.psz_path,
433                                  psz_name ) < 0 )
434                 {
435                     free( psz_name );
436                     i_ret = VLC_ENOMEM;
437                     break;
438                 }
439                 // We need to concatenate the scheme before, as the window version
440                 // of smb_get_uri generates a path (and the other call site needs
441                 // a path). The path is already prefixed by "//" so we just need
442                 // to add "file:"
443                 char* psz_uri;
444                 if( asprintf( &psz_uri, "file:%s", psz_path ) < 0 )
445                 {
446                     free( psz_name );
447                     free( psz_path );
448                     i_ret = VLC_ENOMEM;
449                     break;
450                 }
451                 free( psz_path );
452 
453                 i_ret = vlc_readdir_helper_additem( &rdh, psz_uri, NULL,
454                                     psz_name, ITEM_TYPE_DIRECTORY, ITEM_NET );
455                 free( psz_name );
456                 free( psz_uri );
457             }
458         }
459         NetApiBufferFree( p_info );
460     } while( i_share_enum_res == ERROR_MORE_DATA && i_ret == VLC_SUCCESS );
461 
462     free( wpsz_host );
463 #endif
464 
465     vlc_readdir_helper_finish( &rdh, i_ret == VLC_SUCCESS );
466 
467     return i_ret;
468 }
469 
470 /*****************************************************************************
471  * Control:
472  *****************************************************************************/
Control(stream_t * p_access,int i_query,va_list args)473 static int Control( stream_t *p_access, int i_query, va_list args )
474 {
475     access_sys_t *sys = p_access->p_sys;
476 
477     switch( i_query )
478     {
479     case STREAM_CAN_SEEK:
480     case STREAM_CAN_PAUSE:
481     case STREAM_CAN_CONTROL_PACE:
482         *va_arg( args, bool* ) = true;
483         break;
484 
485     case STREAM_CAN_FASTSEEK:
486         *va_arg( args, bool* ) = false;
487         break;
488 
489     case STREAM_GET_SIZE:
490         if( p_access->pf_readdir != NULL )
491             return VLC_EGENERIC;
492         *va_arg( args, uint64_t * ) = sys->size;
493         break;
494 
495     case STREAM_GET_PTS_DELAY:
496         *va_arg( args, int64_t * ) = INT64_C(1000)
497             * var_InheritInteger( p_access, "network-caching" );
498         break;
499 
500     case STREAM_SET_PAUSE_STATE:
501         /* Nothing to do */
502         break;
503 
504     default:
505         return VLC_EGENERIC;
506     }
507 
508     return VLC_SUCCESS;
509 }
510 
511 #ifdef _WIN32
Win32AddConnection(stream_t * p_access,const char * psz_server,const char * psz_share,const char * psz_user,const char * psz_pwd,const char * psz_domain)512 static void Win32AddConnection( stream_t *p_access, const char *psz_server,
513                                 const char *psz_share, const char *psz_user,
514                                 const char *psz_pwd, const char *psz_domain )
515 {
516     char psz_remote[MAX_PATH];
517     NETRESOURCE net_resource;
518     DWORD i_result;
519     VLC_UNUSED( psz_domain );
520 
521     memset( &net_resource, 0, sizeof(net_resource) );
522     net_resource.dwType = RESOURCETYPE_DISK;
523 
524     if (psz_share)
525         psz_share = psz_share + 1; /* skip first '/' */
526     else
527         psz_share = "";
528 
529     snprintf( psz_remote, sizeof( psz_remote ), "\\\\%s\\%s", psz_server, psz_share );
530     /* remove trailings '/' */
531     char *psz_delim = strchr( psz_remote, '/' );
532     if( psz_delim )
533         *psz_delim = '\0';
534 
535     net_resource.lpRemoteName = psz_remote;
536 
537     i_result = WNetAddConnection2( &net_resource, psz_pwd, psz_user, 0 );
538 
539     if( i_result != NO_ERROR )
540     {
541         msg_Dbg( p_access, "connected to %s", psz_remote );
542     }
543     else if( i_result != ERROR_ALREADY_ASSIGNED &&
544              i_result != ERROR_DEVICE_ALREADY_REMEMBERED )
545     {
546         msg_Dbg( p_access, "already connected to %s", psz_remote );
547     }
548     else
549     {
550         msg_Dbg( p_access, "failed to connect to %s", psz_remote );
551     }
552 }
553 #endif // _WIN32
554