Cygwin: // and //server: switch to Network Discovery

SMBv1 is ultimately deprecated since Novemer 2023.  It's also
not installed by default on latest Windows versions.

Drop using the WNet (SMBv1) API in favor of using Network Discovery.
Given there's no documented Win32 API for that, the code now uses
the Shell API.

Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
Corinna Vinschen 2024-03-20 14:32:49 +01:00
parent 80f722e97c
commit 205190a80b
4 changed files with 275 additions and 240 deletions

View file

@ -502,13 +502,6 @@ LoadDLLfunc (ldap_value_free_len, wldap32)
LoadDLLfunc (LdapGetLastError, wldap32) LoadDLLfunc (LdapGetLastError, wldap32)
LoadDLLfunc (LdapMapErrorToWin32, wldap32) LoadDLLfunc (LdapMapErrorToWin32, wldap32)
LoadDLLfunc (WNetCloseEnum, mpr)
LoadDLLfunc (WNetEnumResourceW, mpr)
LoadDLLfunc (WNetGetLastErrorW, mpr)
LoadDLLfunc (WNetGetProviderNameW, mpr)
LoadDLLfunc (WNetGetResourceInformationW, mpr)
LoadDLLfunc (WNetOpenEnumW, mpr)
LoadDLLfunc (DsEnumerateDomainTrustsW, netapi32) LoadDLLfunc (DsEnumerateDomainTrustsW, netapi32)
LoadDLLfunc (DsGetDcNameW, netapi32) LoadDLLfunc (DsGetDcNameW, netapi32)
LoadDLLfunc (NetApiBufferFree, netapi32) LoadDLLfunc (NetApiBufferFree, netapi32)
@ -521,6 +514,8 @@ LoadDLLfunc (NetUserGetGroups, netapi32)
LoadDLLfunc (NetUserGetInfo, netapi32) LoadDLLfunc (NetUserGetInfo, netapi32)
LoadDLLfunc (NetUserGetLocalGroups, netapi32) LoadDLLfunc (NetUserGetLocalGroups, netapi32)
LoadDLLfunc (CoInitialize, ole32)
LoadDLLfunc (CoUninitialize, ole32)
LoadDLLfunc (CoTaskMemFree, ole32) LoadDLLfunc (CoTaskMemFree, ole32)
LoadDLLfunc (LsaConnectUntrusted, secur32) LoadDLLfunc (LsaConnectUntrusted, secur32)
@ -531,7 +526,9 @@ LoadDLLfunc (LsaLookupAuthenticationPackage, secur32)
LoadDLLfunc (LsaRegisterLogonProcess, secur32) LoadDLLfunc (LsaRegisterLogonProcess, secur32)
LoadDLLfunc (TranslateNameW, secur32) LoadDLLfunc (TranslateNameW, secur32)
LoadDLLfunc (SHCreateItemFromParsingName, shell32)
LoadDLLfunc (SHGetDesktopFolder, shell32) LoadDLLfunc (SHGetDesktopFolder, shell32)
LoadDLLfunc (SHGetKnownFolderItem, shell32)
LoadDLLfunc (CreateFontW, gdi32) LoadDLLfunc (CreateFontW, gdi32)
LoadDLLfunc (DeleteObject, gdi32) LoadDLLfunc (DeleteObject, gdi32)

View file

