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