vboxmrxnp.cpp revision 09c9a430ad2caac61753f90b04a4989c6d54d13a
/** @file
*
* VirtualBox Windows Guest Shared Folders
*
* Network provider dll
*/
/*
* Copyright (C) 2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <windows.h>
#include <windef.h>
#include <winbase.h>
#include <winsvc.h>
#include <winnetwk.h>
#include <npapi.h>
#include <devioctl.h>
#include <stdio.h>
#include "..\driver\vbsfshared.h"
#include <iprt/initterm.h>
#include <VBox/VBoxGuestLib.h>
#define MRX_VBOX_SERVER_NAME_U L"VBOXSVR"
#define MRX_VBOX_SERVER_NAME_ALT_U L"VBOXSRV"
{
/* The CharUpper parameter is a pointer to a null-terminated string,
* or specifies a single character. If the high-order word of this
* parameter is zero, the low-order word must contain a single character to be converted.
*/
}
{
if (!pOutputDataLen)
{
pOutputDataLen = &cbOut;
}
0,
if (INVALID_HANDLE_VALUE != DeviceHandle)
{
NULL);
if (!fSuccess)
{
dwStatus = GetLastError();
}
}
else
{
dwStatus = GetLastError();
static int sLogged = 0;
if (!sLogged)
{
LogRel(("VBOXNP: vbsfIOCTL: Error opening device, last error = %d\n",
dwStatus));
sLogged++;
}
}
return dwStatus;
}
{
switch (nIndex)
{
case WNNC_SPEC_VERSION:
{
} break;
case WNNC_NET_TYPE:
{
} break;
case WNNC_DRIVER_VERSION:
{
} break;
case WNNC_CONNECTION:
{
} break;
case WNNC_ENUMERATION:
{
rc = WNNC_ENUM_LOCAL |
} break;
case WNNC_START:
{
break;
}
case WNNC_DIALOG:
{
} break;
case WNNC_USER:
case WNNC_ADMIN:
default:
{
rc = 0;
} break;
}
return rc;
}
{
Log(("VBOXNP: NPLogonNotify\n"));
*lpLogonScript = NULL;
return WN_SUCCESS;
}
{
Log(("VBOXNP: NPPasswordChangeNotify\n"));
return WN_NOT_SUPPORTED;
}
{
Log(("VBOXNP: NPAddConnection\n"));
}
{
{
return WN_BAD_NETNAME;
}
/* Build connection name: \Device\VBoxMiniRdr\;%DriveLetter%:\vboxsvr\share */
{
LocalName[0] = L'\0';
fLocalName = FALSE;
}
else
{
if ( lpNetResource->lpLocalName[0]
{
}
else
{
}
}
if (dwStatus == WN_SUCCESS)
{
/* Append the remote name. */
if ( lpNetResource->lpRemoteName
{
/* No need for (lstrlen + 1), because 'lpNetResource->lpRemoteName' leading \ is not copied. */
if (lstrlen(ConnectionName) + lstrlen(lpNetResource->lpRemoteName) <= sizeof(ConnectionName) / sizeof(WCHAR))
{
}
else
{
}
}
else
{
}
}
Log(("VBOXNP: NPAddConnection3: ConnectionName: [%ls], len %d, dwStatus 0x%08X\n",
if (dwStatus == WN_SUCCESS)
{
if ( fLocalName
{
Log(("VBOXNP: NPAddConnection3: Connection [%ls] already connected.\n",
}
else
{
if ( !fLocalName
|| GetLastError() == ERROR_FILE_NOT_FOUND)
{
NULL,
NULL);
if (dwStatus == WN_SUCCESS)
{
if ( fLocalName
{
dwStatus = GetLastError();
}
}
else
{
}
}
else
{
}
}
}
Log(("VBOXNP: NPAddConnection3: Returned 0x%08X\n",
dwStatus));
return dwStatus;
}
{
Log(("VBOXNP: NPCancelConnection: Name = %ls\n",
lpName));
{
{
sizeof(LocalName),
&cbOut);
if ( dwStatus == WN_SUCCESS
&& cbOut > 0)
{
if (lstrlen(DD_MRX_VBOX_FS_DEVICE_NAME_U) + 2 + lstrlen(LocalName) + lstrlen(RemoteName) + 1 > sizeof(ConnectionName) / sizeof(WCHAR))
{
}
else
{
NULL,
NULL);
if (dwStatus == WN_SUCCESS)
{
{
dwStatus = GetLastError();
}
}
}
}
else
{
}
}
else
{
/* Both vboxsvr & vboxsrv are now accepted */
{
}
else
{
}
if (Verifier)
{
/* Full remote path */
if (lstrlen(DD_MRX_VBOX_FS_DEVICE_NAME_U) + 2 + lstrlen(lpName) + 1 > sizeof(ConnectionName) / sizeof(WCHAR))
{
}
else
{
NULL,
NULL);
}
}
else
{
}
}
}
Log(("VBOXNP: NPCancelConnection: Returned 0x%08X\n",
dwStatus));
return dwStatus;
}
{
Log(("VBOXNP: NPGetConnection: lpLocalName = %ls\n",
lpLocalName));
if (lpLocalName && lpLocalName[0] != 0)
{
{
sizeof(LocalName),
&cbOut);
{
/* The device specified by lpLocalName is not redirected by this provider. */
}
else
{
if (cbOut == 0)
{
}
}
}
}
if (dwStatus == WN_SUCCESS)
{
Log(("VBOXNP: NPGetConnection: RemoteName: %ls\n",
RemoteName));
if (*lpBufferSize >= len)
{
lpRemoteName[0] = L'\\';
Log(("VBOXNP: NPGetConnection: returning lpRemoteName: %ls\n",
lpRemoteName));
}
else
{
if (*lpBufferSize != 0)
{
/* Log only real errors. Do not log a 0 bytes try. */
Log(("VBOXNP: NPGetConnection: Buffer overflow: *lpBufferSize = %d, len = %d\n",
*lpBufferSize, len));
}
}
*lpBufferSize = len;
}
if ((dwStatus != WN_SUCCESS) &&
(dwStatus != WN_MORE_DATA))
{
Log(("VBOXNP: NPGetConnection: Returned error 0x%08X\n",
dwStatus));
}
return dwStatus;
}
{
while (*lpPrefix)
{
{
/* Not a prefix */
return NULL;
}
lpPrefix++;
lpRemoteName++;
}
return lpRemoteName;
}
{
int cLeadingBackslashes = 0;
while (*lpRemoteName == L'\\')
{
lpRemoteName++;
}
{
if (!lpAfterPrefix)
{
}
return lpAfterPrefix;
}
return NULL;
}
/* Enumerate shared folders as hierarchy:
* VBOXSVR(container)
* +--------------------+
* | \
* Folder1(connectable) FolderN(connectable)
*/
typedef struct _NPENUMCTX
{
bool fRoot;
} NPENUMCTX;
{
Log(("VBOXNP: NPOpenEnum: dwScope 0x%08X, dwType 0x%08X, dwUsage 0x%08X, lpNetResource %p\n",
if (dwUsage == 0)
{
/* The bitmask may be zero to match all of the flags. */
}
/* Allocate the context structure. */
{
}
else
{
{
Log(("VBOXNP: NPOpenEnum: lpRemoteName %ls\n",
}
switch (dwScope)
{
case 6: /* Advertised as WNNC_ENUM_SHAREABLE. This returns C$ system shares.
* NpEnumResource will return NO_MORE_ENTRIES.
*/
{
{
/* If it is NULL or if the lpRemoteName field of the NETRESOURCE is NULL,
* the provider should enumerate the top level of its network.
* But system shares can't be on top level.
*/
break;
}
if ( lpAfterName == NULL
{
break;
}
/* Valid server name. */
break;
}
case RESOURCE_GLOBALNET: /* All resources on the network. */
{
{
/* If it is NULL or if the lpRemoteName field of the NETRESOURCE is NULL,
* the provider should enumerate the top level of its network.
*/
}
else
{
/* Enumerate lpNetResource->lpRemoteName container, which can be only the VBOXSVR container. */
if ( lpAfterName == NULL
{
break;
}
/* Valid server name. */
}
break;
}
case RESOURCE_CONNECTED: /* All currently connected resources. */
case RESOURCE_CONTEXT: /* The interpretation of this is left to the provider. Treat this as RESOURCE_GLOBALNET. */
{
break;
}
default:
Log(("VBOXNP: NPOpenEnum: unsupported scope 0x%lx\n",
dwScope));
break;
}
}
if (dwStatus != WN_SUCCESS)
{
Log(("VBOXNP: NPOpenEnum: Returned error 0x%08X\n",
dwStatus));
if (pCtx)
{
}
}
else
{
Log(("VBOXNP: NPOpenEnum: pCtx %p\n",
pCtx));
}
return dwStatus;
}
{
int cbRemoteName;
Log(("VBOXNP: NPEnumResource: hEnum %p, lpcCount %p, lpBuffer %p, lpBufferSize %p.\n",
{
Log(("VBOXNP: NPEnumResource: WN_BAD_HANDLE\n"));
return WN_BAD_HANDLE;
}
{
Log(("VBOXNP: NPEnumResource: WN_BAD_VALUE\n"));
return WN_BAD_VALUE;
}
Log(("VBOXNP: NPEnumResource: *lpcCount 0x%x, *lpBufferSize 0x%x, pCtx->index %d\n",
ULONG cEntriesCopied = 0;
{
Log(("VBOXNP: NPEnumResource: RESOURCE_CONNECTED\n"));
cbOut = sizeof(ConnectionList);
NULL, 0,
&cbOut);
{
{
{
cbOut = sizeof(RemoteName);
sizeof(LocalName),
&cbOut);
{
break;
}
/* How many bytes is needed for the current NETRESOURCE data. */
cbEntry = sizeof(NETRESOURCE);
cbEntry += sizeof(MRX_VBOX_PROVIDER_NAME_U);
if (cbEntry > cbRemaining)
{
break;
}
cbRemaining -= cbEntry;
/* Reserve the space in the string area. */
*pDst++ = L':';
*pDst++ = L'\0';
*pDst++ = L'\\';
Log(("VBOXNP: NPEnumResource: lpRemoteName: %ls\n",
pNetResource++;
}
}
}
else
{
}
}
{
{
/* VBOXSVR container. */
{
}
else
{
/* Return VBOXSVR server.
* Determine the space needed for this entry.
*/
cbEntry = sizeof(NETRESOURCE);
cbEntry += sizeof(MRX_VBOX_PROVIDER_NAME_U);
if (cbEntry > cbRemaining)
{
/* Do nothing. */
}
else
{
cbRemaining -= cbEntry;
*pDst++ = L'\\';
*pDst++ = L'\\';
}
}
}
else
{
/* Shares of VBOXSVR. */
cbOut = sizeof(ConnectionList);
NULL,
0,
&cbOut);
{
{
{
cbOut = sizeof(RemoteName);
&cbOut);
{
break;
}
/* How many bytes is needed for the current NETRESOURCE data. */
cbEntry = sizeof(NETRESOURCE);
/* Remote name: \\ + vboxsvr + \ + name. */
cbEntry += sizeof(MRX_VBOX_PROVIDER_NAME_U);
if (cbEntry > cbRemaining)
{
break;
}
cbRemaining -= cbEntry;
*pDst++ = L'\\';
*pDst++ = L'\\';
*pDst++ = L'\\';
Log(("VBOXNP: NPEnumResource: lpRemoteName: %ls\n",
pNetResource++;
}
}
}
else
{
}
}
}
{
Log(("VBOXNP: NPEnumResource: dwScope 6\n"));
}
else
{
Log(("VBOXNP: NPEnumResource: invalid dwScope 0x%x\n",
return WN_BAD_HANDLE;
}
{
{
}
else
{
Log(("VBOXNP: NPEnumResource: More Data Needed - %d\n",
cbEntry));
*lpBufferSize = cbEntry;
}
}
Log(("VBOXNP: NPEnumResource: Entries returned %d, dwStatus 0x%08X\n",
return dwStatus;
}
{
Log(("VBOXNP: NPCloseEnum: hEnum %p\n",
hEnum));
if (pCtx)
{
}
Log(("VBOXNP: NPCloseEnum: returns\n"));
return WN_SUCCESS;
}
{
Log(("VBOXNP: NPGetResourceParent: lpNetResource %p, lpBuffer %p, lpBufferSize %p\n",
/* Construct a new NETRESOURCE which is syntactically a parent of lpNetResource,
* then call NPGetResourceInformation to actually fill the buffer.
*/
{
return WN_BAD_NETNAME;
}
if ( lpAfterName == NULL
{
Log(("VBOXNP: NPGetResourceParent: WN_BAD_NETNAME\n"));
return WN_BAD_NETNAME;
}
if (!pParent)
{
return WN_OUT_OF_MEMORY;
}
/* Remove last path component of the pParent->lpRemoteName. */
if (*pLastSlash == L'\\')
{
/* \\server\share\path\, skip last slash immediately. */
pLastSlash--;
}
{
if (*pLastSlash == L'\\')
{
break;
}
pLastSlash--;
}
{
/* It is a leading backslash. Construct "no parent" NETRESOURCE. */
cbEntry = sizeof(NETRESOURCE);
if (cbEntry > *lpBufferSize)
{
*lpBufferSize = cbEntry;
}
else
{
Log(("VBOXNP: NPGetResourceParent: no parent, strings %p/%p\n",
}
}
else
{
/* Make the parent remote name and get its information. */
*pLastSlash = 0;
}
if (pParent)
{
}
return dwStatus;
}
{
Log(("VBOXNP: NPGetResourceInformation: lpNetResource %p, lpBuffer %p, lpBufferSize %p, lplpSystem %p\n",
if ( lpNetResource == NULL
|| lpBufferSize == NULL)
{
Log(("VBOXNP: NPGetResourceInformation: WN_BAD_VALUE\n"));
return WN_BAD_VALUE;
}
Log(("VBOXNP: NPGetResourceInformation: lpRemoteName %ls, *lpBufferSize 0x%x\n",
if ( lpAfterName == NULL
{
Log(("VBOXNP: NPGetResourceInformation: WN_BAD_NETNAME\n"));
return WN_BAD_NETNAME;
}
{
/* The caller passed in a nonzero dwType that does not match
* the actual type of the network resource.
*/
return WN_BAD_DEV_TYPE;
}
/*
* If the input remote resource name was "\\server\share\dir1\dir2",
* then the output NETRESOURCE contains information about the resource "\\server\share".
* The lpRemoteName, lpProvider, dwType, dwDisplayType, and dwUsage fields are returned
* containing values, all other fields being set to NULL.
*/
/* Check what kind of the resource is that by parsing path components.
* lpAfterName points to first WCHAR after a valid server name.
*/
{
/* "\\VBOXSVR" or "\\VBOXSVR\" */
cbEntry = sizeof(NETRESOURCE);
if (cbEntry > *lpBufferSize)
{
*lpBufferSize = cbEntry;
return WN_MORE_DATA;
}
*pStrings++ = L'\\';
*pStrings++ = L'\\';
Log(("VBOXNP: NPGetResourceInformation: lpRemoteName: %ls, strings %p/%p\n",
if (lplpSystem)
{
*lplpSystem = NULL;
}
return WN_SUCCESS;
}
/* *lpAfterName == L'\\', could be share or share + path.
* Check if there are more path components after the share name.
*/
{
lp++;
}
if (*lp == 0)
{
/* It is a share only: \\vboxsvr\share */
cbEntry = sizeof(NETRESOURCE);
cbEntry += 2 * sizeof(WCHAR) + sizeof(MRX_VBOX_SERVER_NAME_U); /* \\ + server name with trailing nul */
if (cbEntry > *lpBufferSize)
{
*lpBufferSize = cbEntry;
return WN_MORE_DATA;
}
*pStrings++ = L'\\';
*pStrings++ = L'\\';
Log(("VBOXNP: NPGetResourceInformation: lpRemoteName: %ls, strings %p/%p\n",
if (lplpSystem)
{
*lplpSystem = NULL;
}
return WN_SUCCESS;
}
/* \\vboxsvr\share\path */
cbEntry = sizeof(NETRESOURCE);
cbEntry += 2 * sizeof(WCHAR) + sizeof(MRX_VBOX_SERVER_NAME_U); /* \\ + server name with trailing nul */
if (cbEntry > *lpBufferSize)
{
*lpBufferSize = cbEntry;
return WN_MORE_DATA;
}
/* The server + share. */
*pStrings++ = L'\\';
*pStrings++ = L'\\';
*pStrings++ = 0;
if (lplpSystem)
{
*lplpSystem = pStrings;
}
Log(("VBOXNP: NPGetResourceInformation: lpRemoteName: %ls, strings %p/%p\n",
return WN_SUCCESS;
}
{
DWORD BufferRequired = 0;
DWORD RemoteNameLength = 0;
DWORD RemainingPathLength = 0;
const WCHAR *lpRemainingPath;
Log(("VBOXNP: NPGetUniversalName: lpLocalPath = %ls, InfoLevel = %d, *lpBufferSize = %d\n",
/* Check is input parameter is OK. */
if ( dwInfoLevel != UNIVERSAL_NAME_INFO_LEVEL
{
Log(("VBOXNP: NPGetUniversalName: Bad dwInfoLevel value: %d\n",
dwInfoLevel));
return WN_BAD_LEVEL;
}
/* The 'lpLocalPath' is "X:\something". Extract the "X:" to pass to NPGetConnection. */
if ( lpLocalPath == NULL
|| lpLocalPath[0] == 0
{
Log(("VBOXNP: NPGetUniversalName: Bad lpLocalPath.\n"));
return WN_BAD_LOCALNAME;
}
LocalDrive[0] = lpLocalPath[0];
LocalDrive[2] = 0;
/* Length of the original path without the driver letter, including trailing NULL. */
/* Build the required structure in place of the supplied buffer. */
if (dwInfoLevel == UNIVERSAL_NAME_INFO_LEVEL)
{
BufferRequired = sizeof (UNIVERSAL_NAME_INFOW);
if (*lpBufferSize >= BufferRequired)
{
/* Enough place for the structure. */
/* At least so many bytes are available for obtaining the remote name. */
}
else
{
RemoteNameLength = 0;
}
/* Put the remote name directly to the buffer if possible and get the name length. */
if ( dwStatus != WN_SUCCESS
&& dwStatus != WN_MORE_DATA)
{
if (dwStatus != WN_NOT_CONNECTED)
{
Log(("VBOXNP: NPGetUniversalName: NPGetConnection returned error 0x%lx\n",
dwStatus));
}
return dwStatus;
}
if (RemoteNameLength < sizeof (WCHAR))
{
Log(("VBOXNP: NPGetUniversalName: Remote name is empty.\n"));
return WN_NO_NETWORK;
}
/* Adjust for actual remote name length. */
/* And for required place for remaining path. */
if (*lpBufferSize < BufferRequired)
{
Log(("VBOXNP: NPGetUniversalName: WN_MORE_DATA BufferRequired: %d\n",
return WN_MORE_DATA;
}
/* Enough memory in the buffer. Add '\' and remaining path to the remote name. */
lpString--; /* Trailing NULL */
}
else
{
BufferRequired = sizeof (REMOTE_NAME_INFOW);
if (*lpBufferSize >= BufferRequired)
{
/* Enough place for the structure. */
/* At least so many bytes are available for obtaining the remote name. */
}
else
{
RemoteNameLength = 0;
}
/* Put the remote name directly to the buffer if possible and get the name length. */
dwStatus = NPGetConnection(LocalDrive, RemoteNameLength? pRemoteNameInfo->lpUniversalName: NULL, &RemoteNameLength);
if ( dwStatus != WN_SUCCESS
&& dwStatus != WN_MORE_DATA)
{
if (dwStatus != WN_NOT_CONNECTED)
{
}
return dwStatus;
}
if (RemoteNameLength < sizeof (WCHAR))
{
Log(("VBOXNP: NPGetUniversalName: Remote name is empty.\n"));
return WN_NO_NETWORK;
}
/* Adjust for actual remote name length. */
/* And for required place for remaining path. */
/* lpConnectionName, which is the remote name. */
/* lpRemainingPath. */
if (*lpBufferSize < BufferRequired)
{
Log(("VBOXNP: NPGetUniversalName: WN_MORE_DATA BufferRequired: %d\n",
return WN_MORE_DATA;
}
/* Enough memory in the buffer. Add \ and remaining path to the remote name. */
lpString--; /* Trailing NULL */
lpDelimiter = lpString; /* Delimiter will be inserted later. For now keep the NULL terminated string. */
*lpDelimiter = 0;
*lpDelimiter = L'\\';
}
Log(("VBOXNP: NPGetUniversalName: WN_SUCCESS\n"));
return WN_SUCCESS;
}
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
RTR3InitDll(0);
VbglR3Init();
LogRel(("VBOXNP: DLL loaded.\n"));
break;
case DLL_PROCESS_DETACH:
LogRel(("VBOXNP: DLL unloaded.\n"));
VbglR3Term();
/// @todo RTR3Term();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
default:
break;
}
return fReturn;
}