From 0856d94e47796e995dfae7f94c3ebfcf524afd28 Mon Sep 17 00:00:00 2001 From: Selva Nair Date: Thu, 6 Oct 2022 21:13:42 -0400 Subject: [PATCH] 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 --- CMakeLists.txt | 27 +++++- configure.ac | 1 + plap/Makefile.am | 8 ++ plap/test-plap-res.rc | 23 ++++++ plap/test-plap.manifest | 56 +++++++++++++ plap/test_plap.cpp | 177 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 plap/test-plap-res.rc create mode 100644 plap/test-plap.manifest create mode 100644 plap/test_plap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fa02acb..e225651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ") diff --git a/configure.ac b/configure.ac index 986a5c0..bb1705d 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/plap/Makefile.am b/plap/Makefile.am index 9a215ab..311b6ac 100644 --- a/plap/Makefile.am +++ b/plap/Makefile.am @@ -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 diff --git a/plap/test-plap-res.rc b/plap/test-plap-res.rc new file mode 100644 index 0000000..7b89241 --- /dev/null +++ b/plap/test-plap-res.rc @@ -0,0 +1,23 @@ +/* + * OpenVPN-GUI -- A Windows GUI for OpenVPN. + * + * Copyright (C) 2009 Heiko Hund + * + * 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" diff --git a/plap/test-plap.manifest b/plap/test-plap.manifest new file mode 100644 index 0000000..5c68f9b --- /dev/null +++ b/plap/test-plap.manifest @@ -0,0 +1,56 @@ + + + +OpenVPN GUI PLAP DLL Test Program + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/PM + + + PerMonitorV2, PerMonitor + + + diff --git a/plap/test_plap.cpp b/plap/test_plap.cpp new file mode 100644 index 0000000..fbdf921 --- /dev/null +++ b/plap/test_plap.cpp @@ -0,0 +1,177 @@ +/* + * OpenVPN-PLAP-Provider + * + * Copyright (C) 2019-2022 Selva Nair + * + * 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 +#include "plap_common.h" +#include +#include +#include +#include +#include +#include +#include + +#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; +}