Add a test program for PLAP provider module

- instantiate OpenVPN PLAP provider which will enumerate configs
  in config-auto directory.
- Attempt to connect each config found one after the other

The test program is deliberately written in C++ as that's how most
Windows programs (and likely, LogonUI.exe) may use the COM object.

Note that duplicate configs are ignored, so ensure  that config
files in config-auto are not "shadowed" by identical named one's
in user's profile or in global config folder.

Additional notes:
The test program is not linked to the plap dll.
Instead it finds the module using CoGetClassObject,
so the plap dll must be registered in the system.

It also tests dynamically loading the dll from
C:\Program Files\OpenVPN\bin\libopenvpn_plap.dll
which should succeed even if the registration is not
proper.

Signed-off-by: Selva Nair <selva.nair@gmail.com>
pull/529/head
Selva Nair 2022-10-06 21:13:42 -04:00
parent a60b3c540b
commit 2ea4bf9229
6 changed files with 290 additions and 2 deletions

View File

@ -6,7 +6,7 @@ if(NOT VCPKG_MANIFEST_DIR)
set(VCPKG_MANIFEST_DIR ${CMAKE_SOURCE_DIR}/vcpkg_manifests/openssl_3)
endif()
project(openvpn-gui C)
project(openvpn-gui C CXX)
add_executable(${PROJECT_NAME} WIN32
access.c
@ -139,10 +139,33 @@ target_compile_definitions(${PROJECT_NAME_PLAP} PRIVATE
WIN32_LEAN_AND_MEAN
HAVE_CONFIG_H)
if (NOT TEST_PLAP_EXE)
set(TEST_PLAP_EXE "test_plap")
endif()
add_executable(${TEST_PLAP_EXE}
plap/test_plap.cpp
plap/plap_common.c)
target_link_libraries(${TEST_PLAP_EXE} PRIVATE
Rpcrt4
Ole32
Gdi32)
target_include_directories(${TEST_PLAP_EXE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR})
target_compile_definitions(${TEST_PLAP_EXE} PRIVATE
_UNICODE
UNICODE
WIN32_LEAN_AND_MEAN
HAVE_CONFIG_H)
if(MSVC)
# work around msvc generator Debug/Release directory ugliness, doesn't apply for Ninja
set_target_properties(${PROJECT_NAME} ${PROJECT_NAME_PLAP} PROPERTIES
set_target_properties(${PROJECT_NAME} ${PROJECT_NAME_PLAP} ${TEST_PLAP_EXE} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<0:>)
endif(MSVC)
set_target_properties(${TEST_PLAP_EXE} PROPERTIES
LINK_FLAGS " /MANIFEST:EMBED /MANIFESTINPUT:${CMAKE_SOURCE_DIR}/plap/test-plap.manifest ")

View File

@ -33,6 +33,7 @@ AM_INIT_AUTOMAKE([subdir-objects])
AC_CANONICAL_HOST
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC_C99
AC_PROG_CXX
AC_CHECK_TOOL([WINDRES], [windres])
LT_INIT([win32-dll])
LT_LANG([Windows Resource])

View File

@ -30,9 +30,13 @@ LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE)
MAINTAINERCLEANFILES = \
$(srcdir)/Makefile.in $(srcdir)/credentialprovider.h
#if ENABLE_OPENVPN_PLAP_TEST
bin_PROGRAMS = test_plap
#endif
lib_LTLIBRARIES = libopenvpn_plap.la
libopenvpn_plap_la_CFLAGS = -DDISABLE_PASSWORD_CHANGE -D_UNICODE -municode
test_plap_CXXFLAGS = -DDEBUG -D_UNICODE -municode
libopenvpn_plap_la_RESOURCES = \
$(top_srcdir)/res/openvpn-gui-res-cs.rc \
@ -101,3 +105,7 @@ libopenvpn_plap_la_LDFLAGS = -no-undefined -avoid-version -static-libgcc
credentialprovider.h:
wget https://raw.githubusercontent.com/mirror/mingw-w64/master/mingw-w64-headers/include/credentialprovider.h
test_plap_SOURCES = test_plap.cpp test-plap-res.rc test-plap.manifest plap_common.c credentialprovider.h
test_plap_LDFLAGS = -static-libgcc -static-libstdc++
test_plap_LDADD = -lrpcrt4 -lole32 -lgdi32 -lcomctl32

23
plap/test-plap-res.rc Normal file
View File

@ -0,0 +1,23 @@
/*
* OpenVPN-GUI -- A Windows GUI for OpenVPN.
*
* Copyright (C) 2009 Heiko Hund <heikoh@users.sf.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Manifest for visual styles */
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "test-plap.manifest"

56
plap/test-plap.manifest Normal file
View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
name="PLAP.TEST.Manifest"
version="1.0.0.0"
type="win32"/>
<description>OpenVPN GUI PLAP DLL Test Program</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<!-- Per Monitor V1 [OS >= Windows 8.1]
Values: False, True, Per-monitor, True/PM -->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<!-- Per Monitor V1 [OS >= Windows 10 Anniversary Update (1607, 10.0.14393, Redstone 1)]
Values: Unaware, System, PerMonitor -->
<!-- Per Monitor V2 [OS >= Windows 10 Creators Update (1703, 10.0.15063, Redstone 2)]
Value: PerMonitorV2 -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

177
plap/test_plap.cpp Normal file
View File

@ -0,0 +1,177 @@
/*
* OpenVPN-PLAP-Provider
*
* Copyright (C) 2019-2022 Selva Nair <selva.nair@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#undef WIN32_LEAN_AND_MEAN
#include <initguid.h>
#include "plap_common.h"
#include <windows.h>
#include <credentialprovider.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#define error_return(fname, hr) do { fwprintf(stdout, L"Error in %ls: status = 0x%08x", fname, hr);\
return 1;} while (0)
typedef HRESULT (WINAPI * f_func)(REFCLSID rclsid, REFIID riid, LPVOID* ppv);
class MyQueryContinue : public IQueryContinueWithStatus
{
public:
STDMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement(&ref_count);
}
STDMETHODIMP_(ULONG) Release() {
int count = InterlockedDecrement(&ref_count);
if (ref_count == 0) delete this;
return count;
}
STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { return E_FAIL; }
STDMETHODIMP QueryContinue() {return time(NULL) > timeout ? S_FALSE : S_OK;}
STDMETHODIMP SetStatusMessage(const wchar_t *ws) { wprintf(L"%ls\r", ws); return S_OK; }
MyQueryContinue() : ref_count(1) {};
time_t timeout;
private:
~MyQueryContinue()=default;
LONG ref_count;
};
static int test_provider(IClassFactory *cf)
{
assert(cf != NULL);
ICredentialProvider *o = NULL;
HRESULT hr;
hr = cf->CreateInstance(NULL, IID_ICredentialProvider, (void**) &o);
if (!SUCCEEDED(hr)) error_return(L"IID_ICredentialProvider", hr);
hr = o->SetUsageScenario(CPUS_PLAP, 0);
if (!SUCCEEDED(hr)) error_return(L"SetUsageScenario", hr);
DWORD count, def;
BOOL auto_def;
hr = o->GetCredentialCount(&count, &def, &auto_def);
if (!SUCCEEDED(hr)) error_return(L"GetCredentialCount", hr);
fwprintf(stdout, L"credential count = %lu, default = %d, autologon = %d\n", count, (int) def, auto_def);
if (count < 1) fwprintf(stdout, L"No persistent configs found!\n");
ICredentialProviderCredential *c = NULL;
MyQueryContinue *qc = new MyQueryContinue();
for (DWORD i = 0; i < count; i++)
{
hr = o->GetCredentialAt(i, &c);
if (!SUCCEEDED(hr)) error_return(L"GetCredentialAt", hr);
fwprintf(stdout, L"credential # = %lu: ", i);
wchar_t *ws;
for (DWORD j = 0; j < 4; j++)
{
hr = c->GetStringValue(j, &ws);
if (!SUCCEEDED(hr)) error_return(L"GetStringValue", hr);
CoTaskMemFree(ws);
}
/* test getbitmap */
HBITMAP bmp;
hr = c->GetBitmapValue(0, &bmp);
if (!SUCCEEDED(hr))
fwprintf(stdout, L"Warning: could not get bitmap"); /* not fatal */
else
DeleteObject(bmp);
/* set a time out so that we can move to next config in case */
qc->timeout = time(NULL) + 20;
/* get a connection instance and call connect on it */
IConnectableCredentialProviderCredential *c1 = NULL;
hr = c->QueryInterface(IID_IConnectableCredentialProviderCredential, (void**)&c1);
fwprintf(stdout, L"\nConnecting connection # <%lu>\n", i);
c1->Connect(qc); /* this will return when connected/failed or qc timesout */
fwprintf(stdout, L"\nsleep for 2 sec\n");
Sleep(2000);
c1->Release();
}
assert(o->Release() == 0); /* check refcount */
assert(qc->Release() == 0);
return 0;
}
int wmain()
{
HRESULT hr;
_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stderr), _O_U16TEXT);
IClassFactory *cf = NULL;
DWORD ctx = CLSCTX_INPROC_SERVER;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (!SUCCEEDED(hr)) error_return(L"CoIntialize", hr);
/* Test by loading the dll */
fwprintf(stdout, L"Test plap dll direct loading\n");
HMODULE lib = LoadLibraryW(L"C:\\Program Files\\OpenVPN\\bin\\libopenvpn_plap.dll");
f_func func = NULL;
if (lib == NULL)
{
fwprintf(stderr, L"Failed to load the dll: error = 0x%08x\n", GetLastError());
}
else {
func = (f_func) GetProcAddress(lib, "DllGetClassObject");
if (!func)
fwprintf(stderr, L"Failed to find DllGetClassObject in dll: error = 0x%08x\n", GetLastError());
}
if (func) {
hr = func(CLSID_OpenVPNProvider, IID_IClassFactory, (void **)&cf);
if (!SUCCEEDED(hr)) fwprintf(stdout, L"Error in DllGetClassObject: status = 0x%08x\n", hr);
else {
fwprintf(stdout, L"Success: found ovpn provider class factory by direct access\n");
cf->Release();
}
}
/* Test by finding the class through COM's registration mechanism */
fwprintf(stdout, L"Testing plap using CoGetclassobject -- requires proper dll registration\n");
hr = CoGetClassObject(CLSID_OpenVPNProvider, ctx, NULL, IID_IClassFactory, (void **)&cf);
if (SUCCEEDED(hr)) {
test_provider(cf);
cf->Release();
}
else {
fwprintf(stdout, L"CoGetClassObject (class not registered?): error = 0x%08x\n", hr);
}
CoUninitialize();
if (lib) {
FreeLibrary(lib);
}
return 0;
}