codec: subsdec: fix variable shadowing
[vlc.git] / modules / access / smb2.c
blob280c5441747c0821e125e34747b3ec8235830ec4
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
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>
46 #include <smb2/smb2.h>
47 #include <smb2/libsmb2.h>
48 #include <smb2/libsmb2-raw.h>
50 #ifdef HAVE_DSM
51 # include <bdsm/netbios_ns.h>
52 # include <bdsm/netbios_defs.h>
54 # ifdef HAVE_ARPA_INET_H
55 # include <arpa/inet.h>
56 # endif
57 #endif
59 #include "smb_common.h"
61 #define CIFS_PORT 445
63 static int Open(vlc_object_t *);
64 static void Close(vlc_object_t *);
66 vlc_module_begin()
67 set_shortname("smb2")
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)
78 vlc_module_end()
80 struct access_sys
82 struct smb2_context * smb2;
83 struct smb2fh * smb2fh;
84 struct smb2dir * smb2dir;
85 struct srvsvc_netshareenumall_rep *share_enum;
86 uint64_t smb2_size;
87 vlc_url_t encoded_url;
88 bool eof;
89 bool smb2_connected;
90 int error_status;
92 bool res_done;
93 union {
94 struct
96 size_t len;
97 } read;
98 } res;
101 static int
102 smb2_check_status(stream_t *access, int status, const char *psz_func)
104 struct access_sys *sys = access->p_sys;
106 if (status < 0)
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",
115 psz_error);
117 else
118 msg_Warn(access, "%s interrupted", psz_func);
119 sys->error_status = status;
120 return -1;
122 else
124 sys->res_done = true;
125 return 0;
129 static void
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)
146 static int
147 vlc_smb2_mainloop(stream_t *access, bool teardown)
149 struct access_sys *sys = access->p_sys;
151 int timeout = -1;
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. */
158 timeout = 500;
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];
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)
173 msg_Warn(access, "vlc_poll_i11e interrupted");
174 else
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() \
186 VLC_UNUSED(smb2); \
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)) \
191 return
193 static void
194 smb2_generic_cb(struct smb2_context *smb2, int status, void *data,
195 void *private_data)
197 VLC_UNUSED(data);
198 VLC_SMB2_GENERIC_CB();
201 static void
202 smb2_read_cb(struct smb2_context *smb2, int status, void *data,
203 void *private_data)
205 VLC_UNUSED(data);
206 VLC_SMB2_GENERIC_CB();
208 if (status == 0)
209 sys->eof = true;
210 else
211 sys->res.read.len = status;
214 static ssize_t
215 FileRead(stream_t *access, void *buf, size_t len)
217 struct access_sys *sys = access->p_sys;
219 if (sys->error_status != 0)
220 return -1;
222 if (sys->eof)
223 return 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");
230 return -1;
233 if (vlc_smb2_mainloop(access, false) < 0)
234 return -1;
236 return sys->res.read.len;
239 static int
240 FileSeek(stream_t *access, uint64_t i_pos)
242 struct access_sys *sys = access->p_sys;
244 if (sys->error_status != 0)
245 return VLC_EGENERIC;
247 if (smb2_lseek(sys->smb2, sys->smb2fh, i_pos, SEEK_SET, NULL) < 0)
249 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_seek_async");
250 return VLC_EGENERIC;
252 sys->eof = false;
254 return VLC_SUCCESS;
257 static int
258 FileControl(stream_t *access, int i_query, va_list args)
260 struct access_sys *sys = access->p_sys;
262 switch (i_query)
264 case STREAM_CAN_SEEK:
265 *va_arg(args, bool *) = true;
266 break;
268 case STREAM_CAN_FASTSEEK:
269 *va_arg(args, bool *) = false;
270 break;
272 case STREAM_CAN_PAUSE:
273 case STREAM_CAN_CONTROL_PACE:
274 *va_arg(args, bool *) = true;
275 break;
277 case STREAM_GET_SIZE:
279 *va_arg(args, uint64_t *) = sys->smb2_size;
280 break;
283 case STREAM_GET_PTS_DELAY:
284 *va_arg(args, vlc_tick_t * ) = VLC_TICK_FROM_MS(
285 var_InheritInteger(access, "network-caching"));
286 break;
288 case STREAM_SET_PAUSE_STATE:
289 break;
291 default:
292 return VLC_EGENERIC;
294 return VLC_SUCCESS;
297 static char *
298 vlc_smb2_get_url(vlc_url_t *url, const char *file)
300 /* smb2://<psz_host><psz_path><file>?<psz_option> */
301 char *buf;
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] != '/' ? "/" : "",
306 file,
307 url->psz_option != NULL ? "?" : "",
308 url->psz_option != NULL ? url->psz_option : "") == -1)
309 return NULL;
310 else
311 return buf;
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)
320 return VLC_ENOMEM;
322 char *url = vlc_smb2_get_url(&sys->encoded_url, name_encoded);
323 free(name_encoded);
324 if (url == NULL)
325 return VLC_ENOMEM;
327 int ret = vlc_readdir_helper_additem(rdh, url, NULL, name, i_type,
328 ITEM_NET);
329 free(url);
330 return ret;
333 static int
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)
347 int i_type;
348 switch (smb2dirent->st.smb2_type)
350 case SMB2_TYPE_FILE:
351 i_type = ITEM_TYPE_FILE;
352 break;
353 case SMB2_TYPE_DIRECTORY:
354 i_type = ITEM_TYPE_DIRECTORY;
355 break;
356 default:
357 i_type = ITEM_TYPE_UNKNOWN;
358 break;
360 ret = AddItem(access, &rdh, smb2dirent->name, i_type);
363 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
365 return ret;
368 static int
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)
384 continue;
385 switch (info->type & 0x3)
387 case SHARE_TYPE_DISKTREE:
388 ret = AddItem(access, &rdh, info->name, ITEM_TYPE_DIRECTORY);
389 break;
393 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
394 return 0;
397 static int
398 vlc_smb2_close_fh(stream_t *access)
400 struct access_sys *sys = access->p_sys;
402 assert(sys->smb2fh);
404 if (smb2_close_async(sys->smb2, sys->smb2fh, smb2_generic_cb, access) < 0)
406 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_close_async");
407 return -1;
410 sys->smb2fh = NULL;
412 return vlc_smb2_mainloop(access, true);
415 static int
416 vlc_smb2_disconnect_share(stream_t *access)
418 struct access_sys *sys = access->p_sys;
420 if (!sys->smb2_connected)
421 return 0;
423 if (smb2_disconnect_share_async(sys->smb2, smb2_generic_cb, access) < 0)
425 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_connect_share_async");
426 return -1;
429 int ret = vlc_smb2_mainloop(access, true);
430 sys->smb2_connected = false;
431 return ret;
434 static void
435 smb2_opendir_cb(struct smb2_context *smb2, int status, void *data,
436 void *private_data)
438 VLC_SMB2_GENERIC_CB();
440 sys->smb2dir = data;
443 static void
444 smb2_open_cb(struct smb2_context *smb2, int status, void *data,
445 void *private_data)
447 VLC_SMB2_GENERIC_CB();
449 sys->smb2fh = data;
452 static void
453 smb2_share_enum_cb(struct smb2_context *smb2, int status, void *data,
454 void *private_data)
456 VLC_SMB2_GENERIC_CB();
458 sys->share_enum = data;
461 static int
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;
473 if (!username)
475 username = "Guest";
476 password = "";
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");
486 goto error;
488 if (vlc_smb2_mainloop(access, false) != 0)
489 goto error;
490 sys->smb2_connected = true;
492 int ret;
493 if (do_enum)
494 ret = smb2_share_enum_async(sys->smb2, smb2_share_enum_cb, access);
495 else
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)
503 goto error;
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);
514 else
516 msg_Err(access, "smb2_stat_cb: file type not handled");
517 sys->error_status = 1;
518 goto error;
522 if (ret < 0)
524 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_open*_async");
525 goto error;
528 if (vlc_smb2_mainloop(access, false) != 0)
529 goto error;
530 return 0;
532 error:
533 vlc_smb2_disconnect_share(access);
534 return -1;
537 static char *
538 vlc_smb2_resolve(stream_t *access, const char *host, unsigned port)
540 (void) access;
541 if (!host)
542 return NULL;
544 #ifdef HAVE_DSM
545 /* Test if the host is an IP */
546 struct in_addr addr;
547 if (inet_pton(AF_INET, host, &addr) == 1)
548 return NULL;
550 /* Test if the host can be resolved */
551 struct addrinfo *info = NULL;
552 if (vlc_getaddrinfo_i11e(host, port, NULL, &info) == 0)
554 freeaddrinfo(info);
555 /* Let smb2 resolve it */
556 return NULL;
559 /* Test if the host is a netbios name */
560 char *out_host = NULL;
561 netbios_ns *ns = netbios_ns_new();
562 uint32_t ip4_addr;
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);
570 return out_host;
571 #else
572 (void) port;
573 return NULL;
574 #endif
577 static int
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))
586 return VLC_ENOMEM;
587 access->p_sys = sys;
589 /* Parse the encoded URL */
590 if (vlc_UrlParseFixup(&sys->encoded_url, access->psz_url) != 0)
591 return VLC_ENOMEM;
593 if (sys->encoded_url.i_port != 0 && sys->encoded_url.i_port != CIFS_PORT)
594 goto error;
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");
601 goto error;
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,
608 CIFS_PORT);
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). */
612 char *url;
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);
618 free(resolved_host);
620 else
621 url = vlc_uri_compose(&sys->encoded_url);
622 if (!vlc_uri_decode(url))
624 free(url);
625 goto error;
627 smb2_url = smb2_parse_url(sys->smb2, url);
628 free(url);
630 if (!smb2_url || !smb2_url->share || !smb2_url->server)
632 msg_Err(access, "smb2_parse_url failed");
633 goto error;
636 int ret = -1;
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,
645 NULL);
646 ret = vlc_smb2_open_share(access, smb2_url, &credential);
648 while (ret == -1
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,
652 smb2_url->server))
654 sys->error_status = 0;
655 ret = vlc_smb2_open_share(access, smb2_url, &credential);
656 if (ret == 0)
657 vlc_credential_store(&credential, access);
659 vlc_credential_clean(&credential);
661 if (ret != 0)
662 goto error;
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;
682 else
683 vlc_assert_unreachable();
685 smb2_destroy_url(smb2_url);
686 free(var_domain);
687 return VLC_SUCCESS;
689 error:
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);
698 free(var_domain);
699 return VLC_EGENERIC;
702 static void
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);
714 else
715 vlc_assert_unreachable();
717 vlc_smb2_disconnect_share(access);
718 smb2_destroy_context(sys->smb2);
720 vlc_UrlClean(&sys->encoded_url);