@ -7,7 +7,6 @@ Cygwin license. Please consult the file "CYGWIN_LICENSE" for
details. */ details. */
#include "winsup.h" #include "winsup.h"
#include <stdlib.h>
#include "cygerrno.h" #include "cygerrno.h"
#include "security.h" #include "security.h"
#include "path.h" #include "path.h"
@ -15,196 +14,259 @@ details. */
#include "dtable.h" #include "dtable.h"
#include "cygheap.h" #include "cygheap.h"
#include "cygthread.h" #include "cygthread.h"
#include "tls_pbuf.h"
#include <shobjidl.h>
#include <shlobj.h>
#include <lm.h>
#include <stdlib.h>
#include <dirent.h> #include <dirent.h>
#include <wctype.h>
enum /* SMBv1 is deprectated and not even installed by default anymore on
Windows 10 and 11 machines or their servers.
So this fhandler class now uses Network Discovery, which, unfortunately,
requires to use the shell API. */
/* Define the required GUIDs here to avoid linking with libuuid.a */
const GUID FOLDERID_NetworkFolder = {
0xd20beec4, 0x5ca8, 0x4905,
{ 0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53 }
};
const GUID BHID_StorageEnum = {
0x4621a4e3, 0xf0d6, 0x4773,
{ 0x8a, 0x9c, 0x46, 0xe7, 0x7b, 0x17, 0x48, 0x40 }
};
const GUID BHID_EnumItems = {
0x94f60519, 0x2850, 0x4924,
{ 0xaa, 0x5a, 0xd1, 0x5e, 0x84, 0x86, 0x80, 0x39 }
};
class dir_cache
{
size_t max_entries;
size_t num_entries;
wchar_t **entry;
public:
dir_cache () : max_entries (0), num_entries (0), entry (NULL) {}
~dir_cache ()
{ {
GET_RESOURCE_OPENENUM = 1, while (num_entries > 0)
GET_RESOURCE_OPENENUMTOP = 2, free (entry[--num_entries]);
GET_RESOURCE_ENUM = 3 free (entry);
}; }
void add (wchar_t *str)
{
if (num_entries >= max_entries)
{
wchar_t **newentry;
newentry = (wchar_t **) realloc (entry, (max_entries + 10)
* sizeof (wchar_t *));
if (!newentry)
return;
entry = newentry;
max_entries += 10;
}
entry[num_entries] = wcsdup (str);
if (entry[num_entries])
{
wchar_t *p = entry[num_entries];
while ((*p = towlower (*p)))
++p;
++num_entries;
}
}
inline wchar_t *operator [](size_t idx) const
{
if (idx < num_entries)
return entry[idx];
return NULL;
}
};
#define DIR_cache (*reinterpret_cast<dir_cache *> (dir->__handle))
struct netdriveinf struct netdriveinf
{
int what;
int ret;
PVOID in;
PVOID out;
DWORD outsize;
HANDLE sem;
};
struct net_hdls
{
HANDLE net;
HANDLE dom;
};
static void
wnet_dbg_out (const char *func, DWORD ndi_ret)
{ {
DWORD gle_ret; DIR *dir;
DWORD error; int err;
WCHAR errorbuf[MAX_PATH]; bool test_only;
WCHAR namebuf[MAX_PATH]; HANDLE sem;
};
if (ndi_ret != ERROR_EXTENDED_ERROR) static inline int
{ hresult_to_errno (HRESULT wres, bool test_only = false)
debug_printf ("%s failed: %u", func, ndi_ret); {
return; if (SUCCEEDED (wres))
} return 0;
gle_ret = WNetGetLastErrorW (&error, errorbuf, MAX_PATH, namebuf, MAX_PATH); /* IEnumShellItems::Reset returns E_NOTIMPL when called for share
if (gle_ret == NO_ERROR) enumeration. However, if the machine doesn't exist, the Win32
debug_printf ("%s failed: %u --> %u from '%W': '%W'", error ERROR_BAD_NETPATH (converted into a HRESULT) is returned. In
func, ndi_ret, error, namebuf, errorbuf); test_only mode, we exploit this. Also, E_ACCESSDENIED is a funny
else one. It means, the machine exists, you just have no right to
debug_printf ("WNetGetLastError failed: %u", gle_ret); access the share list, or SMB doesn't run. */
if (test_only && (wres == E_NOTIMPL || wres == E_ACCESSDENIED))
return 0;
if (((ULONG) wres & 0xffff0000)
== (ULONG) MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0))
return geterrno_from_win_error ((ULONG) wres & 0xffff);
return EACCES;
} }
static DWORD static DWORD
thread_netdrive (void *arg) thread_netdrive (void *arg)
{ {
netdriveinf *ndi = (netdriveinf *) arg; netdriveinf *ndi = (netdriveinf *) arg;
WCHAR provider[256], *dummy = NULL; DIR *dir = ndi->dir;
LPNETRESOURCEW nro; IEnumShellItems *netitem_enum;
DWORD cnt, size; IShellItem *netparent;
struct net_hdls *nh; HRESULT wres;
tmp_pathbuf tp;
ReleaseSemaphore (ndi->sem, 1, NULL); ReleaseSemaphore (ndi->sem, 1, NULL);
switch (ndi->what)
size_t len = strlen (dir->__d_dirname);
wres = CoInitialize (NULL);
if (FAILED (wres))
{ {
case GET_RESOURCE_OPENENUMTOP: ndi->err = hresult_to_errno (wres);
nro = (LPNETRESOURCEW) tp.c_get (); goto out;
nh = (struct net_hdls *) ndi->out;
ndi->ret = WNetGetProviderNameW (WNNC_NET_LANMAN, provider,
(size = 256, &size));
if (ndi->ret != NO_ERROR)
{
wnet_dbg_out ("WNetGetProviderNameW", ndi->ret);
break;
}
memset (nro, 0, sizeof *nro);
nro->dwScope = RESOURCE_GLOBALNET;
nro->dwType = RESOURCETYPE_ANY;
nro->dwDisplayType = RESOURCEDISPLAYTYPE_GROUP;
nro->dwUsage = RESOURCEUSAGE_RESERVED | RESOURCEUSAGE_CONTAINER;
nro->lpRemoteName = provider;
nro->lpProvider = provider;
ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
RESOURCEUSAGE_ALL, nro, &nh->net);
if (ndi->ret != NO_ERROR)
{
wnet_dbg_out ("WNetOpenEnumW", ndi->ret);
break;
}
while ((ndi->ret = WNetEnumResourceW (nh->net, (cnt = 1, &cnt), nro,
(size = NT_MAX_PATH, &size)))
== NO_ERROR)
{
ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
RESOURCEUSAGE_ALL, nro, &nh->dom);
if (ndi->ret == NO_ERROR)
break;
}
break;
case GET_RESOURCE_OPENENUM:
nro = (LPNETRESOURCEW) tp.c_get ();
nh = (struct net_hdls *) ndi->out;
ndi->ret = WNetGetProviderNameW (WNNC_NET_LANMAN, provider,
(size = 256, &size));
if (ndi->ret != NO_ERROR)
{
wnet_dbg_out ("WNetGetProviderNameW", ndi->ret);
break;
}
((LPNETRESOURCEW) ndi->in)->lpProvider = provider;
ndi->ret = WNetGetResourceInformationW ((LPNETRESOURCEW) ndi->in, nro,
(size = NT_MAX_PATH, &size),
&dummy);
if (ndi->ret != NO_ERROR)
{
wnet_dbg_out ("WNetGetResourceInformationW", ndi->ret);
break;
}
ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
RESOURCEUSAGE_ALL, nro, &nh->dom);
break;
case GET_RESOURCE_ENUM:
nh = (struct net_hdls *) ndi->in;
if (!nh->dom)
{
ndi->ret = ERROR_NO_MORE_ITEMS;
break;
}
nro = (LPNETRESOURCEW) tp.c_get ();
while ((ndi->ret = WNetEnumResourceW (nh->dom, (cnt = 1, &cnt),
(LPNETRESOURCEW) ndi->out,
&ndi->outsize)) != NO_ERROR
&& nh->net)
{
WNetCloseEnum (nh->dom);
nh->dom = NULL;
while ((ndi->ret = WNetEnumResourceW (nh->net, (cnt = 1, &cnt), nro,
(size = NT_MAX_PATH, &size)))
== NO_ERROR)
{
ndi->ret = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
RESOURCEUSAGE_ALL, nro, &nh->dom);
if (ndi->ret == NO_ERROR)
break;
}
if (ndi->ret != NO_ERROR)
break;
}
break;
} }
if (len == 2) /* // */
{
wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT,
NULL, IID_PPV_ARGS (&netparent));
if (FAILED (wres))
{
ndi->err = hresult_to_errno (wres);
goto out;
}
}
else
{
wchar_t name[CYG_MAX_PATH];
sys_mbstowcs (name, CYG_MAX_PATH, dir->__d_dirname);
name[0] = L'\\';
name[1] = L'\\';
wres = SHCreateItemFromParsingName (name, NULL,
IID_PPV_ARGS (&netparent));
if (FAILED (wres))
{
ndi->err = hresult_to_errno (wres);
goto out;
}
}
wres = netparent->BindToHandler (NULL, len == 2 ? BHID_StorageEnum
: BHID_EnumItems,
IID_PPV_ARGS (&netitem_enum));
if (FAILED (wres))
{
ndi->err = hresult_to_errno (wres);
netparent->Release ();
goto out;
}
if (len == 2 || ndi->test_only)
{
wres = netitem_enum->Reset ();
if (FAILED (wres) || ndi->test_only)
{
ndi->err = hresult_to_errno (wres, ndi->test_only);
netitem_enum->Release ();
netparent->Release ();
goto out;
}
/* Don't look at me!
Network discovery is very unreliable and the list of machines
returned is just fly-by-night, if the enumerator doesn't have
enough time. The fact that you see *most* (but not necessarily
*all*) machines on the network in Windows Explorer is a result of
the enumeration running in a loop. You can observe this when
rebooting a remote machine and it disappears and reappears in the
Explorer Network list.
However, this is no option for the command line. We need to be able
to enumerate in a single go, since we can't just linger during
readdir() and reset the enumeration multiple times until we have a
supposedly full list.
This makes the following Sleep necessary. Sleeping ~3secs after
Reset fills the enumeration with high probability with almost all
available machines. */
Sleep (3000L);
}
dir->__handle = (char *) new dir_cache ();
do
{
IShellItem *netitem[10] = { 0 };
LPWSTR item_name = NULL;
ULONG count;
wres = netitem_enum->Next (10, netitem, &count);
if (SUCCEEDED (wres) && count > 0)
{
for (ULONG idx = 0; idx < count; ++idx)
{
if (netitem[idx]->GetDisplayName (SIGDN_PARENTRELATIVEPARSING,
&item_name) == S_OK)
{
DIR_cache.add (item_name);
CoTaskMemFree (item_name);
}
netitem[idx]->Release ();
}
}
}
while (wres == S_OK);
netitem_enum->Release ();
netparent->Release ();
ndi->err = 0;
out:
CoUninitialize ();
ReleaseSemaphore (ndi->sem, 1, NULL); ReleaseSemaphore (ndi->sem, 1, NULL);
return 0; return 0;
} }
static DWORD static DWORD
create_thread_and_wait (int what, PVOID in, PVOID out, DWORD outsize, create_thread_and_wait (DIR *dir, bool test_only)
const char *name)
{ {
netdriveinf ndi = { what, 0, in, out, outsize, netdriveinf ndi = { dir, 0, test_only,
CreateSemaphore (&sec_none_nih, 0, 2, NULL) }; CreateSemaphore (&sec_none_nih, 0, 2, NULL) };
cygthread *thr = new cygthread (thread_netdrive, &ndi, name);
cygthread *thr = new cygthread (thread_netdrive, &ndi, "netdrive");
if (thr->detach (ndi.sem)) if (thr->detach (ndi.sem))
ndi.ret = ERROR_OPERATION_ABORTED; ndi.err = EINTR;
CloseHandle (ndi.sem); CloseHandle (ndi.sem);
return ndi.ret; return ndi.err;
} }
virtual_ftype_t virtual_ftype_t
fhandler_netdrive::exists () fhandler_netdrive::exists ()
{ {
char *to; if (strlen (get_name ()) == 2)
const char *from;
size_t len = strlen (get_name ());
if (len == 2)
return virt_rootdir; return virt_rootdir;
char namebuf[len + 1]; DIR dir = { 0 };
tmp_pathbuf tp; dir.__d_dirname = (char *) get_name ();
PWCHAR name = tp.w_get (); int ret = create_thread_and_wait (&dir, true);
for (to = namebuf, from = get_name (); *from; to++, from++) return ret ? virt_none : virt_directory;
*to = (*from == '/') ? '\\' : *from;
*to = '\0';
struct net_hdls nh = { NULL, NULL };
NETRESOURCEW nr = {0};
nr.dwType = RESOURCETYPE_DISK;
sys_mbstowcs (name, NT_MAX_PATH, namebuf);
nr.lpRemoteName = name;
DWORD ret = create_thread_and_wait (GET_RESOURCE_OPENENUM,
&nr, &nh, 0, "WNetOpenEnum");
if (nh.dom)
WNetCloseEnum (nh.dom);
return ret != NO_ERROR ? virt_none : virt_directory;
} }
fhandler_netdrive::fhandler_netdrive (): fhandler_netdrive::fhandler_netdrive ():
@ -226,87 +288,67 @@ fhandler_netdrive::fstat (struct stat *buf)
return 0; return 0;
} }
DIR *
fhandler_netdrive::opendir (int fd)
{
DIR *dir;
int ret;
dir = fhandler_virtual::opendir (fd);
if (dir && (ret = create_thread_and_wait (dir, false)))
{
free (dir->__d_dirname);
free (dir->__d_dirent);
free (dir);
dir = NULL;
set_errno (ret);
syscall_printf ("%p = opendir (%s)", dir, get_name ());
}
return dir;
}
int int
fhandler_netdrive::readdir (DIR *dir, dirent *de) fhandler_netdrive::readdir (DIR *dir, dirent *de)
{ {
NETRESOURCEW *nro; int ret;
DWORD ret;
int res;
tmp_pathbuf tp;
if (!dir->__d_position) if (!DIR_cache[dir->__d_position])
{ {
size_t len = strlen (get_name ()); ret = ENMFILE;
PWCHAR name = NULL; goto out;
NETRESOURCEW nr = { 0 }; }
struct net_hdls *nh;
if (strlen (dir->__d_dirname) == 2)
if (len != 2) /* // */ {
{ sys_wcstombs (de->d_name, sizeof de->d_name,
const char *from; DIR_cache[dir->__d_position] + 2);
char *to; de->d_ino = hash_path_name (get_ino (), de->d_name);
char *namebuf = (char *) alloca (len + 1);
for (to = namebuf, from = get_name (); *from; to++, from++)
*to = (*from == '/') ? '\\' : *from;
*to = '\0';
name = tp.w_get ();
sys_mbstowcs (name, NT_MAX_PATH, namebuf);
}
nr.lpRemoteName = name;
nr.dwType = RESOURCETYPE_DISK;
nh = (struct net_hdls *) ccalloc (HEAP_FHANDLER, 1, sizeof *nh);
ret = create_thread_and_wait (len == 2 ? GET_RESOURCE_OPENENUMTOP
: GET_RESOURCE_OPENENUM,
&nr, nh, 0, "WNetOpenEnum");
if (ret != NO_ERROR)
{
dir->__handle = INVALID_HANDLE_VALUE;
res = geterrno_from_win_error (ret);
goto out;
}
dir->__handle = (HANDLE) nh;
} }
nro = (LPNETRESOURCEW) tp.c_get ();
ret = create_thread_and_wait (GET_RESOURCE_ENUM, dir->__handle, nro,
NT_MAX_PATH, "WnetEnumResource");
if (ret != NO_ERROR)
res = geterrno_from_win_error (ret);
else else
{ {
dir->__d_position++; char full[2 * CYG_MAX_PATH];
PWCHAR bs = wcsrchr (nro->lpRemoteName, L'\\'); char *s;
bs = bs ? bs + 1 : nro->lpRemoteName;
if (strlen (get_name ()) == 2)
{
UNICODE_STRING ss, ds;
tp.u_get (&ds); sys_wcstombs (de->d_name, sizeof de->d_name,
RtlInitUnicodeString (&ss, bs); DIR_cache[dir->__d_position]);
RtlDowncaseUnicodeString (&ds, &ss, FALSE); s = stpcpy (full, dir->__d_dirname);
sys_wcstombs (de->d_name, sizeof de->d_name, *s++ = '/';
ds.Buffer, ds.Length / sizeof (WCHAR)); stpcpy (s, de->d_name);
de->d_ino = hash_path_name (get_ino (), de->d_name); de->d_ino = readdir_get_ino (full, false);
}
else
{
sys_wcstombs (de->d_name, sizeof de->d_name, bs);
char *rpath = tp.c_get ();
sys_wcstombs (rpath, NT_MAX_PATH, nro->lpRemoteName);
de->d_ino = readdir_get_ino (rpath, false);
}
de->d_type = DT_DIR;
res = 0;
} }
dir->__d_position++;
de->d_type = DT_DIR;
ret = 0;
out: out:
syscall_printf ("%d = readdir(%p, %p)", res, dir, de); syscall_printf ("%d = readdir(%p, %p)", ret, dir, de);
return res; return ret;
} }
void void
fhandler_netdrive::seekdir (DIR *dir, long pos) fhandler_netdrive::seekdir (DIR *dir, long pos)
{ {
rewinddir (dir); ::rewinddir (dir);
if (pos < 0) if (pos < 0)
return; return;
while (dir->__d_position < pos) while (dir->__d_position < pos)
@ -317,23 +359,14 @@ fhandler_netdrive::seekdir (DIR *dir, long pos)
void void
fhandler_netdrive::rewinddir (DIR *dir) fhandler_netdrive::rewinddir (DIR *dir)
{ {
if (dir->__handle != INVALID_HANDLE_VALUE) dir->__d_position = 0;
{
struct net_hdls *nh = (struct net_hdls *) dir->__handle;
if (nh->dom)
WNetCloseEnum (nh->dom);
if (nh->net)
WNetCloseEnum (nh->net);
cfree (nh);
}
dir->__handle = INVALID_HANDLE_VALUE;
return fhandler_virtual::rewinddir (dir);
} }
int int
fhandler_netdrive::closedir (DIR *dir) fhandler_netdrive::closedir (DIR *dir)
{ {
rewinddir (dir); if (dir->__handle != INVALID_HANDLE_VALUE)
delete &DIR_cache;
return fhandler_virtual::closedir (dir); return fhandler_virtual::closedir (dir);
} }

View file

@ -3079,6 +3079,7 @@ class fhandler_netdrive: public fhandler_virtual
public: public:
fhandler_netdrive (); fhandler_netdrive ();
virtual_ftype_t exists(); virtual_ftype_t exists();
DIR *opendir (int);
int readdir (DIR *, dirent *); int readdir (DIR *, dirent *);
void seekdir (DIR *, long); void seekdir (DIR *, long);
void rewinddir (DIR *); void rewinddir (DIR *);

View file

@ -21,3 +21,7 @@ What changed:
executable. executable.
- Drop support for NT4 and Samba < 3.0. - Drop support for NT4 and Samba < 3.0.
- Now that SMBv1 is ultimately deprecated and not installed by default
on latest Windows versions, enumerating network servers in // and shares
via //machine is now using Network Discovery just like Windows Explorer.