1 /*****************************************************************************
2  * smb2.c: SMB2 access plug-in
3  *****************************************************************************
4  * Copyright © 2018  VLC authors, VideoLAN and VideoLabs
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20 
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24 
25 #include <assert.h>
26 #include <errno.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #ifdef HAVE_POLL
33 # include <poll.h>
34 #endif
35 
36 #include <vlc_common.h>
37 #include <vlc_access.h>
38 #include <vlc_dialog.h>
39 #include <vlc_input_item.h>
40 #include <vlc_plugin.h>
41 #include <vlc_url.h>
42 #include <vlc_keystore.h>
43 #include <vlc_interrupt.h>
44 #include <vlc_network.h>
45 
46 #include <smb2/smb2.h>
47 #include <smb2/libsmb2.h>
48 #include <smb2/libsmb2-raw.h>
49 
50 #ifdef HAVE_DSM
51 # include <bdsm/netbios_ns.h>
52 # include <bdsm/netbios_defs.h>
53 #endif
54 
55 #ifdef HAVE_ARPA_INET_H
56 # include <arpa/inet.h>
57 #endif
58 
59 #include "smb_common.h"
60 
61 static int Open(vlc_object_t *);
62 static void Close(vlc_object_t *);
63 
64 vlc_module_begin()
65     set_shortname("smb2")
66     set_description("SMB2 / SMB3 input")
67     set_help("Samba (Windows network shares) input via libsmb2")
68     set_capability("access", 21)
69     set_category(CAT_INPUT)
70     set_subcategory(SUBCAT_INPUT_ACCESS)
71     add_string("smb-user", NULL, SMB_USER_TEXT, SMB_USER_LONGTEXT, false)
72     add_password("smb-pwd", NULL, SMB_PASS_TEXT, SMB_PASS_LONGTEXT, false)
73     add_string("smb-domain", NULL, SMB_DOMAIN_TEXT, SMB_DOMAIN_LONGTEXT, false)
74     add_shortcut("smb", "smb2")
75     set_callbacks(Open, Close)
76 vlc_module_end()
77 
78 struct access_sys
79 {
80     struct smb2_context *   smb2;
81     struct smb2fh *         smb2fh;
82     struct smb2dir *        smb2dir;
83     struct srvsvc_netshareenumall_rep *share_enum;
84     uint64_t                smb2_size;
85     vlc_url_t               encoded_url;
86     bool                    eof;
87     bool                    smb2_connected;
88     int                     error_status;
89 
90     bool res_done;
91     union {
92         struct
93         {
94             size_t len;
95         } read;
96     } res;
97 };
98 
99 static int
smb2_check_status(stream_t * access,int status,const char * psz_func)100 smb2_check_status(stream_t *access, int status, const char *psz_func)
101 {
102     struct access_sys *sys = access->p_sys;
103 
104     if (status < 0)
105     {
106         const char *psz_error = smb2_get_error(sys->smb2);
107         msg_Warn(access, "%s failed: %d, '%s'", psz_func, status, psz_error);
108         sys->error_status = status;
109         return -1;
110     }
111     else
112     {
113         sys->res_done = true;
114         return 0;
115     }
116 }
117 
118 static void
smb2_set_error(stream_t * access,const char * psz_func,int err)119 smb2_set_error(stream_t *access, const char *psz_func, int err)
120 {
121     struct access_sys *sys = access->p_sys;
122 
123     msg_Err(access, "%s failed: %d, %s", psz_func, err,
124             smb2_get_error(sys->smb2));
125     sys->error_status = err;
126 }
127 
128 #define VLC_SMB2_CHECK_STATUS(access, status) \
129     smb2_check_status(access, status, __func__)
130 
131 #define VLC_SMB2_SET_ERROR(access, func, err) \
132     smb2_set_error(access, func, err)
133 
134 #define VLC_SMB2_STATUS_DENIED(x) (x == -ECONNREFUSED || x == -EACCES)
135 
136 #if defined (__ELF__) || defined (__MACH__) /* weak support */
137 /* There is no way to know if libsmb2 has these new symbols and we don't want
138  * to increase the version requirement on VLC 3.0, therefore implement a weak
139  * compat version. */
140 const t_socket *
141 smb2_get_fds(struct smb2_context *smb2, size_t *fd_count, int *timeout);
142 int
143 smb2_service_fd(struct smb2_context *smb2, int fd, int revents);
144 
145 __attribute__((weak)) const t_socket *
smb2_get_fds(struct smb2_context * smb2,size_t * fd_count,int * timeout)146 smb2_get_fds(struct smb2_context *smb2, size_t *fd_count, int *timeout)
147 {
148     (void) timeout;
149     static thread_local t_socket fd;
150 
151     *fd_count = 1;
152     fd = smb2_get_fd(smb2);
153     return &fd;
154 }
155 
156 __attribute__((weak)) int
smb2_service_fd(struct smb2_context * smb2,int fd,int revents)157 smb2_service_fd(struct smb2_context *smb2, int fd, int revents)
158 {
159     (void) fd;
160     return smb2_service(smb2, revents);
161 }
162 #endif
163 
164 static int
vlc_smb2_mainloop(stream_t * access,bool teardown)165 vlc_smb2_mainloop(stream_t *access, bool teardown)
166 {
167 #define TEARDOWN_TIMEOUT 250 /* in ms */
168     struct access_sys *sys = access->p_sys;
169 
170     int timeout = -1;
171     int (*poll_func)(struct pollfd *, unsigned, int) = vlc_poll_i11e;
172 
173     /* vlc_smb2_mainloop() can be called to clean-up after an error, but this
174      * function can override the error_status (from async cbs). Therefore,
175      * store the original error_status in order to restore it at the end of
176      * this call (since we want to keep the first and original error status). */
177     int original_error_status = sys->error_status;
178 
179     if (teardown)
180     {
181         /* Don't use vlc_poll_i11e that will return immediately with the EINTR
182          * errno if VLC's input is interrupted. Use the posix poll with a
183          * timeout to let a chance for a clean teardown. */
184         timeout = TEARDOWN_TIMEOUT;
185         poll_func = (void *)poll;
186         sys->error_status = 0;
187     }
188 
189     sys->res_done = false;
190     while (sys->error_status == 0 && !sys->res_done)
191     {
192         int ret, smb2_timeout;
193         size_t fd_count;
194         const t_socket *fds = smb2_get_fds(sys->smb2, &fd_count, &smb2_timeout);
195         int events = smb2_which_events(sys->smb2);
196 
197         struct pollfd p_fds[fd_count];
198         for (size_t i = 0; i < fd_count; ++i)
199         {
200             p_fds[i].events = events;
201             p_fds[i].fd = fds[i];
202         }
203         if (smb2_timeout != -1)
204             timeout = smb2_timeout;
205 
206         if (fds == NULL || (ret = poll_func(p_fds, fd_count, timeout)) < 0)
207         {
208             if (errno == EINTR)
209             {
210                 msg_Warn(access, "vlc_poll_i11e interrupted");
211                 if (poll_func != (void *) poll)
212                 {
213                     /* Try again with a timeout to let the command complete.
214                      * Indeed, if this command is interrupted, every future
215                      * commands will fail and we won't be able to teardown. */
216                     timeout = TEARDOWN_TIMEOUT;
217                     poll_func = (void *) poll;
218                 }
219                 else
220                     sys->error_status = -errno;
221             }
222             else
223             {
224                 msg_Err(access, "vlc_poll_i11e failed");
225                 sys->error_status = -errno;
226             }
227         }
228         else if (ret == 0)
229         {
230             if (teardown)
231                 sys->error_status = -ETIMEDOUT;
232             else if (smb2_service_fd(sys->smb2, -1, 0) < 0)
233                 VLC_SMB2_SET_ERROR(access, "smb2_service", 1);
234         }
235         else
236         {
237             for (size_t i = 0; i < fd_count; ++i)
238             {
239                 if (p_fds[i].revents
240                  && smb2_service_fd(sys->smb2, p_fds[i].fd, p_fds[i].revents) < 0)
241                     VLC_SMB2_SET_ERROR(access, "smb2_service", 1);
242             }
243         }
244     }
245 
246     int ret = sys->error_status == 0 ? 0 : -1;
247     if (original_error_status != 0)
248         sys->error_status = original_error_status;
249     return ret;
250 }
251 
252 #define VLC_SMB2_GENERIC_CB() \
253     VLC_UNUSED(smb2); \
254     stream_t *access = private_data; \
255     struct access_sys *sys = access->p_sys; \
256     assert(sys->smb2 == smb2); \
257     if (VLC_SMB2_CHECK_STATUS(access, status)) \
258         return
259 
260 static void
smb2_generic_cb(struct smb2_context * smb2,int status,void * data,void * private_data)261 smb2_generic_cb(struct smb2_context *smb2, int status, void *data,
262                 void *private_data)
263 {
264     VLC_UNUSED(data);
265     VLC_SMB2_GENERIC_CB();
266 }
267 
268 static void
smb2_read_cb(struct smb2_context * smb2,int status,void * data,void * private_data)269 smb2_read_cb(struct smb2_context *smb2, int status, void *data,
270              void *private_data)
271 {
272     VLC_UNUSED(data);
273     VLC_SMB2_GENERIC_CB();
274 
275     if (status == 0)
276         sys->eof = true;
277     else
278         sys->res.read.len = status;
279 }
280 
281 static ssize_t
FileRead(stream_t * access,void * buf,size_t len)282 FileRead(stream_t *access, void *buf, size_t len)
283 {
284     struct access_sys *sys = access->p_sys;
285 
286     if (sys->error_status != 0)
287         return -1;
288 
289     if (sys->eof)
290         return 0;
291 
292     /* Limit the read size since smb2_read_async() will complete only after
293      * reading the whole requested data and not when whatever data is available
294      * (high read size means a faster I/O but a higher latency). */
295     if (len > 262144)
296         len = 262144;
297 
298     sys->res.read.len = 0;
299     if (smb2_read_async(sys->smb2, sys->smb2fh, buf, len,
300                         smb2_read_cb, access) < 0)
301     {
302         VLC_SMB2_SET_ERROR(access, "smb2_read_async", 1);
303         return -1;
304     }
305 
306     if (vlc_smb2_mainloop(access, false) < 0)
307         return -1;
308 
309     return sys->res.read.len;
310 }
311 
312 static int
FileSeek(stream_t * access,uint64_t i_pos)313 FileSeek(stream_t *access, uint64_t i_pos)
314 {
315     struct access_sys *sys = access->p_sys;
316 
317     if (sys->error_status != 0)
318         return VLC_EGENERIC;
319 
320     if (smb2_lseek(sys->smb2, sys->smb2fh, i_pos, SEEK_SET, NULL) < 0)
321     {
322         VLC_SMB2_SET_ERROR(access, "smb2_seek_async", 1);
323         return VLC_EGENERIC;
324     }
325     sys->eof = false;
326 
327     return VLC_SUCCESS;
328 }
329 
330 static int
FileControl(stream_t * access,int i_query,va_list args)331 FileControl(stream_t *access, int i_query, va_list args)
332 {
333     struct access_sys *sys = access->p_sys;
334 
335     switch (i_query)
336     {
337         case STREAM_CAN_SEEK:
338             *va_arg(args, bool *) = true;
339             break;
340 
341         case STREAM_CAN_FASTSEEK:
342             *va_arg(args, bool *) = false;
343             break;
344 
345         case STREAM_CAN_PAUSE:
346         case STREAM_CAN_CONTROL_PACE:
347             *va_arg(args, bool *) = true;
348             break;
349 
350         case STREAM_GET_SIZE:
351         {
352             *va_arg(args, uint64_t *) = sys->smb2_size;
353             break;
354         }
355 
356         case STREAM_GET_PTS_DELAY:
357             *va_arg( args, int64_t * ) = INT64_C(1000)
358                 * var_InheritInteger( access, "network-caching" );
359             break;
360 
361         case STREAM_SET_PAUSE_STATE:
362             break;
363 
364         default:
365             return VLC_EGENERIC;
366     }
367     return VLC_SUCCESS;
368 }
369 
370 static char *
vlc_smb2_get_url(vlc_url_t * url,const char * file)371 vlc_smb2_get_url(vlc_url_t *url, const char *file)
372 {
373     /* smb2://<psz_host><psz_path><file>?<psz_option> */
374     char *buf;
375     if (asprintf(&buf, "smb://%s%s%s%s%s%s", url->psz_host,
376                  url->psz_path != NULL ? url->psz_path : "",
377                  url->psz_path != NULL && url->psz_path[0] != '\0' &&
378                  url->psz_path[strlen(url->psz_path) - 1] != '/' ? "/" : "",
379                  file,
380                  url->psz_option != NULL ? "?" : "",
381                  url->psz_option != NULL ? url->psz_option : "") == -1)
382         return NULL;
383     else
384         return buf;
385 }
386 
AddItem(stream_t * access,struct vlc_readdir_helper * rdh,const char * name,int i_type)387 static int AddItem(stream_t *access, struct vlc_readdir_helper *rdh,
388                    const char *name, int i_type)
389 {
390     struct access_sys *sys = access->p_sys;
391     char *name_encoded = vlc_uri_encode(name);
392     if (name_encoded == NULL)
393         return VLC_ENOMEM;
394 
395     char *url = vlc_smb2_get_url(&sys->encoded_url, name_encoded);
396     free(name_encoded);
397     if (url == NULL)
398         return VLC_ENOMEM;
399 
400     int ret = vlc_readdir_helper_additem(rdh, url, NULL, name, i_type,
401                                          ITEM_NET);
402     free(url);
403     return ret;
404 }
405 
406 static int
DirRead(stream_t * access,input_item_node_t * p_node)407 DirRead(stream_t *access, input_item_node_t *p_node)
408 {
409     struct access_sys *sys = access->p_sys;
410     struct smb2dirent *smb2dirent;
411     int ret = VLC_SUCCESS;
412     assert(sys->smb2dir);
413 
414     struct vlc_readdir_helper rdh;
415     vlc_readdir_helper_init(&rdh, access, p_node);
416 
417     while (ret == VLC_SUCCESS
418         && (smb2dirent = smb2_readdir(sys->smb2, sys->smb2dir)) != NULL)
419     {
420         int i_type;
421         switch (smb2dirent->st.smb2_type)
422         {
423         case SMB2_TYPE_FILE:
424             i_type = ITEM_TYPE_FILE;
425             break;
426         case SMB2_TYPE_DIRECTORY:
427             i_type = ITEM_TYPE_DIRECTORY;
428             break;
429         default:
430             i_type = ITEM_TYPE_UNKNOWN;
431             break;
432         }
433         ret = AddItem(access, &rdh, smb2dirent->name, i_type);
434     }
435 
436     vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
437 
438     return ret;
439 }
440 
441 static int
ShareEnum(stream_t * access,input_item_node_t * p_node)442 ShareEnum(stream_t *access, input_item_node_t *p_node)
443 {
444     struct access_sys *sys = access->p_sys;
445     assert(sys->share_enum != NULL);
446 
447     int ret = VLC_SUCCESS;
448     struct vlc_readdir_helper rdh;
449     vlc_readdir_helper_init(&rdh, access, p_node);
450 
451     struct srvsvc_netsharectr *ctr = sys->share_enum->ctr;
452     for (uint32_t iinfo = 0;
453          iinfo < ctr->ctr1.count && ret == VLC_SUCCESS; ++iinfo)
454     {
455        struct srvsvc_netshareinfo1 *info = &ctr->ctr1.array[iinfo];
456        if (info->type & SHARE_TYPE_HIDDEN)
457            continue;
458        switch (info->type & 0x3)
459        {
460            case SHARE_TYPE_DISKTREE:
461                ret = AddItem(access, &rdh, info->name, ITEM_TYPE_DIRECTORY);
462                break;
463        }
464     }
465 
466     vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
467     return 0;
468 }
469 
470 static int
vlc_smb2_close_fh(stream_t * access)471 vlc_smb2_close_fh(stream_t *access)
472 {
473     struct access_sys *sys = access->p_sys;
474 
475     assert(sys->smb2fh);
476 
477     if (smb2_close_async(sys->smb2, sys->smb2fh, smb2_generic_cb, access) < 0)
478     {
479         VLC_SMB2_SET_ERROR(access, "smb2_close_async", 1);
480         return -1;
481     }
482 
483     sys->smb2fh = NULL;
484 
485     return vlc_smb2_mainloop(access, true);
486 }
487 
488 static int
vlc_smb2_disconnect_share(stream_t * access)489 vlc_smb2_disconnect_share(stream_t *access)
490 {
491     struct access_sys *sys = access->p_sys;
492 
493     if (!sys->smb2_connected)
494         return 0;
495 
496     if (smb2_disconnect_share_async(sys->smb2, smb2_generic_cb, access) < 0)
497     {
498         VLC_SMB2_SET_ERROR(access, "smb2_connect_share_async", 1);
499         return -1;
500     }
501 
502     int ret = vlc_smb2_mainloop(access, true);
503     sys->smb2_connected = false;
504     return ret;
505 }
506 
507 static void
smb2_opendir_cb(struct smb2_context * smb2,int status,void * data,void * private_data)508 smb2_opendir_cb(struct smb2_context *smb2, int status, void *data,
509                 void *private_data)
510 {
511     VLC_SMB2_GENERIC_CB();
512 
513     sys->smb2dir = data;
514 }
515 
516 static void
smb2_open_cb(struct smb2_context * smb2,int status,void * data,void * private_data)517 smb2_open_cb(struct smb2_context *smb2, int status, void *data,
518              void *private_data)
519 {
520     VLC_SMB2_GENERIC_CB();
521 
522     sys->smb2fh = data;
523 }
524 
525 static void
smb2_share_enum_cb(struct smb2_context * smb2,int status,void * data,void * private_data)526 smb2_share_enum_cb(struct smb2_context *smb2, int status, void *data,
527                    void *private_data)
528 {
529     VLC_SMB2_GENERIC_CB();
530 
531     sys->share_enum = data;
532 }
533 
534 static void
vlc_smb2_print_addr(stream_t * access)535 vlc_smb2_print_addr(stream_t *access)
536 {
537     struct access_sys *sys = access->p_sys;
538 
539     struct sockaddr_storage addr;
540     if (getsockname(smb2_get_fd(sys->smb2), (struct sockaddr *)&addr,
541                     &(socklen_t){ sizeof(addr) }) != 0)
542         return;
543 
544     void *sin_addr;
545     switch (addr.ss_family)
546     {
547         case AF_INET6:
548             sin_addr = &((struct sockaddr_in6 *)&addr)->sin6_addr;
549             break;
550         case AF_INET:
551             sin_addr = &((struct sockaddr_in *)&addr)->sin_addr;
552             break;
553         default:
554             return;
555     }
556     char ip[INET6_ADDRSTRLEN];
557     if (inet_ntop(addr.ss_family, sin_addr, ip, sizeof(ip)) == NULL)
558         return;
559 
560     if (strcmp(ip, sys->encoded_url.psz_host) == 0)
561         return;
562 
563     msg_Dbg(access, "%s: connected from %s\n", sys->encoded_url.psz_host, ip);
564 }
565 
566 static int
vlc_smb2_open_share(stream_t * access,const char * url,const vlc_credential * credential)567 vlc_smb2_open_share(stream_t *access, const char *url,
568                     const vlc_credential *credential)
569 {
570     struct access_sys *sys = access->p_sys;
571 
572     struct smb2_url *smb2_url = NULL;
573 
574     sys->smb2 = smb2_init_context();
575     if (sys->smb2 == NULL)
576     {
577         msg_Err(access, "smb2_init_context failed");
578         goto error;
579     }
580     smb2_url = smb2_parse_url(sys->smb2, url);
581 
582     if (!smb2_url || !smb2_url->share || !smb2_url->server)
583     {
584         msg_Err(access, "smb2_parse_url failed");
585         goto error;
586     }
587 
588     const bool do_enum = smb2_url->share[0] == '\0';
589     const char *username = credential->psz_username;
590     const char *password = credential->psz_password;
591     const char *domain = credential->psz_realm;
592     const char *share = do_enum ? "IPC$" : smb2_url->share;
593     if (!username)
594     {
595         username = "Guest";
596         /* A NULL password enable ntlmssp anonymous login */
597         password = NULL;
598     }
599 
600     smb2_set_security_mode(sys->smb2, SMB2_NEGOTIATE_SIGNING_ENABLED);
601     smb2_set_password(sys->smb2, password);
602     smb2_set_domain(sys->smb2, domain ? domain : "");
603 
604     int err = smb2_connect_share_async(sys->smb2, smb2_url->server, share,
605                                        username, smb2_generic_cb, access);
606     if (err < 0)
607     {
608         VLC_SMB2_SET_ERROR(access, "smb2_connect_share_async", err);
609         goto error;
610     }
611     if (vlc_smb2_mainloop(access, false) != 0)
612         goto error;
613     sys->smb2_connected = true;
614 
615     vlc_smb2_print_addr(access);
616 
617     int ret;
618     if (do_enum)
619         ret = smb2_share_enum_async(sys->smb2, smb2_share_enum_cb, access);
620     else
621     {
622         struct smb2_stat_64 smb2_stat;
623         if (smb2_stat_async(sys->smb2, smb2_url->path, &smb2_stat,
624                             smb2_generic_cb, access) < 0)
625             VLC_SMB2_SET_ERROR(access, "smb2_stat_async", 1);
626 
627         if (vlc_smb2_mainloop(access, false) != 0)
628             goto error;
629 
630         if (smb2_stat.smb2_type == SMB2_TYPE_FILE)
631         {
632             sys->smb2_size = smb2_stat.smb2_size;
633             ret = smb2_open_async(sys->smb2, smb2_url->path, O_RDONLY,
634                                   smb2_open_cb, access);
635         }
636         else if (smb2_stat.smb2_type == SMB2_TYPE_DIRECTORY)
637             ret = smb2_opendir_async(sys->smb2, smb2_url->path,
638                                      smb2_opendir_cb, access);
639         else
640         {
641             msg_Err(access, "smb2_stat_cb: file type not handled");
642             sys->error_status = 1;
643             goto error;
644         }
645     }
646 
647     if (ret < 0)
648     {
649         VLC_SMB2_SET_ERROR(access, "smb2_open*_async", 1);
650         goto error;
651     }
652 
653     if (vlc_smb2_mainloop(access, false) != 0)
654         goto error;
655     smb2_destroy_url(smb2_url);
656     return 0;
657 
658 error:
659     if (smb2_url != NULL)
660         smb2_destroy_url(smb2_url);
661     if (sys->smb2 != NULL)
662     {
663         vlc_smb2_disconnect_share(access);
664         smb2_destroy_context(sys->smb2);
665         sys->smb2 = NULL;
666     }
667     return -1;
668 }
669 
670 static char *
vlc_smb2_resolve(stream_t * access,const char * host,unsigned port)671 vlc_smb2_resolve(stream_t *access, const char *host, unsigned port)
672 {
673     (void) access;
674     if (!host)
675         return NULL;
676 
677 #ifdef HAVE_DSM
678     /* Test if the host is an IP */
679     struct in_addr addr;
680     if (inet_pton(AF_INET, host, &addr) == 1)
681         return NULL;
682 
683     /* Test if the host can be resolved */
684     struct addrinfo *info = NULL;
685     if (vlc_getaddrinfo_i11e(host, port, NULL, &info) == 0)
686     {
687         freeaddrinfo(info);
688         /* Let smb2 resolve it */
689         return NULL;
690     }
691 
692     /* Test if the host is a netbios name */
693     char *out_host = NULL;
694     netbios_ns *ns = netbios_ns_new();
695     uint32_t ip4_addr;
696     if (netbios_ns_resolve(ns, host, NETBIOS_FILESERVER, &ip4_addr) == 0)
697     {
698         char ip[INET_ADDRSTRLEN];
699         if (inet_ntop(AF_INET, &ip4_addr, ip, sizeof(ip)))
700             out_host = strdup(ip);
701     }
702     netbios_ns_destroy(ns);
703     return out_host;
704 #else
705     (void) port;
706     return NULL;
707 #endif
708 }
709 
710 static int
Open(vlc_object_t * p_obj)711 Open(vlc_object_t *p_obj)
712 {
713     stream_t *access = (stream_t *)p_obj;
714     struct access_sys *sys = vlc_obj_calloc(p_obj, 1, sizeof (*sys));
715     char *var_domain = NULL;
716 
717     if (unlikely(sys == NULL))
718         return VLC_ENOMEM;
719     access->p_sys = sys;
720 
721     /* Parse the encoded URL */
722     if (vlc_UrlParseFixup(&sys->encoded_url, access->psz_url) != 0)
723         return VLC_ENOMEM;
724 
725     if (sys->encoded_url.psz_path == NULL)
726         sys->encoded_url.psz_path = (char *) "/";
727 
728     char *resolved_host = vlc_smb2_resolve(access, sys->encoded_url.psz_host,
729                                            sys->encoded_url.i_port);
730 
731     /* smb2_* functions need a decoded url. Re compose the url from the
732      * modified sys->encoded_url (with the resolved host). */
733     char *url;
734     if (resolved_host != NULL)
735     {
736         vlc_url_t resolved_url = sys->encoded_url;
737         resolved_url.psz_host = resolved_host;
738         url = vlc_uri_compose(&resolved_url);
739     }
740     else
741     {
742         url = vlc_uri_compose(&sys->encoded_url);
743     }
744     if (!vlc_uri_decode(url))
745     {
746         free(url);
747         free(resolved_host);
748         goto error;
749     }
750 
751     int ret = -1;
752     vlc_credential credential;
753     vlc_credential_init(&credential, &sys->encoded_url);
754     var_domain = var_InheritString(access, "smb-domain");
755     credential.psz_realm = var_domain;
756 
757     /* First, try Guest login or using "smb-" options (without
758      * keystore/user interaction) */
759     vlc_credential_get(&credential, access, "smb-user", "smb-pwd", NULL,
760                        NULL);
761     ret = vlc_smb2_open_share(access, url, &credential);
762 
763     while (ret == -1
764         && (!sys->error_status || VLC_SMB2_STATUS_DENIED(sys->error_status))
765         && vlc_credential_get(&credential, access, "smb-user", "smb-pwd",
766                               SMB_LOGIN_DIALOG_TITLE, SMB_LOGIN_DIALOG_TEXT,
767                               sys->encoded_url.psz_host))
768     {
769         sys->error_status = 0;
770         ret = vlc_smb2_open_share(access, url, &credential);
771     }
772     free(resolved_host);
773     free(url);
774     if (ret == 0)
775         vlc_credential_store(&credential, access);
776     vlc_credential_clean(&credential);
777 
778     if (ret != 0)
779     {
780         const char *error = smb2_get_error(sys->smb2);
781         if (error && *error)
782             vlc_dialog_display_error(access,
783                                      "SMB2 operation failed", "%s", error);
784         if (credential.i_get_order == GET_FROM_DIALOG)
785         {
786             /* Tell other smb modules (likely dsm) that we already requested
787              * credential to the users and that it it useless to try again.
788              * This avoid to show 2 login dialogs for the same access. */
789             var_Create(access, "smb-dialog-failed", VLC_VAR_VOID);
790         }
791         goto error;
792     }
793 
794     if (sys->smb2fh != NULL)
795     {
796         access->pf_read = FileRead;
797         access->pf_seek = FileSeek;
798         access->pf_control = FileControl;
799     }
800     else if (sys->smb2dir != NULL)
801     {
802         access->pf_readdir = DirRead;
803         access->pf_seek = NULL;
804         access->pf_control = access_vaDirectoryControlHelper;
805     }
806     else if (sys->share_enum != NULL)
807     {
808         access->pf_readdir = ShareEnum;
809         access->pf_seek = NULL;
810         access->pf_control = access_vaDirectoryControlHelper;
811     }
812     else
813         vlc_assert_unreachable();
814 
815     free(var_domain);
816     return VLC_SUCCESS;
817 
818 error:
819     vlc_UrlClean(&sys->encoded_url);
820     free(var_domain);
821 
822     /* Returning VLC_ETIMEOUT will stop the module probe and prevent to load
823      * the next smb module. The smb2 module can return this specific error in
824      * case of network error (EIO) or when the user asked to cancel it
825      * (vlc_killed()). Indeed, in these cases, it is useless to try next smb
826      * modules. */
827     return vlc_killed() || sys->error_status == -EIO ? VLC_ETIMEOUT
828          : VLC_EGENERIC;
829 }
830 
831 static void
Close(vlc_object_t * p_obj)832 Close(vlc_object_t *p_obj)
833 {
834     stream_t *access = (stream_t *)p_obj;
835     struct access_sys *sys = access->p_sys;
836 
837     if (sys->smb2fh != NULL)
838         vlc_smb2_close_fh(access);
839     else if (sys->smb2dir != NULL)
840         smb2_closedir(sys->smb2, sys->smb2dir);
841     else if (sys->share_enum != NULL)
842         smb2_free_data(sys->smb2, sys->share_enum);
843     else
844         vlc_assert_unreachable();
845 
846     vlc_smb2_disconnect_share(access);
847     smb2_destroy_context(sys->smb2);
848 
849     vlc_UrlClean(&sys->encoded_url);
850 }
851