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>
46 #include <smb2/smb2.h>
47 #include <smb2/libsmb2.h>
48 #include <smb2/libsmb2-raw.h>
51 # include <bdsm/netbios_ns.h>
52 # include <bdsm/netbios_defs.h>
54 # ifdef HAVE_ARPA_INET_H
55 # include <arpa/inet.h>
59 #include "smb_common.h"
63 static int Open(vlc_object_t
*);
64 static void Close(vlc_object_t
*);
68 set_description(N_("SMB2 / SMB3 input"))
69 set_help(N_("Samba (Windows network shares) input via libsmb2"))
70 set_capability("access", 21)
71 set_category(CAT_INPUT
)
72 set_subcategory(SUBCAT_INPUT_ACCESS
)
73 add_string("smb-user", NULL
, SMB_USER_TEXT
, SMB_USER_LONGTEXT
, false)
74 add_password("smb-pwd", NULL
, SMB_PASS_TEXT
, SMB_PASS_LONGTEXT
)
75 add_string("smb-domain", NULL
, SMB_DOMAIN_TEXT
, SMB_DOMAIN_LONGTEXT
, false)
76 add_shortcut("smb", "smb2")
77 set_callbacks(Open
, Close
)
82 struct smb2_context
* smb2
;
83 struct smb2fh
* smb2fh
;
84 struct smb2dir
* smb2dir
;
85 struct srvsvc_netshareenumall_rep
*share_enum
;
87 vlc_url_t encoded_url
;
102 smb2_check_status(stream_t
*access
, int status
, const char *psz_func
)
104 struct access_sys
*sys
= access
->p_sys
;
108 if (status
!= -EINTR
)
110 const char *psz_error
= vlc_strerror_c(-status
);
111 msg_Err(access
, "%s failed: %d, '%s'", psz_func
, status
, psz_error
);
112 if (sys
->error_status
== 0)
113 vlc_dialog_display_error(access
,
114 _("SMB2 operation failed"), "%s",
118 msg_Warn(access
, "%s interrupted", psz_func
);
119 sys
->error_status
= status
;
124 sys
->res_done
= true;
130 smb2_set_generic_error(stream_t
*access
, const char *psz_func
)
132 struct access_sys
*sys
= access
->p_sys
;
134 msg_Err(access
, "%s failed: %s", psz_func
, smb2_get_error(sys
->smb2
));
135 sys
->error_status
= 1;
138 #define VLC_SMB2_CHECK_STATUS(access, status) \
139 smb2_check_status(access, status, __func__)
141 #define VLC_SMB2_SET_GENERIC_ERROR(access, func) \
142 smb2_set_generic_error(access, func)
144 #define VLC_SMB2_STATUS_DENIED(x) (x == -ECONNREFUSED || x == -EACCES)
147 vlc_smb2_mainloop(stream_t
*access
, bool teardown
)
149 struct access_sys
*sys
= access
->p_sys
;
152 int (*poll_func
)(struct pollfd
*, unsigned, int) = vlc_poll_i11e
;
154 if (teardown
&& vlc_killed())
156 /* The thread is interrupted, so vlc_poll_i11e will return immediatly.
157 * Use poll() with a timeout instead for tear down. */
159 poll_func
= (void *)poll
;
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)
173 msg_Warn(access
, "vlc_poll_i11e interrupted");
175 msg_Err(access
, "vlc_poll_i11e failed");
176 sys
->error_status
= -errno
;
178 else if (ret
> 0 && p_fds
[0].revents
179 && smb2_service(sys
->smb2
, p_fds
[0].revents
) < 0)
180 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_service");
182 return sys
->error_status
== 0 ? 0 : -1;
185 #define VLC_SMB2_GENERIC_CB() \
187 stream_t *access = private_data; \
188 struct access_sys *sys = access->p_sys; \
189 assert(sys->smb2 == smb2); \
190 if (VLC_SMB2_CHECK_STATUS(access, status)) \
194 smb2_generic_cb(struct smb2_context
*smb2
, int status
, void *data
,
198 VLC_SMB2_GENERIC_CB();
202 smb2_read_cb(struct smb2_context
*smb2
, int status
, void *data
,
206 VLC_SMB2_GENERIC_CB();
211 sys
->res
.read
.len
= status
;
215 FileRead(stream_t
*access
, void *buf
, size_t len
)
217 struct access_sys
*sys
= access
->p_sys
;
219 if (sys
->error_status
!= 0)
225 sys
->res
.read
.len
= 0;
226 if (smb2_read_async(sys
->smb2
, sys
->smb2fh
, buf
, len
,
227 smb2_read_cb
, access
) < 0)
229 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_read_async");
233 if (vlc_smb2_mainloop(access
, false) < 0)
236 return sys
->res
.read
.len
;
240 FileSeek(stream_t
*access
, uint64_t i_pos
)
242 struct access_sys
*sys
= access
->p_sys
;
244 if (sys
->error_status
!= 0)
247 if (smb2_lseek(sys
->smb2
, sys
->smb2fh
, i_pos
, SEEK_SET
, NULL
) < 0)
249 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_seek_async");
258 FileControl(stream_t
*access
, int i_query
, va_list args
)
260 struct access_sys
*sys
= access
->p_sys
;
264 case STREAM_CAN_SEEK
:
265 *va_arg(args
, bool *) = true;
268 case STREAM_CAN_FASTSEEK
:
269 *va_arg(args
, bool *) = false;
272 case STREAM_CAN_PAUSE
:
273 case STREAM_CAN_CONTROL_PACE
:
274 *va_arg(args
, bool *) = true;
277 case STREAM_GET_SIZE
:
279 *va_arg(args
, uint64_t *) = sys
->smb2_size
;
283 case STREAM_GET_PTS_DELAY
:
284 *va_arg(args
, vlc_tick_t
* ) = VLC_TICK_FROM_MS(
285 var_InheritInteger(access
, "network-caching"));
288 case STREAM_SET_PAUSE_STATE
:
298 vlc_smb2_get_url(vlc_url_t
*url
, const char *file
)
300 /* smb2://<psz_host><psz_path><file>?<psz_option> */
302 if (asprintf(&buf
, "smb://%s%s%s%s%s%s", url
->psz_host
,
303 url
->psz_path
!= NULL
? url
->psz_path
: "",
304 url
->psz_path
!= NULL
&& url
->psz_path
[0] != '\0' &&
305 url
->psz_path
[strlen(url
->psz_path
) - 1] != '/' ? "/" : "",
307 url
->psz_option
!= NULL
? "?" : "",
308 url
->psz_option
!= NULL
? url
->psz_option
: "") == -1)
314 static int AddItem(stream_t
*access
, struct vlc_readdir_helper
*rdh
,
315 const char *name
, int i_type
)
317 struct access_sys
*sys
= access
->p_sys
;
318 char *name_encoded
= vlc_uri_encode(name
);
319 if (name_encoded
== NULL
)
322 char *url
= vlc_smb2_get_url(&sys
->encoded_url
, name_encoded
);
327 int ret
= vlc_readdir_helper_additem(rdh
, url
, NULL
, name
, i_type
,
334 DirRead(stream_t
*access
, input_item_node_t
*p_node
)
336 struct access_sys
*sys
= access
->p_sys
;
337 struct smb2dirent
*smb2dirent
;
338 int ret
= VLC_SUCCESS
;
339 assert(sys
->smb2dir
);
341 struct vlc_readdir_helper rdh
;
342 vlc_readdir_helper_init(&rdh
, access
, p_node
);
344 while (ret
== VLC_SUCCESS
345 && (smb2dirent
= smb2_readdir(sys
->smb2
, sys
->smb2dir
)) != NULL
)
348 switch (smb2dirent
->st
.smb2_type
)
351 i_type
= ITEM_TYPE_FILE
;
353 case SMB2_TYPE_DIRECTORY
:
354 i_type
= ITEM_TYPE_DIRECTORY
;
357 i_type
= ITEM_TYPE_UNKNOWN
;
360 ret
= AddItem(access
, &rdh
, smb2dirent
->name
, i_type
);
363 vlc_readdir_helper_finish(&rdh
, ret
== VLC_SUCCESS
);
369 ShareEnum(stream_t
*access
, input_item_node_t
*p_node
)
371 struct access_sys
*sys
= access
->p_sys
;
372 assert(sys
->share_enum
!= NULL
);
374 int ret
= VLC_SUCCESS
;
375 struct vlc_readdir_helper rdh
;
376 vlc_readdir_helper_init(&rdh
, access
, p_node
);
378 struct srvsvc_netsharectr
*ctr
= sys
->share_enum
->ctr
;
379 for (uint32_t iinfo
= 0;
380 iinfo
< ctr
->ctr1
.count
&& ret
== VLC_SUCCESS
; ++iinfo
)
382 struct srvsvc_netshareinfo1
*info
= &ctr
->ctr1
.array
[iinfo
];
383 if (info
->type
& SHARE_TYPE_HIDDEN
)
385 switch (info
->type
& 0x3)
387 case SHARE_TYPE_DISKTREE
:
388 ret
= AddItem(access
, &rdh
, info
->name
, ITEM_TYPE_DIRECTORY
);
393 vlc_readdir_helper_finish(&rdh
, ret
== VLC_SUCCESS
);
398 vlc_smb2_close_fh(stream_t
*access
)
400 struct access_sys
*sys
= access
->p_sys
;
404 if (smb2_close_async(sys
->smb2
, sys
->smb2fh
, smb2_generic_cb
, access
) < 0)
406 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_close_async");
412 return vlc_smb2_mainloop(access
, true);
416 vlc_smb2_disconnect_share(stream_t
*access
)
418 struct access_sys
*sys
= access
->p_sys
;
420 if (!sys
->smb2_connected
)
423 if (smb2_disconnect_share_async(sys
->smb2
, smb2_generic_cb
, access
) < 0)
425 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_connect_share_async");
429 int ret
= vlc_smb2_mainloop(access
, true);
430 sys
->smb2_connected
= false;
435 smb2_opendir_cb(struct smb2_context
*smb2
, int status
, void *data
,
438 VLC_SMB2_GENERIC_CB();
444 smb2_open_cb(struct smb2_context
*smb2
, int status
, void *data
,
447 VLC_SMB2_GENERIC_CB();
453 smb2_share_enum_cb(struct smb2_context
*smb2
, int status
, void *data
,
456 VLC_SMB2_GENERIC_CB();
458 sys
->share_enum
= data
;
462 vlc_smb2_open_share(stream_t
*access
, const struct smb2_url
*smb2_url
,
463 const vlc_credential
*credential
)
465 struct access_sys
*sys
= access
->p_sys
;
467 const bool do_enum
= smb2_url
->share
[0] == '\0';
468 const char *username
= credential
->psz_username
;
469 const char *password
= credential
->psz_password
;
470 const char *domain
= credential
->psz_realm
;
471 const char *share
= do_enum
? "IPC$" : smb2_url
->share
;
479 smb2_set_password(sys
->smb2
, password
);
480 smb2_set_domain(sys
->smb2
, domain
? domain
: "");
482 if (smb2_connect_share_async(sys
->smb2
, smb2_url
->server
, share
,
483 username
, smb2_generic_cb
, access
) < 0)
485 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_connect_share_async");
488 if (vlc_smb2_mainloop(access
, false) != 0)
490 sys
->smb2_connected
= true;
494 ret
= smb2_share_enum_async(sys
->smb2
, smb2_share_enum_cb
, access
);
497 struct smb2_stat_64 smb2_stat
;
498 if (smb2_stat_async(sys
->smb2
, smb2_url
->path
, &smb2_stat
,
499 smb2_generic_cb
, access
) < 0)
500 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_stat_async");
502 if (vlc_smb2_mainloop(access
, false) != 0)
505 if (smb2_stat
.smb2_type
== SMB2_TYPE_FILE
)
507 sys
->smb2_size
= smb2_stat
.smb2_size
;
508 ret
= smb2_open_async(sys
->smb2
, smb2_url
->path
, O_RDONLY
,
509 smb2_open_cb
, access
);
511 else if (smb2_stat
.smb2_type
== SMB2_TYPE_DIRECTORY
)
512 ret
= smb2_opendir_async(sys
->smb2
, smb2_url
->path
,
513 smb2_opendir_cb
, access
);
516 msg_Err(access
, "smb2_stat_cb: file type not handled");
517 sys
->error_status
= 1;
524 VLC_SMB2_SET_GENERIC_ERROR(access
, "smb2_open*_async");
528 if (vlc_smb2_mainloop(access
, false) != 0)
533 vlc_smb2_disconnect_share(access
);
538 vlc_smb2_resolve(stream_t
*access
, const char *host
, unsigned port
)
545 /* Test if the host is an IP */
547 if (inet_pton(AF_INET
, host
, &addr
) == 1)
550 /* Test if the host can be resolved */
551 struct addrinfo
*info
= NULL
;
552 if (vlc_getaddrinfo_i11e(host
, port
, NULL
, &info
) == 0)
555 /* Let smb2 resolve it */
559 /* Test if the host is a netbios name */
560 char *out_host
= NULL
;
561 netbios_ns
*ns
= netbios_ns_new();
563 if (netbios_ns_resolve(ns
, host
, NETBIOS_FILESERVER
, &ip4_addr
) == 0)
565 char ip
[] = "xxx.xxx.xxx.xxx";
566 if (inet_ntop(AF_INET
, &ip4_addr
, ip
, sizeof(ip
)))
567 out_host
= strdup(ip
);
569 netbios_ns_destroy(ns
);
578 Open(vlc_object_t
*p_obj
)
580 stream_t
*access
= (stream_t
*)p_obj
;
581 struct access_sys
*sys
= vlc_obj_calloc(p_obj
, 1, sizeof (*sys
));
582 struct smb2_url
*smb2_url
= NULL
;
583 char *var_domain
= NULL
;
585 if (unlikely(sys
== NULL
))
589 /* Parse the encoded URL */
590 if (vlc_UrlParseFixup(&sys
->encoded_url
, access
->psz_url
) != 0)
593 if (sys
->encoded_url
.i_port
!= 0 && sys
->encoded_url
.i_port
!= CIFS_PORT
)
595 sys
->encoded_url
.i_port
= 0;
597 sys
->smb2
= smb2_init_context();
598 if (sys
->smb2
== NULL
)
600 msg_Err(access
, "smb2_init_context failed");
604 if (sys
->encoded_url
.psz_path
== NULL
)
605 sys
->encoded_url
.psz_path
= (char *) "/";
607 char *resolved_host
= vlc_smb2_resolve(access
, sys
->encoded_url
.psz_host
,
610 /* smb2_* functions need a decoded url. Re compose the url from the
611 * modified sys->encoded_url (without port and with the resolved host). */
613 if (resolved_host
!= NULL
)
615 vlc_url_t resolved_url
= sys
->encoded_url
;
616 resolved_url
.psz_host
= resolved_host
;
617 url
= vlc_uri_compose(&resolved_url
);
621 url
= vlc_uri_compose(&sys
->encoded_url
);
622 if (!vlc_uri_decode(url
))
627 smb2_url
= smb2_parse_url(sys
->smb2
, url
);
630 if (!smb2_url
|| !smb2_url
->share
|| !smb2_url
->server
)
632 msg_Err(access
, "smb2_parse_url failed");
637 vlc_credential credential
;
638 vlc_credential_init(&credential
, &sys
->encoded_url
);
639 var_domain
= var_InheritString(access
, "smb-domain");
640 credential
.psz_realm
= var_domain
;
642 /* First, try Guest login or using "smb-" options (without
643 * keystore/user interaction) */
644 vlc_credential_get(&credential
, access
, "smb-user", "smb-pwd", NULL
,
646 ret
= vlc_smb2_open_share(access
, smb2_url
, &credential
);
649 && (!sys
->error_status
|| VLC_SMB2_STATUS_DENIED(sys
->error_status
))
650 && vlc_credential_get(&credential
, access
, "smb-user", "smb-pwd",
651 SMB_LOGIN_DIALOG_TITLE
, SMB_LOGIN_DIALOG_TEXT
,
654 sys
->error_status
= 0;
655 ret
= vlc_smb2_open_share(access
, smb2_url
, &credential
);
657 vlc_credential_store(&credential
, access
);
659 vlc_credential_clean(&credential
);
664 if (sys
->smb2fh
!= NULL
)
666 access
->pf_read
= FileRead
;
667 access
->pf_seek
= FileSeek
;
668 access
->pf_control
= FileControl
;
670 else if (sys
->smb2dir
!= NULL
)
672 access
->pf_readdir
= DirRead
;
673 access
->pf_seek
= NULL
;
674 access
->pf_control
= access_vaDirectoryControlHelper
;
676 else if (sys
->share_enum
!= NULL
)
678 access
->pf_readdir
= ShareEnum
;
679 access
->pf_seek
= NULL
;
680 access
->pf_control
= access_vaDirectoryControlHelper
;
683 vlc_assert_unreachable();
685 smb2_destroy_url(smb2_url
);
690 if (smb2_url
!= NULL
)
691 smb2_destroy_url(smb2_url
);
692 if (sys
->smb2
!= NULL
)
694 vlc_smb2_disconnect_share(access
);
695 smb2_destroy_context(sys
->smb2
);
697 vlc_UrlClean(&sys
->encoded_url
);
703 Close(vlc_object_t
*p_obj
)
705 stream_t
*access
= (stream_t
*)p_obj
;
706 struct access_sys
*sys
= access
->p_sys
;
708 if (sys
->smb2fh
!= NULL
)
709 vlc_smb2_close_fh(access
);
710 else if (sys
->smb2dir
!= NULL
)
711 smb2_closedir(sys
->smb2
, sys
->smb2dir
);
712 else if (sys
->share_enum
!= NULL
)
713 smb2_free_data(sys
->smb2
, sys
->share_enum
);
715 vlc_assert_unreachable();
717 vlc_smb2_disconnect_share(access
);
718 smb2_destroy_context(sys
->smb2
);
720 vlc_UrlClean(&sys
->encoded_url
);