1 /*****************************************************************************
2 * smb2.c: SMB2 access plug-in
3 *****************************************************************************
4 * Copyright © 2018 VLC authors, VideoLAN and VideoLabs
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.
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.
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 *****************************************************************************/
29 #include <sys/types.h>
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>
42 #include <vlc_keystore.h>
43 #include <vlc_interrupt.h>
44 #include <vlc_network.h>
45 #include <vlc_memstream.h>
47 #include <smb2/smb2.h>
48 #include <smb2/libsmb2.h>
49 #include <smb2/libsmb2-raw.h>
52 # include <bdsm/netbios_ns.h>
53 # include <bdsm/netbios_defs.h>
55 # ifdef HAVE_ARPA_INET_H
56 # include <arpa/inet.h>
60 #include "smb_common.h"
62 static int Open(vlc_object_t
*);
63 static void Close(vlc_object_t
*);
67 set_description(N_("SMB2 / SMB3 input"))
68 set_help(N_("Samba (Windows network shares) input via libsmb2"))
69 set_capability("access", 21)
70 set_category(CAT_INPUT
)
71 set_subcategory(SUBCAT_INPUT_ACCESS
)
72 add_string("smb-user", NULL
, SMB_USER_TEXT
, SMB_USER_LONGTEXT
, false)
73 add_password("smb-pwd", NULL
, SMB_PASS_TEXT
, SMB_PASS_LONGTEXT
)
74 add_string("smb-domain", NULL
, SMB_DOMAIN_TEXT
, SMB_DOMAIN_LONGTEXT
, false)
75 add_shortcut("smb", "smb2")
76 set_callbacks(Open
, Close
)
81 struct smb2_context
* smb2
;
82 struct smb2fh
* smb2fh
;
83 struct smb2dir
* smb2dir
;
84 struct srvsvc_netshareenumall_rep
*share_enum
;
86 vlc_url_t encoded_url
;
101 smb2_check_status(stream_t
*access
, int status
, const char *psz_func
)
103 struct access_sys
*sys
= access
->p_sys
;
107 const char *psz_error
= smb2_get_error(sys
->smb2
);
108 msg_Warn(access
, "%s failed: %d, '%s'", psz_func
, status
, psz_error
);
109 sys
->error_status
= status
;
114 sys
->res_done
= true;
120 smb2_set_error(stream_t
*access
, const char *psz_func
, int err
)
122 struct access_sys
*sys
= access
->p_sys
;
124 msg_Err(access
, "%s failed: %d, %s", psz_func
, err
,
125 smb2_get_error(sys
->smb2
));
126 sys
->error_status
= err
;
129 #define VLC_SMB2_CHECK_STATUS(access, status) \
130 smb2_check_status(access, status, __func__)
132 #define VLC_SMB2_SET_ERROR(access, func, err) \
133 smb2_set_error(access, func, err)
135 #define VLC_SMB2_STATUS_DENIED(x) (x == -ECONNREFUSED || x == -EACCES)
138 vlc_smb2_mainloop(stream_t
*access
, bool teardown
)
140 #define TEARDOWN_TIMEOUT 250 /* in ms */
141 struct access_sys
*sys
= access
->p_sys
;
144 int (*poll_func
)(struct pollfd
*, unsigned, int) = vlc_poll_i11e
;
146 /* vlc_smb2_mainloop() can be called to clean-up after an error, but this
147 * function can override the error_status (from async cbs). Therefore,
148 * store the original error_status in order to restore it at the end of
149 * this call (since we want to keep the first and original error status). */
150 int original_error_status
= sys
->error_status
;
154 /* Don't use vlc_poll_i11e that will return immediately with the EINTR
155 * errno if VLC's input is interrupted. Use the posix poll with a
156 * timeout to let a chance for a clean teardown. */
157 timeout
= TEARDOWN_TIMEOUT
;
158 poll_func
= (void *)poll
;
159 sys
->error_status
= 0;
162 sys
->res_done
= false;
163 while (sys
->error_status
== 0 && !sys
->res_done
)
165 struct pollfd p_fds
[1];
167 p_fds
[0].fd
= smb2_get_fd(sys
->smb2
);
168 p_fds
[0].events
= smb2_which_events(sys
->smb2
);
170 if (p_fds
[0].fd
== -1 || (ret
= poll_func(p_fds
, 1, timeout
)) < 0)
174 msg_Warn(access
, "vlc_poll_i11e interrupted");
175 if (poll_func
!= (void *) poll
)
177 /* Try again with a timeout to let the command complete.
178 * Indeed, if this command is interrupted, every future
179 * commands will fail and we won't be able to teardown. */
180 timeout
= TEARDOWN_TIMEOUT
;
181 poll_func
= (void *) poll
;
184 sys
->error_status
= -errno
;
188 msg_Err(access
, "vlc_poll_i11e failed");
189 sys
->error_status
= -errno
;
193 sys
->error_status
= -ETIMEDOUT
;
194 else if (ret
> 0 && p_fds
[0].revents
195 && smb2_service(sys
->smb2
, p_fds
[0].revents
) < 0)
196 VLC_SMB2_SET_ERROR(access
, "smb2_service", 1);
199 int ret
= sys
->error_status
== 0 ? 0 : -1;
200 if (original_error_status
!= 0)
201 sys
->error_status
= original_error_status
;
205 #define VLC_SMB2_GENERIC_CB() \
207 stream_t *access = private_data; \
208 struct access_sys *sys = access->p_sys; \
209 assert(sys->smb2 == smb2); \
210 if (VLC_SMB2_CHECK_STATUS(access, status)) \
214 smb2_generic_cb(struct smb2_context
*smb2
, int status
, void *data
,
218 VLC_SMB2_GENERIC_CB();
222 smb2_read_cb(struct smb2_context
*smb2
, int status
, void *data
,
226 VLC_SMB2_GENERIC_CB();
231 sys
->res
.read
.len
= status
;
235 FileRead(stream_t
*access
, void *buf
, size_t len
)
237 struct access_sys
*sys
= access
->p_sys
;
239 if (sys
->error_status
!= 0)
245 /* Limit the read size since smb2_read_async() will complete only after
246 * reading the whole requested data and not when whatever data is available
247 * (high read size means a faster I/O but a higher latency). */
251 sys
->res
.read
.len
= 0;
252 if (smb2_read_async(sys
->smb2
, sys
->smb2fh
, buf
, len
,
253 smb2_read_cb
, access
) < 0)
255 VLC_SMB2_SET_ERROR(access
, "smb2_read_async", 1);
259 if (vlc_smb2_mainloop(access
, false) < 0)
262 return sys
->res
.read
.len
;
266 FileSeek(stream_t
*access
, uint64_t i_pos
)
268 struct access_sys
*sys
= access
->p_sys
;
270 if (sys
->error_status
!= 0)
273 if (smb2_lseek(sys
->smb2
, sys
->smb2fh
, i_pos
, SEEK_SET
, NULL
) < 0)
275 VLC_SMB2_SET_ERROR(access
, "smb2_seek_async", 1);
284 FileControl(stream_t
*access
, int i_query
, va_list args
)
286 struct access_sys
*sys
= access
->p_sys
;
290 case STREAM_CAN_SEEK
:
291 *va_arg(args
, bool *) = true;
294 case STREAM_CAN_FASTSEEK
:
295 *va_arg(args
, bool *) = false;
298 case STREAM_CAN_PAUSE
:
299 case STREAM_CAN_CONTROL_PACE
:
300 *va_arg(args
, bool *) = true;
303 case STREAM_GET_SIZE
:
305 *va_arg(args
, uint64_t *) = sys
->smb2_size
;
309 case STREAM_GET_PTS_DELAY
:
310 *va_arg(args
, vlc_tick_t
* ) = VLC_TICK_FROM_MS(
311 var_InheritInteger(access
, "network-caching"));
314 case STREAM_SET_PAUSE_STATE
:
324 vlc_smb2_get_url(vlc_url_t
*url
, const char *file
)
326 /* smb2://<psz_host><i_port><psz_path><file>?<psz_option> */
327 struct vlc_memstream buf
;
328 vlc_memstream_open(&buf
);
329 if (strchr(url
->psz_host
, ':') != NULL
)
330 vlc_memstream_printf(&buf
, "smb://[%s]", url
->psz_host
);
332 vlc_memstream_printf(&buf
, "smb://%s", url
->psz_host
);
334 if (url
->i_port
!= 0)
335 vlc_memstream_printf(&buf
, ":%d", url
->i_port
);
337 if (url
->psz_path
!= NULL
)
339 vlc_memstream_puts(&buf
, url
->psz_path
);
340 if (url
->psz_path
[0] != '\0' && url
->psz_path
[strlen(url
->psz_path
) - 1] != '/')
341 vlc_memstream_putc(&buf
, '/');
344 vlc_memstream_putc(&buf
, '/');
346 vlc_memstream_puts(&buf
, file
);
349 vlc_memstream_printf(&buf
, "?%s", url
->psz_option
);
351 if (vlc_memstream_close(&buf
))
356 static int AddItem(stream_t
*access
, struct vlc_readdir_helper
*rdh
,
357 const char *name
, int i_type
)
359 struct access_sys
*sys
= access
->p_sys
;
360 char *name_encoded
= vlc_uri_encode(name
);
361 if (name_encoded
== NULL
)
364 char *url
= vlc_smb2_get_url(&sys
->encoded_url
, name_encoded
);
369 int ret
= vlc_readdir_helper_additem(rdh
, url
, NULL
, name
, i_type
,
376 DirRead(stream_t
*access
, input_item_node_t
*p_node
)
378 struct access_sys
*sys
= access
->p_sys
;
379 struct smb2dirent
*smb2dirent
;
380 int ret
= VLC_SUCCESS
;
381 assert(sys
->smb2dir
);
383 struct vlc_readdir_helper rdh
;
384 vlc_readdir_helper_init(&rdh
, access
, p_node
);
386 while (ret
== VLC_SUCCESS
387 && (smb2dirent
= smb2_readdir(sys
->smb2
, sys
->smb2dir
)) != NULL
)
390 switch (smb2dirent
->st
.smb2_type
)
393 i_type
= ITEM_TYPE_FILE
;
395 case SMB2_TYPE_DIRECTORY
:
396 i_type
= ITEM_TYPE_DIRECTORY
;
399 i_type
= ITEM_TYPE_UNKNOWN
;
402 ret
= AddItem(access
, &rdh
, smb2dirent
->name
, i_type
);
405 vlc_readdir_helper_finish(&rdh
, ret
== VLC_SUCCESS
);
411 ShareEnum(stream_t
*access
, input_item_node_t
*p_node
)
413 struct access_sys
*sys
= access
->p_sys
;
414 assert(sys
->share_enum
!= NULL
);
416 int ret
= VLC_SUCCESS
;
417 struct vlc_readdir_helper rdh
;
418 vlc_readdir_helper_init(&rdh
, access
, p_node
);
420 struct srvsvc_netsharectr
*ctr
= sys
->share_enum
->ctr
;
421 for (uint32_t iinfo
= 0;
422 iinfo
< ctr
->ctr1
.count
&& ret
== VLC_SUCCESS
; ++iinfo
)
424 struct srvsvc_netshareinfo1
*info
= &ctr
->ctr1
.array
[iinfo
];
425 if (info
->type
& SHARE_TYPE_HIDDEN
)
427 switch (info
->type
& 0x3)
429 case SHARE_TYPE_DISKTREE
:
430 ret
= AddItem(access
, &rdh
, info
->name
, ITEM_TYPE_DIRECTORY
);
435 vlc_readdir_helper_finish(&rdh
, ret
== VLC_SUCCESS
);
440 vlc_smb2_close_fh(stream_t
*access
)
442 struct access_sys
*sys
= access
->p_sys
;
446 if (smb2_close_async(sys
->smb2
, sys
->smb2fh
, smb2_generic_cb
, access
) < 0)
448 VLC_SMB2_SET_ERROR(access
, "smb2_close_async", 1);
454 return vlc_smb2_mainloop(access
, true);
458 vlc_smb2_disconnect_share(stream_t
*access
)
460 struct access_sys
*sys
= access
->p_sys
;
462 if (!sys
->smb2_connected
)
465 if (smb2_disconnect_share_async(sys
->smb2
, smb2_generic_cb
, access
) < 0)
467 VLC_SMB2_SET_ERROR(access
, "smb2_connect_share_async", 1);
471 int ret
= vlc_smb2_mainloop(access
, true);
472 sys
->smb2_connected
= false;
477 smb2_opendir_cb(struct smb2_context
*smb2
, int status
, void *data
,
480 VLC_SMB2_GENERIC_CB();
486 smb2_open_cb(struct smb2_context
*smb2
, int status
, void *data
,
489 VLC_SMB2_GENERIC_CB();
495 smb2_share_enum_cb(struct smb2_context
*smb2
, int status
, void *data
,
498 VLC_SMB2_GENERIC_CB();
500 sys
->share_enum
= data
;
504 vlc_smb2_open_share(stream_t
*access
, const struct smb2_url
*smb2_url
,
505 const vlc_credential
*credential
)
507 struct access_sys
*sys
= access
->p_sys
;
509 const bool do_enum
= smb2_url
->share
[0] == '\0';
510 const char *username
= credential
->psz_username
;
511 const char *password
= credential
->psz_password
;
512 const char *domain
= credential
->psz_realm
;
513 const char *share
= do_enum
? "IPC$" : smb2_url
->share
;
518 /* A NULL password enable ntlmssp anonymous login */
522 smb2_set_password(sys
->smb2
, password
);
523 smb2_set_domain(sys
->smb2
, domain
? domain
: "");
525 int err
= smb2_connect_share_async(sys
->smb2
, smb2_url
->server
, share
,
526 username
, smb2_generic_cb
, access
);
529 VLC_SMB2_SET_ERROR(access
, "smb2_connect_share_async", err
);
532 if (vlc_smb2_mainloop(access
, false) != 0)
534 sys
->smb2_connected
= true;
538 ret
= smb2_share_enum_async(sys
->smb2
, smb2_share_enum_cb
, access
);
541 struct smb2_stat_64 smb2_stat
;
542 if (smb2_stat_async(sys
->smb2
, smb2_url
->path
, &smb2_stat
,
543 smb2_generic_cb
, access
) < 0)
544 VLC_SMB2_SET_ERROR(access
, "smb2_stat_async", 1);
546 if (vlc_smb2_mainloop(access
, false) != 0)
549 if (smb2_stat
.smb2_type
== SMB2_TYPE_FILE
)
551 sys
->smb2_size
= smb2_stat
.smb2_size
;
552 ret
= smb2_open_async(sys
->smb2
, smb2_url
->path
, O_RDONLY
,
553 smb2_open_cb
, access
);
555 else if (smb2_stat
.smb2_type
== SMB2_TYPE_DIRECTORY
)
556 ret
= smb2_opendir_async(sys
->smb2
, smb2_url
->path
,
557 smb2_opendir_cb
, access
);
560 msg_Err(access
, "smb2_stat_cb: file type not handled");
561 sys
->error_status
= 1;
568 VLC_SMB2_SET_ERROR(access
, "smb2_open*_async", 1);
572 if (vlc_smb2_mainloop(access
, false) != 0)
577 vlc_smb2_disconnect_share(access
);
582 vlc_smb2_resolve(stream_t
*access
, const char *host
, unsigned port
)
589 /* Test if the host is an IP */
591 if (inet_pton(AF_INET
, host
, &addr
) == 1)
594 /* Test if the host can be resolved */
595 struct addrinfo
*info
= NULL
;
596 if (vlc_getaddrinfo_i11e(host
, port
, NULL
, &info
) == 0)
599 /* Let smb2 resolve it */
603 /* Test if the host is a netbios name */
604 char *out_host
= NULL
;
605 netbios_ns
*ns
= netbios_ns_new();
609 if (netbios_ns_resolve(ns
, host
, NETBIOS_FILESERVER
, &ip4_addr
) == 0)
611 char ip
[] = "xxx.xxx.xxx.xxx";
612 if (inet_ntop(AF_INET
, &ip4_addr
, ip
, sizeof(ip
)))
613 out_host
= strdup(ip
);
615 netbios_ns_destroy(ns
);
624 Open(vlc_object_t
*p_obj
)
626 stream_t
*access
= (stream_t
*)p_obj
;
627 struct access_sys
*sys
= vlc_obj_calloc(p_obj
, 1, sizeof (*sys
));
628 struct smb2_url
*smb2_url
= NULL
;
629 char *var_domain
= NULL
;
631 if (unlikely(sys
== NULL
))
635 /* Parse the encoded URL */
636 if (vlc_UrlParseFixup(&sys
->encoded_url
, access
->psz_url
) != 0)
639 sys
->smb2
= smb2_init_context();
640 if (sys
->smb2
== NULL
)
642 msg_Err(access
, "smb2_init_context failed");
646 smb2_set_security_mode(sys
->smb2
, SMB2_NEGOTIATE_SIGNING_ENABLED
);
648 if (sys
->encoded_url
.psz_path
== NULL
)
649 sys
->encoded_url
.psz_path
= (char *) "/";
651 char *resolved_host
= vlc_smb2_resolve(access
, sys
->encoded_url
.psz_host
,
652 sys
->encoded_url
.i_port
);
654 /* smb2_* functions need a decoded url. Re compose the url from the
655 * modified sys->encoded_url (with the resolved host). */
657 if (resolved_host
!= NULL
)
659 vlc_url_t resolved_url
= sys
->encoded_url
;
660 resolved_url
.psz_host
= resolved_host
;
661 url
= vlc_uri_compose(&resolved_url
);
665 url
= vlc_uri_compose(&sys
->encoded_url
);
666 if (!vlc_uri_decode(url
))
671 smb2_url
= smb2_parse_url(sys
->smb2
, url
);
674 if (!smb2_url
|| !smb2_url
->share
|| !smb2_url
->server
)
676 msg_Err(access
, "smb2_parse_url failed");
681 vlc_credential credential
;
682 vlc_credential_init(&credential
, &sys
->encoded_url
);
683 var_domain
= var_InheritString(access
, "smb-domain");
684 credential
.psz_realm
= var_domain
;
686 /* First, try Guest login or using "smb-" options (without
687 * keystore/user interaction) */
688 vlc_credential_get(&credential
, access
, "smb-user", "smb-pwd", NULL
,
690 ret
= vlc_smb2_open_share(access
, smb2_url
, &credential
);
693 && (!sys
->error_status
|| VLC_SMB2_STATUS_DENIED(sys
->error_status
))
694 && vlc_credential_get(&credential
, access
, "smb-user", "smb-pwd",
695 SMB_LOGIN_DIALOG_TITLE
, SMB_LOGIN_DIALOG_TEXT
,
698 sys
->error_status
= 0;
699 ret
= vlc_smb2_open_share(access
, smb2_url
, &credential
);
702 vlc_credential_store(&credential
, access
);
703 vlc_credential_clean(&credential
);
707 const char *error
= smb2_get_error(sys
->smb2
);
709 vlc_dialog_display_error(access
,
710 _("SMB2 operation failed"), "%s", error
);
711 if (credential
.i_get_order
== GET_FROM_DIALOG
)
713 /* Tell other smb modules (likely dsm) that we already requested
714 * credential to the users and that it it useless to try again.
715 * This avoid to show 2 login dialogs for the same access. */
716 var_Create(access
, "smb-dialog-failed", VLC_VAR_VOID
);
721 if (sys
->smb2fh
!= NULL
)
723 access
->pf_read
= FileRead
;
724 access
->pf_seek
= FileSeek
;
725 access
->pf_control
= FileControl
;
727 else if (sys
->smb2dir
!= NULL
)
729 access
->pf_readdir
= DirRead
;
730 access
->pf_seek
= NULL
;
731 access
->pf_control
= access_vaDirectoryControlHelper
;
733 else if (sys
->share_enum
!= NULL
)
735 access
->pf_readdir
= ShareEnum
;
736 access
->pf_seek
= NULL
;
737 access
->pf_control
= access_vaDirectoryControlHelper
;
740 vlc_assert_unreachable();
742 smb2_destroy_url(smb2_url
);
747 if (smb2_url
!= NULL
)
748 smb2_destroy_url(smb2_url
);
749 if (sys
->smb2
!= NULL
)
751 vlc_smb2_disconnect_share(access
);
752 smb2_destroy_context(sys
->smb2
);
754 vlc_UrlClean(&sys
->encoded_url
);
757 /* Returning VLC_ETIMEOUT will stop the module probe and prevent to load
758 * the next smb module. The smb2 module can return this specific error in
759 * case of network error (EIO) or when the user asked to cancel it
760 * (vlc_killed()). Indeed, in these cases, it is useless to try next smb
762 return vlc_killed() || sys
->error_status
== -EIO
? VLC_ETIMEOUT
767 Close(vlc_object_t
*p_obj
)
769 stream_t
*access
= (stream_t
*)p_obj
;
770 struct access_sys
*sys
= access
->p_sys
;
772 if (sys
->smb2fh
!= NULL
)
773 vlc_smb2_close_fh(access
);
774 else if (sys
->smb2dir
!= NULL
)
775 smb2_closedir(sys
->smb2
, sys
->smb2dir
);
776 else if (sys
->share_enum
!= NULL
)
777 smb2_free_data(sys
->smb2
, sys
->share_enum
);
779 vlc_assert_unreachable();
781 vlc_smb2_disconnect_share(access
);
782 smb2_destroy_context(sys
->smb2
);
784 vlc_UrlClean(&sys
->encoded_url
);