lib: media_player: fix aout callbacks having no effects
[vlc.git] / modules / access / smb2.c
blob531e93206d06735e5b96e04aaadf96dfe6ec0614
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 *****************************************************************************/
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
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_H
33 # include <poll.h>
34 #endif
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 #include <vlc_memstream.h>
47 #include <smb2/smb2.h>
48 #include <smb2/libsmb2.h>
49 #include <smb2/libsmb2-raw.h>
51 #ifdef HAVE_DSM
52 # include <bdsm/netbios_ns.h>
53 # include <bdsm/netbios_defs.h>
55 # ifdef HAVE_ARPA_INET_H
56 # include <arpa/inet.h>
57 # endif
58 #endif
60 #include "smb_common.h"
62 static int Open(vlc_object_t *);
63 static void Close(vlc_object_t *);
65 vlc_module_begin()
66 set_shortname("smb2")
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)
77 vlc_module_end()
79 struct access_sys
81 struct smb2_context * smb2;
82 struct smb2fh * smb2fh;
83 struct smb2dir * smb2dir;
84 struct srvsvc_netshareenumall_rep *share_enum;
85 uint64_t smb2_size;
86 vlc_url_t encoded_url;
87 bool eof;
88 bool smb2_connected;
89 int error_status;
91 bool res_done;
92 union {
93 struct
95 size_t len;
96 } read;
97 } res;
100 static int
101 smb2_check_status(stream_t *access, int status, const char *psz_func)
103 struct access_sys *sys = access->p_sys;
105 if (status < 0)
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;
110 return -1;
112 else
114 sys->res_done = true;
115 return 0;
119 static void
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)
137 static int
138 vlc_smb2_mainloop(stream_t *access, bool teardown)
140 #define TEARDOWN_TIMEOUT 250 /* in ms */
141 struct access_sys *sys = access->p_sys;
143 int timeout = -1;
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;
152 if (teardown)
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];
166 int ret;
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)
172 if (errno == EINTR)
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;
183 else
184 sys->error_status = -errno;
186 else
188 msg_Err(access, "vlc_poll_i11e failed");
189 sys->error_status = -errno;
192 else if (ret == 0)
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;
202 return ret;
205 #define VLC_SMB2_GENERIC_CB() \
206 VLC_UNUSED(smb2); \
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)) \
211 return
213 static void
214 smb2_generic_cb(struct smb2_context *smb2, int status, void *data,
215 void *private_data)
217 VLC_UNUSED(data);
218 VLC_SMB2_GENERIC_CB();
221 static void
222 smb2_read_cb(struct smb2_context *smb2, int status, void *data,
223 void *private_data)
225 VLC_UNUSED(data);
226 VLC_SMB2_GENERIC_CB();
228 if (status == 0)
229 sys->eof = true;
230 else
231 sys->res.read.len = status;
234 static ssize_t
235 FileRead(stream_t *access, void *buf, size_t len)
237 struct access_sys *sys = access->p_sys;
239 if (sys->error_status != 0)
240 return -1;
242 if (sys->eof)
243 return 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). */
248 if (len > 262144)
249 len = 262144;
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);
256 return -1;
259 if (vlc_smb2_mainloop(access, false) < 0)
260 return -1;
262 return sys->res.read.len;
265 static int
266 FileSeek(stream_t *access, uint64_t i_pos)
268 struct access_sys *sys = access->p_sys;
270 if (sys->error_status != 0)
271 return VLC_EGENERIC;
273 if (smb2_lseek(sys->smb2, sys->smb2fh, i_pos, SEEK_SET, NULL) < 0)
275 VLC_SMB2_SET_ERROR(access, "smb2_seek_async", 1);
276 return VLC_EGENERIC;
278 sys->eof = false;
280 return VLC_SUCCESS;
283 static int
284 FileControl(stream_t *access, int i_query, va_list args)
286 struct access_sys *sys = access->p_sys;
288 switch (i_query)
290 case STREAM_CAN_SEEK:
291 *va_arg(args, bool *) = true;
292 break;
294 case STREAM_CAN_FASTSEEK:
295 *va_arg(args, bool *) = false;
296 break;
298 case STREAM_CAN_PAUSE:
299 case STREAM_CAN_CONTROL_PACE:
300 *va_arg(args, bool *) = true;
301 break;
303 case STREAM_GET_SIZE:
305 *va_arg(args, uint64_t *) = sys->smb2_size;
306 break;
309 case STREAM_GET_PTS_DELAY:
310 *va_arg(args, vlc_tick_t * ) = VLC_TICK_FROM_MS(
311 var_InheritInteger(access, "network-caching"));
312 break;
314 case STREAM_SET_PAUSE_STATE:
315 break;
317 default:
318 return VLC_EGENERIC;
320 return VLC_SUCCESS;
323 static char *
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);
331 else
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, '/');
343 else
344 vlc_memstream_putc(&buf, '/');
346 vlc_memstream_puts(&buf, file);
348 if (url->psz_option)
349 vlc_memstream_printf(&buf, "?%s", url->psz_option);
351 if (vlc_memstream_close(&buf))
352 return NULL;
353 return buf.ptr;
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)
362 return VLC_ENOMEM;
364 char *url = vlc_smb2_get_url(&sys->encoded_url, name_encoded);
365 free(name_encoded);
366 if (url == NULL)
367 return VLC_ENOMEM;
369 int ret = vlc_readdir_helper_additem(rdh, url, NULL, name, i_type,
370 ITEM_NET);
371 free(url);
372 return ret;
375 static int
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)
389 int i_type;
390 switch (smb2dirent->st.smb2_type)
392 case SMB2_TYPE_FILE:
393 i_type = ITEM_TYPE_FILE;
394 break;
395 case SMB2_TYPE_DIRECTORY:
396 i_type = ITEM_TYPE_DIRECTORY;
397 break;
398 default:
399 i_type = ITEM_TYPE_UNKNOWN;
400 break;
402 ret = AddItem(access, &rdh, smb2dirent->name, i_type);
405 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
407 return ret;
410 static int
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)
426 continue;
427 switch (info->type & 0x3)
429 case SHARE_TYPE_DISKTREE:
430 ret = AddItem(access, &rdh, info->name, ITEM_TYPE_DIRECTORY);
431 break;
435 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
436 return 0;
439 static int
440 vlc_smb2_close_fh(stream_t *access)
442 struct access_sys *sys = access->p_sys;
444 assert(sys->smb2fh);
446 if (smb2_close_async(sys->smb2, sys->smb2fh, smb2_generic_cb, access) < 0)
448 VLC_SMB2_SET_ERROR(access, "smb2_close_async", 1);
449 return -1;
452 sys->smb2fh = NULL;
454 return vlc_smb2_mainloop(access, true);
457 static int
458 vlc_smb2_disconnect_share(stream_t *access)
460 struct access_sys *sys = access->p_sys;
462 if (!sys->smb2_connected)
463 return 0;
465 if (smb2_disconnect_share_async(sys->smb2, smb2_generic_cb, access) < 0)
467 VLC_SMB2_SET_ERROR(access, "smb2_connect_share_async", 1);
468 return -1;
471 int ret = vlc_smb2_mainloop(access, true);
472 sys->smb2_connected = false;
473 return ret;
476 static void
477 smb2_opendir_cb(struct smb2_context *smb2, int status, void *data,
478 void *private_data)
480 VLC_SMB2_GENERIC_CB();
482 sys->smb2dir = data;
485 static void
486 smb2_open_cb(struct smb2_context *smb2, int status, void *data,
487 void *private_data)
489 VLC_SMB2_GENERIC_CB();
491 sys->smb2fh = data;
494 static void
495 smb2_share_enum_cb(struct smb2_context *smb2, int status, void *data,
496 void *private_data)
498 VLC_SMB2_GENERIC_CB();
500 sys->share_enum = data;
503 static int
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;
515 if (!username)
517 username = "Guest";
518 /* A NULL password enable ntlmssp anonymous login */
519 password = NULL;
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);
527 if (err < 0)
529 VLC_SMB2_SET_ERROR(access, "smb2_connect_share_async", err);
530 goto error;
532 if (vlc_smb2_mainloop(access, false) != 0)
533 goto error;
534 sys->smb2_connected = true;
536 int ret;
537 if (do_enum)
538 ret = smb2_share_enum_async(sys->smb2, smb2_share_enum_cb, access);
539 else
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)
547 goto error;
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);
558 else
560 msg_Err(access, "smb2_stat_cb: file type not handled");
561 sys->error_status = 1;
562 goto error;
566 if (ret < 0)
568 VLC_SMB2_SET_ERROR(access, "smb2_open*_async", 1);
569 goto error;
572 if (vlc_smb2_mainloop(access, false) != 0)
573 goto error;
574 return 0;
576 error:
577 vlc_smb2_disconnect_share(access);
578 return -1;
581 static char *
582 vlc_smb2_resolve(stream_t *access, const char *host, unsigned port)
584 (void) access;
585 if (!host)
586 return NULL;
588 #ifdef HAVE_DSM
589 /* Test if the host is an IP */
590 struct in_addr addr;
591 if (inet_pton(AF_INET, host, &addr) == 1)
592 return NULL;
594 /* Test if the host can be resolved */
595 struct addrinfo *info = NULL;
596 if (vlc_getaddrinfo_i11e(host, port, NULL, &info) == 0)
598 freeaddrinfo(info);
599 /* Let smb2 resolve it */
600 return NULL;
603 /* Test if the host is a netbios name */
604 char *out_host = NULL;
605 netbios_ns *ns = netbios_ns_new();
606 if (!ns)
607 return NULL;
608 uint32_t ip4_addr;
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);
616 return out_host;
617 #else
618 (void) port;
619 return NULL;
620 #endif
623 static int
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))
632 return VLC_ENOMEM;
633 access->p_sys = sys;
635 /* Parse the encoded URL */
636 if (vlc_UrlParseFixup(&sys->encoded_url, access->psz_url) != 0)
637 return VLC_ENOMEM;
639 sys->smb2 = smb2_init_context();
640 if (sys->smb2 == NULL)
642 msg_Err(access, "smb2_init_context failed");
643 goto error;
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). */
656 char *url;
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);
662 free(resolved_host);
664 else
665 url = vlc_uri_compose(&sys->encoded_url);
666 if (!vlc_uri_decode(url))
668 free(url);
669 goto error;
671 smb2_url = smb2_parse_url(sys->smb2, url);
672 free(url);
674 if (!smb2_url || !smb2_url->share || !smb2_url->server)
676 msg_Err(access, "smb2_parse_url failed");
677 goto error;
680 int ret = -1;
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,
689 NULL);
690 ret = vlc_smb2_open_share(access, smb2_url, &credential);
692 while (ret == -1
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,
696 smb2_url->server))
698 sys->error_status = 0;
699 ret = vlc_smb2_open_share(access, smb2_url, &credential);
701 if (ret == 0)
702 vlc_credential_store(&credential, access);
703 vlc_credential_clean(&credential);
705 if (ret != 0)
707 const char *error = smb2_get_error(sys->smb2);
708 if (error && *error)
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);
718 goto error;
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;
739 else
740 vlc_assert_unreachable();
742 smb2_destroy_url(smb2_url);
743 free(var_domain);
744 return VLC_SUCCESS;
746 error:
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);
755 free(var_domain);
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
761 * modules. */
762 return vlc_killed() || sys->error_status == -EIO ? VLC_ETIMEOUT
763 : VLC_EGENERIC;
766 static void
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);
778 else
779 vlc_assert_unreachable();
781 vlc_smb2_disconnect_share(access);
782 smb2_destroy_context(sys->smb2);
784 vlc_UrlClean(&sys->encoded_url);