smb2: add libdsm netbios resolver
[vlc.git] / modules / access / smb2.c
blob9bd5a1fea8f08738b0694db138a313d73f8aadd9
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(N_("smb2"))
68 set_description(N_("SMB2 input"))
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)
73 add_string("smb-domain", NULL, SMB_DOMAIN_TEXT, SMB_DOMAIN_LONGTEXT, false)
74 set_capability("access", 21)
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 if (status != -EINTR)
109 const char *psz_error = vlc_strerror_c(-status);
110 msg_Err(access, "%s failed: %d, '%s'", psz_func, status, psz_error);
111 if (sys->error_status == 0)
112 vlc_dialog_display_error(access,
113 _("SMB2 operation failed"), "%s",
114 psz_error);
116 else
117 msg_Warn(access, "%s interrupted", psz_func);
118 sys->error_status = status;
119 return -1;
121 else
123 sys->res_done = true;
124 return 0;
128 static void
129 smb2_set_generic_error(stream_t *access, const char *psz_func)
131 struct access_sys *sys = access->p_sys;
133 msg_Err(access, "%s failed: %s", psz_func, smb2_get_error(sys->smb2));
134 sys->error_status = 1;
137 #define VLC_SMB2_CHECK_STATUS(access, status) \
138 smb2_check_status(access, status, __func__)
140 #define VLC_SMB2_SET_GENERIC_ERROR(access, func) \
141 smb2_set_generic_error(access, func)
143 #define VLC_SMB2_STATUS_DENIED(x) (x == -ECONNREFUSED || x == -EACCES)
145 static int
146 vlc_smb2_mainloop(stream_t *access, bool teardown)
148 struct access_sys *sys = access->p_sys;
150 int timeout = -1;
151 int (*poll_func)(struct pollfd *, unsigned, int) = vlc_poll_i11e;
153 if (teardown && vlc_killed())
155 /* The thread is interrupted, so vlc_poll_i11e will return immediatly.
156 * Use poll() with a timeout instead for tear down. */
157 timeout = 500;
158 poll_func = (void *)poll;
161 sys->res_done = false;
162 while (sys->error_status == 0 && !sys->res_done)
164 struct pollfd p_fds[1];
165 int ret;
166 p_fds[0].fd = smb2_get_fd(sys->smb2);
167 p_fds[0].events = smb2_which_events(sys->smb2);
169 if (p_fds[0].fd == -1 || (ret = poll_func(p_fds, 1, timeout)) < 0)
171 if (errno == EINTR)
172 msg_Warn(access, "vlc_poll_i11e interrupted");
173 else
174 msg_Err(access, "vlc_poll_i11e failed");
175 sys->error_status = -errno;
177 else if (ret > 0 && p_fds[0].revents
178 && smb2_service(sys->smb2, p_fds[0].revents) < 0)
179 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_service");
181 return sys->error_status == 0 ? 0 : -1;
184 #define VLC_SMB2_GENERIC_CB() \
185 VLC_UNUSED(smb2); \
186 stream_t *access = private_data; \
187 struct access_sys *sys = access->p_sys; \
188 assert(sys->smb2 == smb2); \
189 if (VLC_SMB2_CHECK_STATUS(access, status)) \
190 return
192 static void
193 smb2_generic_cb(struct smb2_context *smb2, int status, void *data,
194 void *private_data)
196 VLC_UNUSED(data);
197 VLC_SMB2_GENERIC_CB();
200 static void
201 smb2_read_cb(struct smb2_context *smb2, int status, void *data,
202 void *private_data)
204 VLC_UNUSED(data);
205 VLC_SMB2_GENERIC_CB();
207 if (status == 0)
208 sys->eof = true;
209 else
210 sys->res.read.len = status;
213 static ssize_t
214 FileRead(stream_t *access, void *buf, size_t len)
216 struct access_sys *sys = access->p_sys;
218 if (sys->error_status != 0)
219 return -1;
221 if (sys->eof)
222 return 0;
224 sys->res.read.len = 0;
225 if (smb2_read_async(sys->smb2, sys->smb2fh, buf, len,
226 smb2_read_cb, access) < 0)
228 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_read_async");
229 return -1;
232 if (vlc_smb2_mainloop(access, false) < 0)
233 return -1;
235 return sys->res.read.len;
238 static int
239 FileSeek(stream_t *access, uint64_t i_pos)
241 struct access_sys *sys = access->p_sys;
243 if (sys->error_status != 0)
244 return VLC_EGENERIC;
246 if (smb2_lseek(sys->smb2, sys->smb2fh, i_pos, SEEK_SET, NULL) < 0)
248 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_seek_async");
249 return VLC_EGENERIC;
251 sys->eof = false;
253 return VLC_SUCCESS;
256 static int
257 FileControl(stream_t *access, int i_query, va_list args)
259 struct access_sys *sys = access->p_sys;
261 switch (i_query)
263 case STREAM_CAN_SEEK:
264 *va_arg(args, bool *) = true;
265 break;
267 case STREAM_CAN_FASTSEEK:
268 *va_arg(args, bool *) = false;
269 break;
271 case STREAM_CAN_PAUSE:
272 case STREAM_CAN_CONTROL_PACE:
273 *va_arg(args, bool *) = true;
274 break;
276 case STREAM_GET_SIZE:
278 *va_arg(args, uint64_t *) = sys->smb2_size;
279 break;
282 case STREAM_GET_PTS_DELAY:
283 *va_arg(args, vlc_tick_t * ) = VLC_TICK_FROM_MS(
284 var_InheritInteger(access, "network-caching"));
285 break;
287 case STREAM_SET_PAUSE_STATE:
288 break;
290 default:
291 return VLC_EGENERIC;
293 return VLC_SUCCESS;
296 static char *
297 vlc_smb2_get_url(vlc_url_t *url, const char *file)
299 /* smb2://<psz_host><psz_path><file>?<psz_option> */
300 char *buf;
301 if (asprintf(&buf, "smb://%s%s%s%s%s%s", url->psz_host,
302 url->psz_path != NULL ? url->psz_path : "",
303 url->psz_path != NULL && url->psz_path[0] != '\0' &&
304 url->psz_path[strlen(url->psz_path) - 1] != '/' ? "/" : "",
305 file,
306 url->psz_option != NULL ? "?" : "",
307 url->psz_option != NULL ? url->psz_option : "") == -1)
308 return NULL;
309 else
310 return buf;
313 static int AddItem(stream_t *access, struct vlc_readdir_helper *rdh,
314 const char *name, int i_type)
316 struct access_sys *sys = access->p_sys;
317 char *name_encoded = vlc_uri_encode(name);
318 if (name_encoded == NULL)
319 return VLC_ENOMEM;
321 char *url = vlc_smb2_get_url(&sys->encoded_url, name_encoded);
322 free(name_encoded);
323 if (url == NULL)
324 return VLC_ENOMEM;
326 int ret = vlc_readdir_helper_additem(rdh, url, NULL, name, i_type,
327 ITEM_NET);
328 free(url);
329 return ret;
332 static int
333 DirRead(stream_t *access, input_item_node_t *p_node)
335 struct access_sys *sys = access->p_sys;
336 struct smb2dirent *smb2dirent;
337 int ret = VLC_SUCCESS;
338 assert(sys->smb2dir);
340 struct vlc_readdir_helper rdh;
341 vlc_readdir_helper_init(&rdh, access, p_node);
343 while (ret == VLC_SUCCESS
344 && (smb2dirent = smb2_readdir(sys->smb2, sys->smb2dir)) != NULL)
346 int i_type;
347 switch (smb2dirent->st.smb2_type)
349 case SMB2_TYPE_FILE:
350 i_type = ITEM_TYPE_FILE;
351 break;
352 case SMB2_TYPE_DIRECTORY:
353 i_type = ITEM_TYPE_DIRECTORY;
354 break;
355 default:
356 i_type = ITEM_TYPE_UNKNOWN;
357 break;
359 ret = AddItem(access, &rdh, smb2dirent->name, i_type);
362 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
364 return ret;
367 static int
368 ShareEnum(stream_t *access, input_item_node_t *p_node)
370 struct access_sys *sys = access->p_sys;
371 assert(sys->share_enum != NULL);
373 int ret = VLC_SUCCESS;
374 struct vlc_readdir_helper rdh;
375 vlc_readdir_helper_init(&rdh, access, p_node);
377 struct srvsvc_netsharectr *ctr = sys->share_enum->ctr;
378 for (uint32_t iinfo = 0;
379 iinfo < ctr->ctr1.count && ret == VLC_SUCCESS; ++iinfo)
381 struct srvsvc_netshareinfo1 *info = &ctr->ctr1.array[iinfo];
382 if (info->type & SHARE_TYPE_HIDDEN)
383 continue;
384 switch (info->type & 0x3)
386 case SHARE_TYPE_DISKTREE:
387 ret = AddItem(access, &rdh, info->name, ITEM_TYPE_DIRECTORY);
388 break;
392 vlc_readdir_helper_finish(&rdh, ret == VLC_SUCCESS);
393 return 0;
396 static int
397 vlc_smb2_close_fh(stream_t *access)
399 struct access_sys *sys = access->p_sys;
401 assert(sys->smb2fh);
403 if (smb2_close_async(sys->smb2, sys->smb2fh, smb2_generic_cb, access) < 0)
405 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_close_async");
406 return -1;
409 sys->smb2fh = NULL;
411 return vlc_smb2_mainloop(access, true);
414 static int
415 vlc_smb2_disconnect_share(stream_t *access)
417 struct access_sys *sys = access->p_sys;
419 if (!sys->smb2_connected)
420 return 0;
422 if (smb2_disconnect_share_async(sys->smb2, smb2_generic_cb, access) < 0)
424 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_connect_share_async");
425 return -1;
428 int ret = vlc_smb2_mainloop(access, true);
429 sys->smb2_connected = false;
430 return ret;
433 static void
434 smb2_opendir_cb(struct smb2_context *smb2, int status, void *data,
435 void *private_data)
437 VLC_SMB2_GENERIC_CB();
439 sys->smb2dir = data;
442 static void
443 smb2_open_cb(struct smb2_context *smb2, int status, void *data,
444 void *private_data)
446 VLC_SMB2_GENERIC_CB();
448 sys->smb2fh = data;
451 static void
452 smb2_share_enum_cb(struct smb2_context *smb2, int status, void *data,
453 void *private_data)
455 VLC_SMB2_GENERIC_CB();
457 sys->share_enum = data;
460 static int
461 vlc_smb2_open_share(stream_t *access, const struct smb2_url *smb2_url,
462 const vlc_credential *credential)
464 struct access_sys *sys = access->p_sys;
466 const bool do_enum = smb2_url->share[0] == '\0';
467 const char *username = credential->psz_username;
468 const char *password = credential->psz_password;
469 const char *domain = credential->psz_realm;
470 const char *share = do_enum ? "IPC$" : smb2_url->share;
472 if (!username)
474 username = "Guest";
475 password = "";
478 smb2_set_password(sys->smb2, password);
479 smb2_set_domain(sys->smb2, domain ? domain : "");
481 if (smb2_connect_share_async(sys->smb2, smb2_url->server, share,
482 username, smb2_generic_cb, access) < 0)
484 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_connect_share_async");
485 goto error;
487 if (vlc_smb2_mainloop(access, false) != 0)
488 goto error;
489 sys->smb2_connected = true;
491 int ret;
492 if (do_enum)
493 ret = smb2_share_enum_async(sys->smb2, smb2_share_enum_cb, access);
494 else
496 struct smb2_stat_64 smb2_stat;
497 if (smb2_stat_async(sys->smb2, smb2_url->path, &smb2_stat,
498 smb2_generic_cb, access) < 0)
499 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_stat_async");
501 if (vlc_smb2_mainloop(access, false) != 0)
502 goto error;
504 if (smb2_stat.smb2_type == SMB2_TYPE_FILE)
506 sys->smb2_size = smb2_stat.smb2_size;
507 ret = smb2_open_async(sys->smb2, smb2_url->path, O_RDONLY,
508 smb2_open_cb, access);
510 else if (smb2_stat.smb2_type == SMB2_TYPE_DIRECTORY)
511 ret = smb2_opendir_async(sys->smb2, smb2_url->path,
512 smb2_opendir_cb, access);
513 else
515 msg_Err(access, "smb2_stat_cb: file type not handled");
516 sys->error_status = 1;
517 goto error;
521 if (ret < 0)
523 VLC_SMB2_SET_GENERIC_ERROR(access, "smb2_open*_async");
524 goto error;
527 if (vlc_smb2_mainloop(access, false) != 0)
528 goto error;
529 return 0;
531 error:
532 vlc_smb2_disconnect_share(access);
533 return -1;
536 static int
537 vlc_smb2_resolve(stream_t *access, char **host, unsigned port)
539 (void) access;
540 if (!*host)
541 return -1;
543 #ifdef HAVE_DSM
544 /* Test if the host is an IP */
545 struct in_addr addr;
546 if (inet_pton(AF_INET, *host, &addr) == 1)
547 return 0;
549 /* Test if the host can be resolved */
550 struct addrinfo *info = NULL;
551 if (vlc_getaddrinfo_i11e(*host, port, NULL, &info) == 0)
553 freeaddrinfo(info);
554 /* Let smb2 resolve it */
555 return 0;
558 /* Test if the host is a netbios name */
559 netbios_ns *ns = netbios_ns_new();
560 uint32_t ip4_addr;
561 int ret = -1;
562 if (netbios_ns_resolve(ns, *host, NETBIOS_FILESERVER, &ip4_addr) == 0)
564 char ip[] = "xxx.xxx.xxx.xxx";
565 if (inet_ntop(AF_INET, &ip4_addr, ip, sizeof(ip)))
567 free(*host);
568 *host = strdup(ip);
569 if (*host)
570 ret = 0;
573 netbios_ns_destroy(ns);
574 return ret;
575 #else
576 (void) port;
577 return 0;
578 #endif
581 static int
582 Open(vlc_object_t *p_obj)
584 stream_t *access = (stream_t *)p_obj;
585 struct access_sys *sys = vlc_obj_calloc(p_obj, 1, sizeof (*sys));
586 struct smb2_url *smb2_url = NULL;
587 char *var_domain = NULL;
589 if (unlikely(sys == NULL))
590 return VLC_ENOMEM;
591 access->p_sys = sys;
593 /* Parse the encoded URL */
594 if (vlc_UrlParseFixup(&sys->encoded_url, access->psz_url) != 0)
595 return VLC_ENOMEM;
597 if (sys->encoded_url.i_port != 0 && sys->encoded_url.i_port != CIFS_PORT)
598 goto error;
599 sys->encoded_url.i_port = 0;
601 if (vlc_smb2_resolve(access, &sys->encoded_url.psz_host, CIFS_PORT))
602 goto error;
604 if (sys->encoded_url.psz_path == NULL)
605 sys->encoded_url.psz_path = (char *) "/";
607 sys->smb2 = smb2_init_context();
608 if (sys->smb2 == NULL)
610 msg_Err(access, "smb2_init_context failed");
611 goto error;
614 /* smb2_* functions need a decoded url. Re compose the url from the
615 * modified sys->encoded_url (without port and with the resolved host). */
616 char *url = vlc_uri_compose(&sys->encoded_url);
617 if (!vlc_uri_decode(url))
619 free(url);
620 goto error;
622 smb2_url = smb2_parse_url(sys->smb2, url);
623 free(url);
625 if (!smb2_url || !smb2_url->share || !smb2_url->server)
627 msg_Err(access, "smb2_parse_url failed");
628 goto error;
631 int ret = -1;
632 vlc_credential credential;
633 vlc_credential_init(&credential, &sys->encoded_url);
634 var_domain = var_InheritString(access, "smb-domain");
635 credential.psz_realm = var_domain;
637 /* First, try Guest login or using "smb-" options (without
638 * keystore/user interaction) */
639 vlc_credential_get(&credential, access, "smb-user", "smb-pwd", NULL,
640 NULL);
641 ret = vlc_smb2_open_share(access, smb2_url, &credential);
643 while (ret == -1
644 && (!sys->error_status || VLC_SMB2_STATUS_DENIED(sys->error_status))
645 && vlc_credential_get(&credential, access, "smb-user", "smb-pwd",
646 SMB_LOGIN_DIALOG_TITLE, SMB_LOGIN_DIALOG_TEXT,
647 smb2_url->server))
649 sys->error_status = 0;
650 ret = vlc_smb2_open_share(access, smb2_url, &credential);
651 if (ret == 0)
652 vlc_credential_store(&credential, access);
654 vlc_credential_clean(&credential);
656 if (ret != 0)
657 goto error;
659 if (sys->smb2fh != NULL)
661 access->pf_read = FileRead;
662 access->pf_seek = FileSeek;
663 access->pf_control = FileControl;
665 else if (sys->smb2dir != NULL)
667 access->pf_readdir = DirRead;
668 access->pf_seek = NULL;
669 access->pf_control = access_vaDirectoryControlHelper;
671 else if (sys->share_enum != NULL)
673 access->pf_readdir = ShareEnum;
674 access->pf_seek = NULL;
675 access->pf_control = access_vaDirectoryControlHelper;
677 else
678 vlc_assert_unreachable();
680 smb2_destroy_url(smb2_url);
681 free(var_domain);
682 return VLC_SUCCESS;
684 error:
685 if (smb2_url != NULL)
686 smb2_destroy_url(smb2_url);
687 if (sys->smb2 != NULL)
689 vlc_smb2_disconnect_share(access);
690 smb2_destroy_context(sys->smb2);
692 vlc_UrlClean(&sys->encoded_url);
693 free(var_domain);
694 return VLC_EGENERIC;
697 static void
698 Close(vlc_object_t *p_obj)
700 stream_t *access = (stream_t *)p_obj;
701 struct access_sys *sys = access->p_sys;
703 if (sys->smb2fh != NULL)
704 vlc_smb2_close_fh(access);
705 else if (sys->smb2dir != NULL)
706 smb2_closedir(sys->smb2, sys->smb2dir);
707 else if (sys->share_enum != NULL)
708 smb2_free_data(sys->smb2, sys->share_enum);
709 else
710 vlc_assert_unreachable();
712 vlc_smb2_disconnect_share(access);
713 smb2_destroy_context(sys->smb2);
715 vlc_UrlClean(&sys->encoded_url);