e5ebb4f76a8a37426dbc36e2ac0323cbb6884b64
[vlc.git] / modules / access / smb.c
1 /*****************************************************************************
2  * smb.c: SMB input module
3  *****************************************************************************
4  * Copyright (C) 2001-2015 VLC authors and VideoLAN
5  * $Id$
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 #   define smbc_open(a,b,c) vlc_open(a,b,c)
38 #   define smbc_stat(a,b) _stati64(a,b)
39 #   define smbc_read read
40 #   define smbc_lseek _lseeki64
41 #   define smbc_close close
42 #else
43 #   include <libsmbclient.h>
44 #endif
45
46 #include <vlc_common.h>
47 #include <vlc_fs.h>
48 #include <vlc_plugin.h>
49 #include <vlc_access.h>
50 #include <vlc_input_item.h>
51 #include <vlc_url.h>
52 #include <vlc_keystore.h>
53
54 #include "smb_common.h"
55
56 /*****************************************************************************
57  * Module descriptor
58  *****************************************************************************/
59 static int  Open ( vlc_object_t * );
60 static void Close( vlc_object_t * );
61
62 #define SMB_HELP N_("Samba (Windows network shares) input")
63 vlc_module_begin ()
64     set_shortname( "SMB" )
65     set_description( N_("SMB input") )
66     set_help(SMB_HELP)
67     set_capability( "access", 0 )
68     set_category( CAT_INPUT )
69     set_subcategory( SUBCAT_INPUT_ACCESS )
70     add_string( "smb-user", NULL, SMB_USER_TEXT, SMB_USER_LONGTEXT,
71                 false )
72     add_password( "smb-pwd", NULL, SMB_PASS_TEXT,
73                   SMB_PASS_LONGTEXT, false )
74     add_string( "smb-domain", NULL, SMB_DOMAIN_TEXT,
75                 SMB_DOMAIN_LONGTEXT, false )
76     add_shortcut( "smb" )
77     set_callbacks( Open, Close )
78 vlc_module_end ()
79
80 /*****************************************************************************
81  * Local prototypes
82  *****************************************************************************/
83 static ssize_t Read( access_t *, uint8_t *, size_t );
84 static int Seek( access_t *, uint64_t );
85 static int Control( access_t *, int, va_list );
86 #ifndef _WIN32
87 static int DirRead( access_t *, input_item_node_t * );
88 static int DirControl( access_t *, int, va_list );
89 #endif
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( access_t *, const char *, const char *, const char *, const char *, const char * );
100 #else
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]]]] */
118 static int smb_get_uri( access_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  ****************************************************************************/
150 static int Open( vlc_object_t *p_this )
151 {
152     access_t     *p_access = (access_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     vlc_UrlParse( &url, p_access->psz_url );
179     if( url.psz_path )
180     {
181         psz_decoded_path = vlc_uri_decode_duplicate( url.psz_path );
182         if( !psz_decoded_path )
183         {
184             vlc_UrlClean( &url );
185             return VLC_EGENERIC;
186         }
187     }
188
189     vlc_credential_init( &credential, &url );
190     psz_var_domain = var_InheritString( p_access, "smb-domain" );
191     credential.psz_realm = psz_var_domain;
192     vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
193                         NULL, NULL );
194     for (;;)
195     {
196         if( smb_get_uri( p_access, &psz_uri, credential.psz_realm,
197                          credential.psz_username, credential.psz_password,
198                          url.psz_host, psz_decoded_path, NULL ) == -1 )
199         {
200             vlc_credential_clean( &credential );
201             free(psz_var_domain);
202             free( psz_decoded_path );
203             vlc_UrlClean( &url );
204             return VLC_ENOMEM;
205         }
206
207         if( ( i_ret = smbc_stat( psz_uri, &filestat ) ) && errno == EACCES )
208         {
209             errno = 0;
210             if( vlc_credential_get( &credential, p_access, "smb-user", "smb-pwd",
211                                     SMB_LOGIN_DIALOG_TITLE,
212                                     SMB_LOGIN_DIALOG_TEXT, url.psz_host) )
213                 continue;
214         }
215
216         /* smbc_stat fails with servers or shares. Assume they are directory */
217         if( i_ret || S_ISDIR( filestat.st_mode ) )
218             b_is_dir = true;
219         break;
220     }
221
222     vlc_credential_store( &credential, p_access );
223     vlc_credential_clean( &credential );
224     free(psz_var_domain);
225     free( psz_decoded_path );
226
227     /* Init p_access */
228     access_InitFields( p_access );
229     p_sys =
230     p_access->p_sys = (access_sys_t*)calloc( 1, sizeof( access_sys_t ) );
231     if( !p_sys )
232     {
233         free( psz_uri );
234         vlc_UrlClean( &url );
235         return VLC_ENOMEM;
236     }
237
238     if( b_is_dir )
239     {
240 #ifdef _WIN32
241         free( p_sys );
242         free( psz_uri );
243         vlc_UrlClean( &url );
244         return VLC_EGENERIC;
245 #else
246         p_sys->url = url;
247         p_access->pf_readdir = DirRead;
248         p_access->pf_control = DirControl;
249         i_smb = smbc_opendir( psz_uri );
250         i_size = 0;
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     }
259     free( psz_uri );
260
261     if( i_smb < 0 )
262     {
263         msg_Err( p_access, "open failed for '%s' (%s)",
264                  p_access->psz_location, vlc_strerror_c(errno) );
265         vlc_UrlClean( &p_sys->url );
266         free( p_sys );
267         return VLC_EGENERIC;
268     }
269
270     p_sys->size = i_size;
271     p_sys->i_smb = i_smb;
272
273     return VLC_SUCCESS;
274 }
275
276 /*****************************************************************************
277  * Close: free unused data structures
278  *****************************************************************************/
279 static void Close( vlc_object_t *p_this )
280 {
281     access_t     *p_access = (access_t*)p_this;
282     access_sys_t *p_sys = p_access->p_sys;
283
284     vlc_UrlClean( &p_sys->url );
285
286 #ifndef _WIN32
287     if( p_access->pf_readdir )
288         smbc_closedir( p_sys->i_smb );
289     else
290 #endif
291         smbc_close( p_sys->i_smb );
292     free( p_sys );
293 }
294
295 /*****************************************************************************
296  * Seek: try to go at the right place
297  *****************************************************************************/
298 static int Seek( access_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     p_access->info.b_eof = false;
316
317     return VLC_SUCCESS;
318 }
319
320 /*****************************************************************************
321  * Read:
322  *****************************************************************************/
323 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len )
324 {
325     access_sys_t *p_sys = p_access->p_sys;
326     int i_read;
327
328     if( p_access->info.b_eof ) return 0;
329
330     i_read = smbc_read( p_sys->i_smb, p_buffer, i_len );
331     if( i_read < 0 )
332     {
333         msg_Err( p_access, "read failed (%s)", vlc_strerror_c(errno) );
334         p_access->info.b_eof = true;
335         return -1;
336     }
337
338     if( i_read == 0 ) p_access->info.b_eof = true;
339
340     return i_read;
341 }
342
343 #ifndef _WIN32
344 /*****************************************************************************
345  * DirRead:
346  *****************************************************************************/
347 static int DirRead (access_t *p_access, input_item_node_t *p_node )
348 {
349     access_sys_t *p_sys = p_access->p_sys;
350     struct smbc_dirent *p_entry;
351     int i_ret = VLC_SUCCESS;
352
353     struct access_fsdir fsdir;
354     access_fsdir_init( &fsdir, p_access, p_node );
355
356     while( i_ret == VLC_SUCCESS && ( p_entry = smbc_readdir( p_sys->i_smb ) ) )
357     {
358         char *psz_uri;
359         const char *psz_server = p_sys->url.psz_host;
360         const char *psz_path = p_sys->url.psz_path;
361         const char *psz_name = p_entry->name;
362         int i_type;
363
364         switch( p_entry->smbc_type )
365         {
366         case SMBC_SERVER:
367         case SMBC_WORKGROUP:
368             psz_server = p_sys->url.psz_host;
369             psz_path = NULL;
370             psz_name = NULL;
371         case SMBC_FILE_SHARE:
372         case SMBC_DIR:
373             i_type = ITEM_TYPE_DIRECTORY;
374             break;
375         case SMBC_FILE:
376             i_type = ITEM_TYPE_FILE;
377             break;
378         default:
379         case SMBC_PRINTER_SHARE:
380         case SMBC_COMMS_SHARE:
381         case SMBC_IPC_SHARE:
382         case SMBC_LINK:
383             continue;
384         }
385
386         char *psz_encoded_name = NULL;
387         if( psz_name != NULL
388          && ( psz_encoded_name = vlc_uri_encode( psz_name ) ) == NULL )
389         {
390             i_ret = VLC_ENOMEM;
391             break;
392         }
393         if( smb_get_uri( p_access, &psz_uri, NULL, NULL, NULL,
394                          psz_server, psz_path, psz_encoded_name ) < 0 )
395         {
396             free(psz_encoded_name);
397             i_ret = VLC_ENOMEM;
398             break;
399         }
400         free(psz_encoded_name);
401         i_ret = access_fsdir_additem( &fsdir, psz_uri, p_entry->name,
402                                       i_type, ITEM_NET );
403         free( psz_uri );
404     }
405
406     access_fsdir_finish( &fsdir, i_ret == VLC_SUCCESS );
407
408     return i_ret;
409 }
410
411 static int DirControl( access_t *p_access, int i_query, va_list args )
412 {
413     switch( i_query )
414     {
415     case ACCESS_IS_DIRECTORY:
416         *va_arg( args, bool * ) = true; /* might loop */
417         break;
418     default:
419         return access_vaDirectoryControlHelper( p_access, i_query, args );
420     }
421
422     return VLC_SUCCESS;
423 }
424 #endif
425
426 /*****************************************************************************
427  * Control:
428  *****************************************************************************/
429 static int Control( access_t *p_access, int i_query, va_list args )
430 {
431     switch( i_query )
432     {
433     case ACCESS_CAN_SEEK:
434     case ACCESS_CAN_PAUSE:
435     case ACCESS_CAN_CONTROL_PACE:
436         *va_arg( args, bool* ) = true;
437         break;
438
439     case ACCESS_CAN_FASTSEEK:
440         *va_arg( args, bool* ) = false;
441         break;
442
443     case ACCESS_GET_SIZE:
444         if( p_access->pf_readdir != NULL )
445             return VLC_EGENERIC;
446         *va_arg( args, uint64_t * ) = p_access->p_sys->size;
447         break;
448
449     case ACCESS_GET_PTS_DELAY:
450         *va_arg( args, int64_t * ) = INT64_C(1000)
451             * var_InheritInteger( p_access, "network-caching" );
452         break;
453
454     case ACCESS_SET_PAUSE_STATE:
455         /* Nothing to do */
456         break;
457
458     default:
459         return VLC_EGENERIC;
460     }
461
462     return VLC_SUCCESS;
463 }
464
465 #ifdef _WIN32
466 static void Win32AddConnection( access_t *p_access, const char *psz_server,
467                                 const char *psz_share, const char *psz_user,
468                                 const char *psz_pwd, const char *psz_domain )
469 {
470     char psz_remote[MAX_PATH];
471     NETRESOURCE net_resource;
472     DWORD i_result;
473     VLC_UNUSED( psz_domain );
474
475     memset( &net_resource, 0, sizeof(net_resource) );
476     net_resource.dwType = RESOURCETYPE_DISK;
477
478     if (psz_share)
479         psz_share = psz_share + 1; /* skip first '/' */
480     else
481         psz_share = "";
482
483     snprintf( psz_remote, sizeof( psz_remote ), "\\\\%s\\%s", psz_server, psz_share );
484     /* remove trailings '/' */
485     char *psz_delim = strchr( psz_remote, '/' );
486     if( psz_delim )
487         *psz_delim = '\0';
488
489     net_resource.lpRemoteName = psz_remote;
490
491     i_result = WNetAddConnection2( &net_resource, psz_pwd, psz_user, 0 );
492
493     if( i_result != NO_ERROR )
494     {
495         msg_Dbg( p_access, "connected to %s", psz_remote );
496     }
497     else if( i_result != ERROR_ALREADY_ASSIGNED &&
498              i_result != ERROR_DEVICE_ALREADY_REMEMBERED )
499     {
500         msg_Dbg( p_access, "already connected to %s", psz_remote );
501     }
502     else
503     {
504         msg_Dbg( p_access, "failed to connect to %s", psz_remote );
505     }
506 }
507 #endif // _WIN32