summaryrefslogtreecommitdiffstats
path: root/sal/osl/w32/security.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sal/osl/w32/security.cxx')
-rw-r--r--sal/osl/w32/security.cxx662
1 files changed, 662 insertions, 0 deletions
diff --git a/sal/osl/w32/security.cxx b/sal/osl/w32/security.cxx
new file mode 100644
index 000000000..21ed64a78
--- /dev/null
+++ b/sal/osl/w32/security.cxx
@@ -0,0 +1,662 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "system.h"
+#include <userenv.h>
+
+#include <cassert>
+#include <osl/security.h>
+#include <osl/diagnose.h>
+#include <osl/thread.h>
+#include <osl/file.h>
+#include <systools/win32/uwinapi.h>
+#include <sddl.h>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include "secimpl.hxx"
+
+/* To get an impersonation token we need to create an impersonation
+ duplicate so every access token has to be created with duplicate
+ access rights */
+
+#define TOKEN_DUP_QUERY (TOKEN_QUERY|TOKEN_DUPLICATE)
+
+static bool GetSpecialFolder(rtl_uString **strPath, REFKNOWNFOLDERID rFolder);
+// We use LPCTSTR here, because we use it with SE_foo_NAME constants
+// which are defined in winnt.h as UNICODE-dependent TEXT("PrivilegeName")
+static bool Privilege(LPCTSTR pszPrivilege, bool bEnable);
+static bool getUserNameImpl(oslSecurity Security, rtl_uString **strName, bool bIncludeDomain);
+
+oslSecurity SAL_CALL osl_getCurrentSecurity(void)
+{
+ oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl)));
+ if (pSecImpl)
+ {
+ pSecImpl->m_pNetResource = nullptr;
+ pSecImpl->m_User[0] = '\0';
+ pSecImpl->m_hToken = nullptr;
+ pSecImpl->m_hProfile = nullptr;
+ }
+ return pSecImpl;
+}
+
+oslSecurityError SAL_CALL osl_loginUser( rtl_uString *strUserName, rtl_uString *strPasswd, oslSecurity *pSecurity )
+{
+ oslSecurityError ret;
+
+ sal_Unicode* strUser;
+ sal_Unicode* strDomain = o3tl::toU(_wcsdup(o3tl::toW(rtl_uString_getStr(strUserName))));
+ HANDLE hUserToken;
+ LUID luid;
+
+ if (nullptr != (strUser = o3tl::toU(wcschr(o3tl::toW(strDomain), L'/'))))
+ *strUser++ = L'\0';
+ else
+ {
+ strUser = strDomain;
+ strDomain = nullptr;
+ }
+
+ // this process must have the right: 'act as a part of operatingsystem'
+ OSL_ASSERT(LookupPrivilegeValue(nullptr, SE_TCB_NAME, &luid));
+ (void) luid;
+
+ if (LogonUserW(o3tl::toW(strUser), strDomain ? o3tl::toW(strDomain) : L"", o3tl::toW(rtl_uString_getStr(strPasswd)),
+ LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
+ &hUserToken))
+ {
+ oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl)));
+ if (pSecImpl)
+ {
+ pSecImpl->m_pNetResource = nullptr;
+ pSecImpl->m_hToken = hUserToken;
+ pSecImpl->m_hProfile = nullptr;
+ wcscpy(o3tl::toW(pSecImpl->m_User), o3tl::toW(strUser));
+ }
+ *pSecurity = pSecImpl;
+ ret = pSecImpl ? osl_Security_E_None : osl_Security_E_Unknown;
+ }
+ else
+ {
+ ret = osl_Security_E_UserUnknown;
+ }
+
+ if (strDomain)
+ free(strDomain);
+ else
+ free(strUser);
+
+ return ret;
+}
+
+oslSecurityError SAL_CALL osl_loginUserOnFileServer(rtl_uString *strUserName,
+ rtl_uString *strPasswd,
+ rtl_uString *strFileServer,
+ oslSecurity *pSecurity)
+{
+ oslSecurityError ret;
+ DWORD err;
+ NETRESOURCEW netResource;
+ sal_Unicode* remoteName;
+ sal_Unicode* userName;
+
+ remoteName = static_cast<sal_Unicode *>(malloc((rtl_uString_getLength(strFileServer) + rtl_uString_getLength(strUserName) + 4) * sizeof(sal_Unicode)));
+ userName = static_cast<sal_Unicode *>(malloc((rtl_uString_getLength(strFileServer) + rtl_uString_getLength(strUserName) + 2) * sizeof(sal_Unicode)));
+
+ wcscpy(o3tl::toW(remoteName), L"\\\\");
+ wcscat(o3tl::toW(remoteName), o3tl::toW(rtl_uString_getStr(strFileServer)));
+ wcscat(o3tl::toW(remoteName), L"\\");
+ wcscat(o3tl::toW(remoteName), o3tl::toW(rtl_uString_getStr(strUserName)));
+
+ wcscpy(o3tl::toW(userName), o3tl::toW(rtl_uString_getStr(strFileServer)));
+ wcscat(o3tl::toW(userName), L"\\");
+ wcscat(o3tl::toW(userName), o3tl::toW(rtl_uString_getStr(strUserName)));
+
+ netResource.dwScope = RESOURCE_GLOBALNET;
+ netResource.dwType = RESOURCETYPE_DISK;
+ netResource.dwDisplayType = RESOURCEDISPLAYTYPE_SHARE;
+ netResource.dwUsage = RESOURCEUSAGE_CONNECTABLE;
+ netResource.lpLocalName = nullptr;
+ netResource.lpRemoteName = o3tl::toW(remoteName);
+ netResource.lpComment = nullptr;
+ netResource.lpProvider = nullptr;
+
+ err = WNetAddConnection2W(&netResource, o3tl::toW(rtl_uString_getStr(strPasswd)), o3tl::toW(userName), 0);
+
+ if ((err == NO_ERROR) || (err == ERROR_ALREADY_ASSIGNED))
+ {
+ oslSecurityImpl* pSecImpl = static_cast<oslSecurityImpl *>(malloc(sizeof(oslSecurityImpl)));
+ if (pSecImpl)
+ {
+ pSecImpl->m_pNetResource = static_cast<NETRESOURCEW *>(malloc(sizeof(NETRESOURCE)));
+ if (pSecImpl->m_pNetResource)
+ {
+ *pSecImpl->m_pNetResource = netResource;
+ pSecImpl->m_hToken = nullptr;
+ pSecImpl->m_hProfile = nullptr;
+ wcscpy(o3tl::toW(pSecImpl->m_User), o3tl::toW(rtl_uString_getStr(strUserName)));
+ }
+ else
+ {
+ free(pSecImpl);
+ pSecImpl = nullptr;
+ }
+ }
+ *pSecurity = pSecImpl;
+
+ ret = pSecImpl ? osl_Security_E_None : osl_Security_E_Unknown;
+ }
+ else
+ {
+ ret = osl_Security_E_UserUnknown;
+ }
+
+ free(remoteName);
+ free(userName);
+
+ return ret;
+}
+
+sal_Bool SAL_CALL osl_isAdministrator(oslSecurity Security)
+{
+ if (!Security)
+ return false;
+
+ HANDLE hImpersonationToken = nullptr;
+ PSID psidAdministrators;
+ SID_IDENTIFIER_AUTHORITY siaNtAuthority = { SECURITY_NT_AUTHORITY };
+ bool bSuccess = false;
+
+ /* If Security contains an access token we need to duplicate it to an impersonation
+ access token. NULL works with CheckTokenMembership() as the current effective
+ impersonation token
+ */
+
+ if ( static_cast<oslSecurityImpl*>(Security)->m_hToken )
+ {
+ if ( !DuplicateToken (static_cast<oslSecurityImpl*>(Security)->m_hToken, SecurityImpersonation, &hImpersonationToken) )
+ return false;
+ }
+
+ /* CheckTokenMembership() can be used on W2K and higher (NT4 no longer supported by OOo)
+ and also works on Vista to retrieve the effective user rights. Just checking for
+ membership of Administrators group is not enough on Vista this would require additional
+ complicated checks as described in KB article Q118626: http://support.microsoft.com/kb/118626/en-us
+ */
+
+ if (AllocateAndInitializeSid(&siaNtAuthority,
+ 2,
+ SECURITY_BUILTIN_DOMAIN_RID,
+ DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &psidAdministrators))
+ {
+ BOOL fSuccess = FALSE;
+
+ if (CheckTokenMembership(hImpersonationToken, psidAdministrators, &fSuccess) && fSuccess)
+ bSuccess = true;
+
+ FreeSid(psidAdministrators);
+ }
+
+ if (hImpersonationToken)
+ CloseHandle(hImpersonationToken);
+
+ return bSuccess;
+}
+
+void SAL_CALL osl_freeSecurityHandle(oslSecurity Security)
+{
+ if (!Security)
+ return;
+
+ oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security);
+
+ if (pSecImpl->m_pNetResource != nullptr)
+ {
+ WNetCancelConnection2W(pSecImpl->m_pNetResource->lpRemoteName, 0, true);
+
+ free(pSecImpl->m_pNetResource->lpRemoteName);
+ free(pSecImpl->m_pNetResource);
+ }
+
+ if (pSecImpl->m_hToken)
+ CloseHandle(pSecImpl->m_hToken);
+
+ if ( pSecImpl->m_hProfile )
+ CloseHandle(pSecImpl->m_hProfile);
+
+ free (pSecImpl);
+}
+
+sal_Bool SAL_CALL osl_getUserIdent(oslSecurity Security, rtl_uString **strIdent)
+{
+ if (!Security)
+ return false;
+
+ oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security);
+
+ HANDLE hAccessToken = pSecImpl->m_hToken;
+
+ if (hAccessToken == nullptr)
+ OpenProcessToken(GetCurrentProcess(), TOKEN_DUP_QUERY, &hAccessToken);
+
+ if (hAccessToken)
+ {
+ DWORD nInfoBuffer = 512;
+ UCHAR* pInfoBuffer = static_cast<UCHAR *>(malloc(nInfoBuffer));
+
+ while (!GetTokenInformation(hAccessToken, TokenUser,
+ pInfoBuffer, nInfoBuffer, &nInfoBuffer))
+ {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ if (auto p = static_cast<UCHAR *>(realloc(pInfoBuffer, nInfoBuffer)))
+ pInfoBuffer = p;
+ else
+ {
+ free(pInfoBuffer);
+ pInfoBuffer = nullptr;
+ break;
+ }
+ }
+ else
+ {
+ free(pInfoBuffer);
+ pInfoBuffer = nullptr;
+ break;
+ }
+ }
+
+ if (pSecImpl->m_hToken == nullptr)
+ CloseHandle(hAccessToken);
+
+ if (pInfoBuffer)
+ {
+ PSID pSid = reinterpret_cast<PTOKEN_USER>(pInfoBuffer)->User.Sid;
+
+ LPWSTR pSidStr = nullptr;
+ bool bResult = ConvertSidToStringSidW(pSid, &pSidStr);
+ if (bResult)
+ {
+ rtl_uString_newFromStr(strIdent, o3tl::toU(pSidStr));
+ LocalFree(pSidStr);
+ }
+ else
+ {
+ const DWORD dwError = GetLastError();
+ SAL_WARN(
+ "sal.osl",
+ "ConvertSidToStringSidW failed. GetLastError returned: " << dwError);
+ }
+
+ free(pInfoBuffer);
+
+ return bResult;
+ }
+ }
+ else
+ {
+ DWORD needed = 0;
+
+ WNetGetUserW(nullptr, nullptr, &needed);
+ if (needed < 16)
+ needed = 16;
+
+ if (auto Ident = static_cast<sal_Unicode *>(malloc(needed*sizeof(sal_Unicode))))
+ {
+ if (WNetGetUserW(nullptr, o3tl::toW(Ident), &needed) != NO_ERROR)
+ {
+ wcscpy(o3tl::toW(Ident), L"unknown");
+ Ident[7] = L'\0';
+ }
+
+ rtl_uString_newFromStr( strIdent, Ident);
+ free(Ident);
+ return true;
+ }
+ }
+ return false;
+}
+
+sal_Bool SAL_CALL osl_getUserName(oslSecurity Security, rtl_uString **strName)
+{
+ return getUserNameImpl(Security, strName, true);
+}
+
+sal_Bool SAL_CALL osl_getShortUserName(oslSecurity Security, rtl_uString **strName)
+{
+ return getUserNameImpl(Security, strName, false);
+}
+
+sal_Bool SAL_CALL osl_getHomeDir(oslSecurity Security, rtl_uString **pustrDirectory)
+{
+ if (!Security)
+ return false;
+
+ rtl_uString *ustrSysDir = nullptr;
+ bool bSuccess = false;
+
+ oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security);
+
+ if (pSecImpl->m_pNetResource != nullptr)
+ {
+ rtl_uString_newFromStr( &ustrSysDir, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName));
+
+ bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath( ustrSysDir, pustrDirectory );
+ }
+ else
+ {
+ bSuccess = GetSpecialFolder(&ustrSysDir, FOLDERID_Documents) &&
+ (osl_File_E_None == osl_getFileURLFromSystemPath(ustrSysDir, pustrDirectory));
+ }
+
+ if ( ustrSysDir )
+ rtl_uString_release( ustrSysDir );
+
+ return bSuccess;
+}
+
+sal_Bool SAL_CALL osl_getConfigDir(oslSecurity Security, rtl_uString **pustrDirectory)
+{
+ if (!Security)
+ return false;
+
+ bool bSuccess = false;
+ oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security);
+
+ if (pSecImpl->m_pNetResource != nullptr)
+ {
+ rtl_uString *ustrSysDir = nullptr;
+
+ rtl_uString_newFromStr( &ustrSysDir, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName));
+ bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath( ustrSysDir, pustrDirectory);
+
+ if ( ustrSysDir )
+ rtl_uString_release( ustrSysDir );
+ }
+ else
+ {
+ if (pSecImpl->m_hToken)
+ {
+ /* not implemented */
+ OSL_ASSERT(false);
+ }
+ else
+ {
+ rtl_uString *ustrFile = nullptr;
+ sal_Unicode sFile[_MAX_PATH];
+
+ if ( !GetSpecialFolder( &ustrFile, FOLDERID_RoamingAppData) )
+ {
+ OSL_VERIFY(GetWindowsDirectoryW(o3tl::toW(sFile), _MAX_DIR) > 0);
+
+ rtl_uString_newFromStr( &ustrFile, sFile);
+ }
+
+ bSuccess = osl_File_E_None == osl_getFileURLFromSystemPath(ustrFile, pustrDirectory);
+
+ if ( ustrFile )
+ rtl_uString_release( ustrFile );
+ }
+ }
+
+ return bSuccess;
+}
+
+sal_Bool SAL_CALL osl_loadUserProfile(oslSecurity Security)
+{
+ /* CreateProcessAsUser does not load the specified user's profile
+ into the HKEY_USERS registry key. This means that access to information
+ in the HKEY_CURRENT_USER registry key may not produce results consistent
+ with a normal interactive logon.
+ It is your responsibility to load the user's registry hive into HKEY_USERS
+ with the LoadUserProfile function before calling CreateProcessAsUser.
+ */
+
+ RegCloseKey(HKEY_CURRENT_USER);
+
+ if (!Privilege(SE_RESTORE_NAME, true))
+ return false;
+
+ bool bOk = false;
+ HANDLE hAccessToken = static_cast<oslSecurityImpl*>(Security)->m_hToken;
+
+ /* try to create user profile */
+ if ( !hAccessToken )
+ {
+ /* retrieve security handle if not done before e.g. osl_getCurrentSecurity()
+ */
+ HANDLE hProcess = GetCurrentProcess();
+
+ if (hProcess != nullptr)
+ {
+ OpenProcessToken(hProcess, TOKEN_IMPERSONATE, &hAccessToken);
+ CloseHandle(hProcess);
+ }
+ }
+
+ rtl_uString *buffer = nullptr;
+ PROFILEINFOW pi;
+
+ getUserNameImpl(Security, &buffer, false);
+
+ ZeroMemory(&pi, sizeof(pi));
+ pi.dwSize = sizeof(pi);
+ pi.lpUserName = o3tl::toW(rtl_uString_getStr(buffer));
+ pi.dwFlags = PI_NOUI;
+
+ if (LoadUserProfileW(hAccessToken, &pi))
+ {
+ UnloadUserProfile(hAccessToken, pi.hProfile);
+
+ bOk = true;
+ }
+
+ rtl_uString_release(buffer);
+
+ if (hAccessToken && (hAccessToken != static_cast<oslSecurityImpl*>(Security)->m_hToken))
+ CloseHandle(hAccessToken);
+
+ return bOk;
+}
+
+void SAL_CALL osl_unloadUserProfile(oslSecurity Security)
+{
+ if ( static_cast<oslSecurityImpl*>(Security)->m_hProfile == nullptr )
+ return;
+
+ HANDLE hAccessToken = static_cast<oslSecurityImpl*>(Security)->m_hToken;
+
+ if ( !hAccessToken )
+ {
+ /* retrieve security handle if not done before e.g. osl_getCurrentSecurity()
+ */
+ HANDLE hProcess = GetCurrentProcess();
+
+ if (hProcess != nullptr)
+ {
+ OpenProcessToken(hProcess, TOKEN_IMPERSONATE, &hAccessToken);
+ CloseHandle(hProcess);
+ }
+ }
+
+ /* unloading the user profile */
+ UnloadUserProfile(hAccessToken, static_cast<oslSecurityImpl*>(Security)->m_hProfile);
+
+ static_cast<oslSecurityImpl*>(Security)->m_hProfile = nullptr;
+
+ if (hAccessToken && (hAccessToken != static_cast<oslSecurityImpl*>(Security)->m_hToken))
+ CloseHandle(hAccessToken);
+}
+
+static bool GetSpecialFolder(rtl_uString **strPath, REFKNOWNFOLDERID rFolder)
+{
+ bool bRet = false;
+ PWSTR PathW;
+ if (SUCCEEDED(SHGetKnownFolderPath(rFolder, KF_FLAG_CREATE, nullptr, &PathW)))
+ {
+ rtl_uString_newFromStr(strPath, o3tl::toU(PathW));
+ CoTaskMemFree(PathW);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+// We use LPCTSTR here, because we use it with SE_foo_NAME constants
+// which are defined in winnt.h as UNICODE-dependent TEXT("PrivilegeName")
+static bool Privilege(LPCTSTR strPrivilege, bool bEnable)
+{
+ HANDLE hToken;
+ TOKEN_PRIVILEGES tp;
+
+ // obtain the processes token
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_DUP_QUERY, &hToken))
+ return false;
+
+ // get the luid
+ if (!LookupPrivilegeValue(nullptr, strPrivilege, &tp.Privileges[0].Luid))
+ return false;
+
+ tp.PrivilegeCount = 1;
+
+ if (bEnable)
+ tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ else
+ tp.Privileges[0].Attributes = 0;
+
+ // enable or disable the privilege
+ if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, nullptr, nullptr))
+ return false;
+
+ if (!CloseHandle(hToken))
+ return false;
+
+ return true;
+}
+
+static bool getUserNameImpl(oslSecurity Security, rtl_uString **strName, bool bIncludeDomain)
+{
+ if (!Security)
+ return false;
+
+ oslSecurityImpl *pSecImpl = static_cast<oslSecurityImpl*>(Security);
+
+ HANDLE hAccessToken = pSecImpl->m_hToken;
+
+ if (hAccessToken == nullptr)
+ OpenProcessToken(GetCurrentProcess(), TOKEN_DUP_QUERY, &hAccessToken);
+
+ if (hAccessToken)
+ {
+ DWORD nInfoBuffer = 512;
+ UCHAR* pInfoBuffer = static_cast<UCHAR *>(malloc(nInfoBuffer));
+
+ while (!GetTokenInformation(hAccessToken, TokenUser,
+ pInfoBuffer, nInfoBuffer, &nInfoBuffer))
+ {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ {
+ if (auto p = static_cast<UCHAR *>(realloc(pInfoBuffer, nInfoBuffer)))
+ pInfoBuffer = p;
+ else
+ {
+ free(pInfoBuffer);
+ pInfoBuffer = nullptr;
+ break;
+ }
+ }
+ else
+ {
+ free(pInfoBuffer);
+ pInfoBuffer = nullptr;
+ break;
+ }
+ }
+
+ if (pSecImpl->m_hToken == nullptr)
+ CloseHandle(hAccessToken);
+
+ if (pInfoBuffer)
+ {
+ sal_Unicode UserName[128];
+ sal_Unicode DomainName[128];
+ sal_Unicode Name[257];
+ DWORD nUserName = SAL_N_ELEMENTS(UserName);
+ DWORD nDomainName = SAL_N_ELEMENTS(DomainName);
+ SID_NAME_USE sUse;
+
+ if (LookupAccountSidW(nullptr, reinterpret_cast<PTOKEN_USER>(pInfoBuffer)->User.Sid,
+ o3tl::toW(UserName), &nUserName,
+ o3tl::toW(DomainName), &nDomainName, &sUse))
+ {
+ if (bIncludeDomain)
+ {
+ wcscpy(o3tl::toW(Name), o3tl::toW(DomainName));
+ wcscat(o3tl::toW(Name), L"/");
+ wcscat(o3tl::toW(Name), o3tl::toW(UserName));
+ }
+ else
+ {
+ wcscpy(o3tl::toW(Name), o3tl::toW(UserName));
+ }
+
+ rtl_uString_newFromStr(strName, Name);
+ free(pInfoBuffer);
+ return true;
+ }
+ }
+ }
+ else
+ {
+ DWORD needed=0;
+ sal_Unicode *pNameW=nullptr;
+
+ WNetGetUserW(nullptr, nullptr, &needed);
+ pNameW = static_cast<sal_Unicode *>(malloc (needed*sizeof(sal_Unicode)));
+ assert(pNameW); // Don't handle OOM conditions
+
+ if (WNetGetUserW(nullptr, o3tl::toW(pNameW), &needed) == NO_ERROR)
+ {
+ rtl_uString_newFromStr( strName, pNameW);
+
+ if (pNameW)
+ free(pNameW);
+ return true;
+ }
+ else if (pSecImpl->m_User[0] != '\0')
+ {
+ rtl_uString_newFromStr(strName, o3tl::toU(pSecImpl->m_pNetResource->lpRemoteName));
+
+ if (pNameW)
+ free(pNameW);
+
+ return true;
+ }
+
+ if (pNameW)
+ free(pNameW);
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */