openvpn-gui/plap/plap_connection.c

692 lines
19 KiB
C

/*
* OpenVPN-PLAP-Provider
*
* Copyright (C) 2017-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
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "plap_common.h"
#include "plap_connection.h"
#include "plap_dll.h"
#include <assert.h>
#include "resource.h"
/* A "class" that implements IConnectableCredentialProviderCredential */
struct OpenVPNConnection
{
const IConnectableCredentialProviderCredentialVtbl *lpVtbl; /* base class vtable */
/* "private" members */
ICredentialProviderCredentialEvents *events; /* Passed in by Logon UI for callbacks */
connection_t *c; /* GUI connection data */
const wchar_t *display_name;
IQueryContinueWithStatus *qc; /* Passed in by LogonUI for checking status of connect */
BOOL connect_cancelled; /* we set this true if user cancels the connect operation */
LONG ref_count;
};
#define ICCPC IConnectableCredentialProviderCredential /* shorthand for a long name */
#define ISCONNECTED(c) (ConnectionState(c) == state_connected)
#define ISDISCONNECTED(c) (ConnectionState(c) == state_disconnected)
#define ISONHOLD(c) (ConnectionState(c) == state_onhold)
extern DWORD status_menu_id;
/* Methods in IConnectableCredentialProviderCredential that we need to define */
/* IUnknown */
static HRESULT WINAPI QueryInterface(ICCPC *this, REFIID riid, void** ppv);
static ULONG WINAPI AddRef(ICCPC *this);
static ULONG WINAPI Release(ICCPC *this);
/* ICredentialProviderCredential */
static HRESULT WINAPI Advise(ICCPC *this, ICredentialProviderCredentialEvents *e);
static HRESULT WINAPI UnAdvise(ICCPC *this);
static HRESULT WINAPI SetSelected(ICCPC *this, BOOL *auto_logon);
static HRESULT WINAPI SetDeselected(ICCPC *this);
static HRESULT WINAPI GetFieldState(ICCPC *this, DWORD field, CREDENTIAL_PROVIDER_FIELD_STATE *fs,
CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *fis);
static HRESULT WINAPI GetStringValue(ICCPC *this, DWORD index, WCHAR **ws);
static HRESULT WINAPI GetBitmapValue(ICCPC *this, DWORD field, HBITMAP *bmp);
static HRESULT WINAPI GetSubmitButtonValue(ICCPC *this, DWORD field, DWORD *adjacent);
static HRESULT WINAPI SetStringValue(ICCPC *this, DWORD field, const WCHAR *ws);
static HRESULT WINAPI GetCheckboxValue(ICCPC *this, DWORD field, BOOL *checked, wchar_t **label);
static HRESULT WINAPI GetComboBoxValueCount(ICCPC *this, DWORD field, DWORD *items, DWORD *selected_item);
static HRESULT WINAPI GetComboBoxValueAt(ICCPC *this, DWORD field, DWORD item, wchar_t **item_value);
static HRESULT WINAPI SetCheckboxValue(ICCPC *this, DWORD field, BOOL checked);
static HRESULT WINAPI SetComboBoxSelectedValue(ICCPC *this, DWORD field, DWORD selected_item);
static HRESULT WINAPI CommandLinkClicked(ICCPC *this, DWORD field);
static HRESULT WINAPI GetSerialization(ICCPC *this,
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *response,
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *cs, wchar_t **text,
CREDENTIAL_PROVIDER_STATUS_ICON *icon);
static HRESULT WINAPI ReportResult(ICCPC *this, NTSTATUS status, NTSTATUS substatus,
wchar_t **status_text, CREDENTIAL_PROVIDER_STATUS_ICON *icon);
/* IConnectableCredentialProviderCredential */
static HRESULT WINAPI Connect(ICCPC *this, IQueryContinueWithStatus *qc);
static HRESULT WINAPI Disconnect(ICCPC *this);
/* use a static table for filling in the methods */
#define M_(x) .x = x
static const IConnectableCredentialProviderCredentialVtbl iccpc_vtbl = {
M_(QueryInterface),
M_(AddRef),
M_(Release),
M_(Advise),
M_(UnAdvise),
M_(SetSelected),
M_(SetDeselected),
M_(GetFieldState),
M_(GetStringValue),
M_(GetBitmapValue),
M_(GetCheckboxValue),
M_(GetSubmitButtonValue),
M_(GetComboBoxValueCount),
M_(GetComboBoxValueAt),
M_(SetStringValue),
M_(SetCheckboxValue),
M_(SetComboBoxSelectedValue),
M_(CommandLinkClicked),
M_(GetSerialization),
M_(ReportResult),
M_(Connect),
M_(Disconnect),
};
/* constructor */
OpenVPNConnection *
OpenVPNConnection_new()
{
dmsg(L"Entry");
OpenVPNConnection *oc = calloc(sizeof(*oc), 1);
if (oc)
{
oc->lpVtbl = &iccpc_vtbl;
oc->ref_count = 1;
dll_addref();
}
return oc;
}
/* destructor */
static void
OpenVPNConnection_free(OpenVPNConnection *oc)
{
dmsg(L"Entry: profile: <%ls>", oc->display_name);
free(oc);
dll_release();
}
static ULONG WINAPI
AddRef(ICCPC *this)
{
OpenVPNConnection *oc = (OpenVPNConnection *) this;
dmsg(L"Connection: ref_count after increment = %lu", oc->ref_count + 1);
return InterlockedIncrement(&oc->ref_count);
}
static ULONG WINAPI
Release(ICCPC *this)
{
OpenVPNConnection *oc = (OpenVPNConnection *) this;
int count = InterlockedDecrement(&oc->ref_count);
dmsg(L"Connection: ref_count after decrement = %lu", count);
if (count == 0)
{
OpenVPNConnection_free(oc); /* delete self */
}
return count;
}
static HRESULT WINAPI
QueryInterface(ICCPC *this, REFIID riid, void** ppv)
{
HRESULT hr;
#ifdef DEBUG
debug_print_guid(riid, L"In OVPN Connection QueryInterface with riid = ");
#endif
if (ppv != NULL)
{
if (IsEqualIID(riid, &IID_IUnknown)
|| IsEqualIID(riid, &IID_ICredentialProviderCredential)
|| IsEqualIID(riid, &IID_IConnectableCredentialProviderCredential))
{
*ppv = this;
ADDREF(this);
hr = S_OK;
}
else
{
*ppv = NULL;
hr = E_NOINTERFACE;
}
}
else
{
hr = E_POINTER;
}
return hr;
}
/*
* LogonUI calls Advise first and the passed in events may be used for
* making callbacks to notify changes asynchronously
*/
static HRESULT
WINAPI Advise(ICCPC *this, ICredentialProviderCredentialEvents *e)
{
HWND hwnd;
dmsg(L"Entry");
OpenVPNConnection *oc = (OpenVPNConnection *) this;
if (oc->events)
RELEASE(oc->events);
oc->events = e;
ADDREF(e);
if (e
&& e->lpVtbl->OnCreatingWindow(e, &hwnd) == S_OK)
{
dmsg(L"Setting hwnd");
SetParentWindow(hwnd);
}
return S_OK;
}
/* We keep a copy of events pointer in Advise, release it here */
static HRESULT WINAPI
UnAdvise(ICCPC *this)
{
dmsg(L"Entry");
OpenVPNConnection *oc = (OpenVPNConnection *) this;
if (oc->events)
RELEASE(oc->events);
oc->events = NULL;
return S_OK;
}
/*
* LogonUI calls this function when our tile is selected
* We do nothing here and let the user choose which profile to connect.
*/
static HRESULT WINAPI
SetSelected(ICCPC *this, BOOL *auto_logon)
{
#ifdef DEBUG
OpenVPNConnection *oc = (OpenVPNConnection *) this;
dmsg(L"profile: %ls", oc->display_name);
#else
(void) this;
#endif
/* setting true here will autoconnect the first entry and Windows will
* autoselect it for the rest of the login session, blocking all other
* entries and other providers, if any. Some commercial products do
* that but its not a good idea, IMO. So we set false here.
*/
*auto_logon = FALSE;
return S_OK;
}
static HRESULT WINAPI
SetDeselected(ICCPC *this)
{
#ifdef DEBUG
OpenVPNConnection *oc = (OpenVPNConnection *) this;
dmsg(L"profile: %ls", oc->display_name);
#else
(void) this;
#endif
return S_OK;
}
/*
* Return display states for a particular field of a tile
*/
static HRESULT WINAPI
GetFieldState(UNUSED ICCPC *this, DWORD field,
CREDENTIAL_PROVIDER_FIELD_STATE *fs,
CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *fis)
{
HRESULT hr;
dmsg(L"field = %lu", field);
if (field < _countof(field_states))
{
if (fs)
*fs = field_states[field];
if (fis)
*fis = (field_desc[field].cpft == CPFT_SUBMIT_BUTTON) ? CPFIS_FOCUSED : CPFIS_NONE;
hr = S_OK;
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
/*
* Return the string value of the field at the index
*/
static HRESULT WINAPI
GetStringValue(ICCPC *this, DWORD index, WCHAR **ws)
{
HRESULT hr = S_OK;
dmsg(L"index = %lu", index);
OpenVPNConnection *oc = (OpenVPNConnection *) this;
const wchar_t *val = L"";
if (index >= _countof(field_desc))
{
return E_INVALIDARG;
}
/* we have custom field strings only for connection name */
if (field_desc[index].cpft == CPFT_LARGE_TEXT)
{
val = oc->display_name; /* connection name */
}
/* do not to use malloc here as the client will free using CoTaskMemFree */
hr = SHStrDupW(val, ws);
if (!SUCCEEDED(hr))
{
hr = E_OUTOFMEMORY;
dmsg(L"OOM while copying field_strings[%lu]", index);
}
else
{
dmsg(L"Returning %ls", *ws);
}
return hr;
}
/*
* return the image for the tile at index field
*/
static HRESULT WINAPI
GetBitmapValue(UNUSED ICCPC *this, DWORD field, HBITMAP *bmp)
{
HRESULT hr = S_OK;
dmsg(L"field = %lu ", field);
if (field_desc[field].cpft == CPFT_TILE_IMAGE && bmp)
{
HBITMAP tmp = LoadBitmapW(hinst_global, MAKEINTRESOURCE(IDB_TILE_IMAGE));
if (tmp)
{
*bmp = tmp; /* The caller takes ownership of this memory */
dmsg(L"Returning a bitmap for PLAP tile %lu", field);
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
dmsg(L"LoadBitmap failed with error = 0x%08x", hr);
}
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
/*
* Return the index of the field the button should be placed adjacent to.
*/
static HRESULT WINAPI
GetSubmitButtonValue(UNUSED ICCPC *this, DWORD field, DWORD *adjacent )
{
HRESULT hr;
dmsg(L"field = %lu", field);
/* we have defined field ids in order of appearance */
if (field_desc[field].cpft == CPFT_SUBMIT_BUTTON && field > 0 && adjacent)
{
*adjacent = field - 1;
hr = S_OK;
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
/*
* Set the value of a field which can accept user text input
* This gets called as user types into a field.
*/
static HRESULT WINAPI
SetStringValue(UNUSED ICCPC *this, UNUSED DWORD field, UNUSED const WCHAR *ws )
{
HRESULT hr;
dmsg(L"field = %lu", field);
/* We do not allow setting any fields -- at least not yet */
hr = E_INVALIDARG;
return hr;
}
static HRESULT WINAPI
GetCheckboxValue(UNUSED ICCPC *this, UNUSED DWORD field,
UNUSED BOOL *checked, UNUSED wchar_t **label)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
static HRESULT WINAPI
GetComboBoxValueCount(UNUSED ICCPC *this, UNUSED DWORD field, UNUSED DWORD *items,
UNUSED DWORD *selected_item)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
static HRESULT WINAPI
GetComboBoxValueAt(UNUSED ICCPC *this, UNUSED DWORD field, UNUSED DWORD item,
UNUSED wchar_t **item_value)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
static HRESULT WINAPI
SetCheckboxValue(UNUSED ICCPC *this, UNUSED DWORD field, UNUSED BOOL checked)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
static HRESULT WINAPI
SetComboBoxSelectedValue(UNUSED ICCPC *this, UNUSED DWORD field, UNUSED DWORD selected_item)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
static HRESULT WINAPI
CommandLinkClicked(UNUSED ICCPC *this, UNUSED DWORD field)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
/*
* Collect the username and password into a serialized credential for the usage scenario
* This gets called soon after connect -- result returned here influences the status of
* Connect.
* We do not support SSO, so no credentials are serialized. We just indicate whether
* we got correct credentials for the Connect process or not.
*/
static HRESULT WINAPI
GetSerialization(ICCPC *this, CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *response,
UNUSED CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *cs, wchar_t **text,
CREDENTIAL_PROVIDER_STATUS_ICON *icon)
{
HRESULT hr = S_OK;
OpenVPNConnection *oc = (OpenVPNConnection *) this;
dmsg(L"Entry: profile <%ls>", oc->display_name);
if (oc->connect_cancelled || !ISCONNECTED(oc->c))
{
dmsg(L"connect cancelled or failed");
/* return _NOT_FINISHED response here for the prompt go
* back to the PLAP tiles screen. Unclear the return value
* should be success or not : using S_OK.
*/
*response = CPGSR_NO_CREDENTIAL_NOT_FINISHED;
if (text && icon)
{
if (oc->connect_cancelled)
hr = SHStrDupW(L"Connection cancelled by user", text);
else if (text)
hr = SHStrDupW(L"Connection failed to complete", text);
*icon = CPSI_ERROR;
}
}
else
{
/* Finished but no credentials to pass on to LogonUI */
*response = CPGSR_NO_CREDENTIAL_FINISHED;
/* return CPGSR_RETURN_NO_CREDENTIAL_FINISHED leads to a loop! */
if (text && icon)
{
hr = SHStrDupW(L"Connected..", text);
*icon = CPSI_SUCCESS;
}
}
return hr;
}
static HRESULT WINAPI
ReportResult(UNUSED ICCPC *this, UNUSED NTSTATUS status, UNUSED NTSTATUS substatus,
UNUSED wchar_t **status_text, UNUSED CREDENTIAL_PROVIDER_STATUS_ICON *icon)
{
dmsg(L"Entry");
return E_NOTIMPL;
}
/* Helper to send status string feedback to events object and qc if available */
static void
NotifyEvents(OpenVPNConnection *oc, const wchar_t *status)
{
if (oc->events)
{
oc->events->lpVtbl->SetFieldString(oc->events, (ICredentialProviderCredential*) oc,
STATUS_FIELD_INDEX, status);
}
if (oc->qc)
{
oc->qc->lpVtbl->SetStatusMessage(oc->qc, status);
}
}
static HRESULT WINAPI
ProgressCallback(HWND hwnd, UINT msg, WPARAM wParam, UNUSED LPARAM lParam, LONG_PTR ref_data)
{
assert(ref_data);
OpenVPNConnection *oc = (OpenVPNConnection *) ref_data;
connection_t *c = oc->c;
IQueryContinueWithStatus *qc = oc->qc;
assert(qc);
HRESULT hr = S_FALSE;
WCHAR status[256] = L"";
if (msg == TDN_BUTTON_CLICKED && wParam == IDCANCEL)
{
dmsg(L"wParam=IDCANCEL -- returning S_OK");
hr = S_OK; /* this will cause the progress dialog to close */
}
else if (ISCONNECTED(c) || ISDISCONNECTED(c) || (qc->lpVtbl->QueryContinue(qc) != S_OK))
{
/* this will trigger IDCANCEL */
PostMessageW(hwnd, WM_CLOSE, 0, 0);
dmsg(L"profile = %ls, closing progress dialog", oc->display_name);
hr = S_OK;
}
else if (ISONHOLD(c)) /* Try to connect again */
{
ConnectHelper(c);
}
else if (!ISCONNECTED(c) && msg == TDN_BUTTON_CLICKED && wParam == status_menu_id)
{
dmsg(L"wParam = status_menu_id -- showing status window");
ShowStatusWindow(c, TRUE);
}
else if (!ISCONNECTED(c) && msg == TDN_BUTTON_CLICKED && wParam == IDRETRY)
{
dmsg(L"wParam = IDRETRY -- closing progress dialog for restart");
hr = S_OK;
}
if (msg == TDN_CREATED)
{
dmsg(L"starting progress bar marquee");
PostMessageW(hwnd, TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0);
}
if (msg == TDN_TIMER)
{
GetConnectionStatusText(c, status, _countof(status));
NotifyEvents(oc, status);
SendMessageW(hwnd, TDM_UPDATE_ELEMENT_TEXT, TDE_CONTENT, (LPARAM) status);
dmsg(L"Connection status <%ls>", status);
}
return hr;
}
static HRESULT WINAPI
Connect(ICCPC *this, IQueryContinueWithStatus *qc)
{
OpenVPNConnection *oc = (OpenVPNConnection *) this;
wchar_t status[256] = L"";
dmsg(L"profile: <%ls>", oc->display_name);
oc->qc = qc;
NotifyEvents(oc, L"");
again:
oc->connect_cancelled = FALSE;
SetActiveProfile(oc->c); /* This enables UI dialogs to be shown for c */
ConnectHelper(oc->c);
Sleep(100);
/* if not immediately connected, show a progress dialog with
* service state changes and retry/cancel options. Progress dialog
* returns on error, cancel or when connected.
*/
if (!ISCONNECTED(oc->c) && !ISDISCONNECTED(oc->c))
{
dmsg(L"Runninng progress dialog");
int res = RunProgressDialog(oc->c, ProgressCallback, (LONG_PTR) oc);
dmsg(L"Out of progress dialog with res = %d", res);
if (res == IDRETRY && !ISCONNECTED(oc->c))
{
wcsncpy_s(status, _countof(status), L"Current State: Retrying", _TRUNCATE);
NotifyEvents(oc, status);
DisconnectHelper(oc->c);
goto again;
}
else if (res == IDCANCEL && !ISCONNECTED(oc->c) && !ISDISCONNECTED(oc->c))
{
wcsncpy_s(status, _countof(status), L"Current State: Cancelling", _TRUNCATE);
NotifyEvents(oc, status);
DisconnectHelper(oc->c);
oc->connect_cancelled = TRUE;
}
}
GetConnectionStatusText(oc->c, status, _countof(status));
NotifyEvents(oc, status);
ShowStatusWindow(oc->c, FALSE);
oc->qc = NULL;
SetActiveProfile(NULL);
dmsg (L"Exit with: <%ls>", ISCONNECTED(oc->c) ? L"success" : L"error/cancel");
return ISCONNECTED(oc->c) ? S_OK : E_FAIL;
}
static HRESULT WINAPI
Disconnect(ICCPC *this)
{
OpenVPNConnection *oc = (OpenVPNConnection *) this;
dmsg (L"profile <%ls>", oc->display_name);
NotifyEvents(oc, L"Disconnecting");
DisconnectHelper(oc->c);
NotifyEvents(oc, L""); /* clear status text */
return S_OK;
}
HRESULT WINAPI
OVPNConnection_Initialize(OpenVPNConnection *this, connection_t *conn, const wchar_t *display_name)
{
HRESULT hr = S_OK;
this->c = conn;
this->display_name = display_name;
dmsg(L"profile <%ls>", display_name);
return hr;
}
/* Copy field descriptor -- caller will free using CoTaskMemFree, alloc using compatible allocator */
HRESULT CopyFieldDescriptor(CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *fd_out,
const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *fd_in)
{
HRESULT hr = S_OK;
memcpy(fd_out, fd_in, sizeof(CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR));
/* now copy the label string if present */
if (fd_in->pszLabel)
{
hr = SHStrDupW(fd_in->pszLabel, &fd_out->pszLabel);
}
return hr;
}