Path: blob/main/crypto/krb5/src/windows/leashdll/lshutil.cpp
34914 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* leashdll/lshutil.cpp - text manipulation for principal entry */2/*3* Copyright (C) 2012 by the Massachusetts Institute of Technology.4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9*10* * Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in15* the documentation and/or other materials provided with the16* distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS19* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT20* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS21* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE22* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,23* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES24* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR25* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)26* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,27* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)28* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED29* OF THE POSSIBILITY OF SUCH DAMAGE.30*/3132/*33*34* Leash Principal Edit Control35*36* Edit control customized to enter a principal.37* -Autocomplete functionality using history of previously successful38* authentications39* -Option to automatically convert realm to uppercase as user types40* -Suggest default realm when no matches available from history41*/4243#include <windows.h>44#include <wtypes.h> // LPOLESTR45#include <Shldisp.h> // IAutoComplete46#include <ShlGuid.h> // CLSID_AutoComplete47#include <shobjidl.h> // IAutoCompleteDropDown48#include <objbase.h> // CoCreateInstance49#include <tchar.h>50#include <map>51#include <vector>5253#include "leashwin.h"54#include "leashdll.h"5556#pragma comment(lib, "ole32.lib") // CoCreateInstance5758//59// DynEnumString:60// IEnumString implementation that can be dynamically updated after creation.61//62class DynEnumString : public IEnumString63{64public:65// IUnknown66STDMETHODIMP_(ULONG) AddRef()67{68return ++m_refcount;69}7071STDMETHODIMP_(ULONG) Release()72{73ULONG refcount = --m_refcount;74if (refcount == 0)75delete this;76return refcount;77}7879STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)80{81IUnknown *punk = NULL;82if (riid == IID_IUnknown)83punk = static_cast<IUnknown*>(this);84else if (riid == IID_IEnumString)85punk = static_cast<IEnumString*>(this);86*ppvObject = punk;87if (punk == NULL)88return E_NOINTERFACE;89punk->AddRef();90return S_OK;91}9293// IEnumString94public:95STDMETHODIMP Clone(IEnumString **ppclone)96{97LPTSTR *data = m_aStrings.data();98ULONG count = m_aStrings.size();99*ppclone = new DynEnumString(count, data);100return S_OK;101}102103STDMETHODIMP Next(ULONG count, LPOLESTR *elements, ULONG *pFetched)104{105ULONG fetched = 0;106while (fetched < count) {107if (m_iter == m_aStrings.end())108break;109LPTSTR src = *m_iter++;110// @TODO: add _UNICODE version111DWORD nLengthW = ::MultiByteToWideChar(CP_ACP,1120, &src[0], -1, NULL, 0);113LPOLESTR copy =114(LPOLESTR )::CoTaskMemAlloc(sizeof(OLECHAR) * nLengthW);115if (copy != NULL) {116if (::MultiByteToWideChar(CP_ACP,1170, &src[0], -1, copy, nLengthW)) {118elements[fetched++] = copy;119} else {120// failure...121// TODO: debug spew122::CoTaskMemFree(copy);123copy = NULL;124}125}126}127*pFetched = fetched;128129return fetched == count ? S_OK : S_FALSE;130}131132STDMETHODIMP Reset()133{134m_iter = m_aStrings.begin();135return S_OK;136}137138STDMETHODIMP Skip(ULONG count)139{140for (ULONG i=0; i<count; i++) {141if (m_iter == m_aStrings.end()) {142m_iter = m_aStrings.begin();143break;144}145m_iter++;146}147return S_OK;148}149150// Custom interface151DynEnumString(ULONG count, LPTSTR *strings)152{153m_aStrings.reserve(count + 1);154for (ULONG i = 0; i < count; i++) {155AddString(strings[i]);156}157m_iter = m_aStrings.begin();158m_refcount = 1;159}160161virtual ~DynEnumString()162{163RemoveAll();164}165166void RemoveAll()167{168for (m_iter = m_aStrings.begin();169m_iter != m_aStrings.end();170m_iter++)171delete[] (*m_iter);172m_aStrings.erase(m_aStrings.begin(), m_aStrings.end());173}174175void AddString(LPTSTR str)176{177LPTSTR copy = NULL;178if (str) {179copy = _tcsdup(str);180if (copy)181m_aStrings.push_back(copy);182}183}184185186void RemoveString(LPTSTR str)187{188std::vector<LPTSTR>::const_iterator i;189for (i = m_aStrings.begin(); i != m_aStrings.end(); i++) {190if (_tcscmp(*i, str) == 0) {191delete[] (*i);192m_aStrings.erase(i);193break;194}195}196}197198private:199ULONG m_refcount;200std::vector<LPTSTR>::iterator m_iter;201std::vector<LPTSTR> m_aStrings;202};203204// Registry key to store history of successfully authenticated principals205#define LEASH_REGISTRY_PRINCIPALS_KEY_NAME "Software\\MIT\\Leash\\Principals"206207// Free principal list obtained by getPrincipalList()208static void freePrincipalList(LPTSTR *princs, int count)209{210int i;211if (count) {212for (i = 0; i < count; i++)213if (princs[i])214free(princs[i]);215delete[] princs;216}217}218219// Retrieve history of successfully authenticated principals from registry220static void getPrincipalList(LPTSTR **outPrincs, int *outPrincCount)221{222DWORD count = 0;223DWORD valCount = 0;224DWORD maxLen = 0;225LPTSTR tempValName = NULL;226LPTSTR *princs = NULL;227*outPrincs = NULL;228HKEY hKey = NULL;229unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,230LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0,2310, KEY_READ, 0, &hKey, 0);232if (rc == S_OK) {233// get string count234rc = RegQueryInfoKey(235hKey,236NULL, // __out_opt LPTSTR lpClass,237NULL, // __inout_opt LPDWORD lpcClass,238NULL, // __reserved LPDWORD lpReserved,239NULL, // __out_opt LPDWORD lpcSubKeys,240NULL, // __out_opt LPDWORD lpcMaxSubKeyLen,241NULL, // __out_opt LPDWORD lpcMaxClassLen,242&valCount, //__out_opt LPDWORD lpcValues,243&maxLen, // __out_opt LPDWORD lpcMaxValueNameLen,244NULL, // __out_opt LPDWORD lpcMaxValueLen,245NULL, // __out_opt LPDWORD lpcbSecurityDescriptor,246NULL // __out_opt PFILETIME lpftLastWriteTime247);248}249if (valCount == 0)250goto cleanup;251252princs = new LPTSTR[valCount];253if (princs == NULL)254goto cleanup;255256tempValName = new TCHAR[maxLen+1];257if (tempValName == NULL)258goto cleanup;259260// enumerate values...261for (DWORD iReg = 0; iReg < valCount; iReg++) {262LPTSTR princ = NULL;263DWORD size = maxLen+1;264rc = RegEnumValue(hKey, iReg, tempValName, &size,265NULL, NULL, NULL, NULL);266if (rc == ERROR_SUCCESS)267princ = _tcsdup(tempValName);268if (princ != NULL)269princs[count++] = princ;270}271272*outPrincCount = count;273count = 0;274*outPrincs = princs;275princs = NULL;276277cleanup:278if (tempValName)279delete[] tempValName;280if (princs)281freePrincipalList(princs, count);282if (hKey)283RegCloseKey(hKey);284return;285}286287288// HookWindow289// Utility class to process messages relating to the specified hwnd290class HookWindow291{292public:293typedef std::pair<HWND, HookWindow*> map_elem;294typedef std::map<HWND, HookWindow*> map;295296HookWindow(HWND in_hwnd) : m_hwnd(in_hwnd)297{298// add 'this' to static hash299m_ctrl_id = GetDlgCtrlID(in_hwnd);300m_parent = ::GetParent(m_hwnd);301sm_map.insert(map_elem(m_parent, this));302// grab current window proc and replace with our wndproc303m_parent_wndproc = SetWindowLongPtr(m_parent,304GWLP_WNDPROC,305(ULONG_PTR)(&sWindowProc));306}307308virtual ~HookWindow()309{310// unhook hwnd and restore old wndproc311SetWindowLongPtr(m_parent, GWLP_WNDPROC, m_parent_wndproc);312sm_map.erase(m_parent);313}314315// Process a message316// return 'false' to forward message to parent wndproc317virtual bool WindowProc(UINT msg, WPARAM wParam, LPARAM lParam,318LRESULT *lr) = 0;319320protected:321static LRESULT sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,322LPARAM lParam);323324HWND m_hwnd;325HWND m_parent;326ULONG_PTR m_parent_wndproc;327int m_ctrl_id;328329static map sm_map;330};331332HookWindow::map HookWindow::sm_map;333334LRESULT HookWindow::sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,335LPARAM lParam)336{337LRESULT result;338// hash hwnd to get object and call actual window proc,339// then parent window proc as necessary340HookWindow::map::const_iterator iter = sm_map.find(hwnd);341if (iter != sm_map.end()) {342if (!iter->second->WindowProc(uMsg, wParam, lParam, &result))343result = CallWindowProc((WNDPROC )iter->second->m_parent_wndproc,344hwnd, uMsg, wParam, lParam);345} else {346result = ::DefWindowProc(hwnd, uMsg, wParam, lParam);347}348return result;349}350351//352class PrincipalEditControl : public HookWindow353{354public:355PrincipalEditControl(HWND hwnd, bool bUpperCaseRealm) : HookWindow(hwnd)356,m_ignore_change(0)357,m_bUpperCaseRealm(bUpperCaseRealm)358,m_defaultRealm(NULL)359,m_ctx(0)360,m_enumString(NULL)361,m_acdd(NULL)362,m_princStr(NULL)363{364pkrb5_init_context(&m_ctx);365GetDefaultRealm();366InitAutocomplete();367}368369~PrincipalEditControl()370{371DestroyAutocomplete();372if (m_princStr)373delete[] m_princStr;374if (m_ctx && m_defaultRealm)375pkrb5_free_default_realm(m_ctx, m_defaultRealm);376if (m_ctx)377pkrb5_free_context(m_ctx);378}379380void ClearHistory()381{382if (m_enumString != NULL)383m_enumString->RemoveAll();384if (m_acdd != NULL)385m_acdd->ResetEnumerator();386if (m_princStr != NULL) {387delete[] m_princStr;388m_princStr = NULL;389}390}391392protected:393// Convert str to upper case394// This should be more-or-less _UNICODE-agnostic395static bool StrToUpper(LPTSTR str)396{397bool bChanged = false;398int c;399if (str != NULL) {400while ((c = *str) != NULL) {401if (__isascii(c) && islower(c)) {402bChanged = true;403*str = _toupper(c);404}405str++;406}407}408return bChanged;409}410411void GetDefaultRealm()412{413// @TODO: _UNICODE support here414if ((m_defaultRealm == NULL) && m_ctx) {415pkrb5_get_default_realm(m_ctx, &m_defaultRealm);416}417}418419// Append default realm to user and add to the autocomplete enum string420void SuggestDefaultRealm(LPTSTR user)421{422if (m_defaultRealm == NULL)423return;424425int princ_len = _tcslen(user) + _tcslen(m_defaultRealm) + 1;426LPTSTR princStr = new TCHAR[princ_len];427if (princStr) {428_sntprintf_s(princStr, princ_len, _TRUNCATE, "%s%s", user,429m_defaultRealm);430if (m_princStr != NULL && (_tcscmp(princStr, m_princStr) == 0)) {431// this string is already added, ok to just bail432delete[] princStr;433} else {434if (m_princStr != NULL) {435// get rid of the old suggestion436m_enumString->RemoveString(m_princStr);437delete[] m_princStr;438}439// add the new one440m_enumString->AddString(princStr);441if (m_acdd != NULL)442m_acdd->ResetEnumerator();443m_princStr = princStr;444}445}446}447448bool AdjustRealmCase(LPTSTR princStr, LPTSTR realmStr)449{450bool bChanged = StrToUpper(realmStr);451if (bChanged) {452DWORD selStart, selEnd;453::SendMessage(m_hwnd, EM_GETSEL, (WPARAM)&selStart,454(LPARAM)&selEnd);455::SetWindowText(m_hwnd, princStr);456::SendMessage(m_hwnd, EM_SETSEL, (WPARAM)selStart, (LPARAM)selEnd);457}458return bChanged;459}460461bool ProcessText()462{463bool bChanged = false;464int text_len = GetWindowTextLength(m_hwnd);465if (text_len > 0) {466LPTSTR str = new TCHAR [++text_len];467if (str != NULL) {468GetWindowText(m_hwnd, str, text_len);469LPTSTR realmStr = strchr(str, '@');470if (realmStr != NULL) {471++realmStr;472if (*realmStr == 0) {473SuggestDefaultRealm(str);474}475else if (m_bUpperCaseRealm) {476AdjustRealmCase(str, realmStr);477bChanged = true;478}479}480delete[] str;481}482}483return bChanged;484}485486virtual bool WindowProc(UINT msg, WPARAM wp, LPARAM lp, LRESULT *lr)487{488bool bChanged = false;489switch (msg) {490case WM_COMMAND:491if ((LOWORD(wp)==m_ctrl_id) &&492(HIWORD(wp)==EN_CHANGE)) {493if ((!m_ignore_change++) && ProcessText()) {494bChanged = true;495*lr = 0;496}497m_ignore_change--;498}499default:500break;501}502return bChanged;503}504505void InitAutocomplete()506{507CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);508509// read strings from registry510LPTSTR *princs = NULL;511int count = 0;512getPrincipalList(&princs, &count);513514// Create our custom IEnumString implementation515HRESULT hRes;516DynEnumString *pEnumString = new DynEnumString(count, princs);517if (princs)518freePrincipalList(princs, count);519520m_enumString = pEnumString;521522// Create and initialize IAutoComplete object using IEnumString523IAutoComplete *pac = NULL;524hRes = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,525IID_PPV_ARGS(&pac));526if (pac != NULL) {527pac->Init(m_hwnd, pEnumString, NULL, NULL);528529IAutoCompleteDropDown* pacdd = NULL;530hRes = pac->QueryInterface(IID_IAutoCompleteDropDown, (LPVOID*)&pacdd);531pac->Release();532m_acdd = pacdd;533}534}535536void DestroyAutocomplete()537{538if (m_acdd != NULL)539m_acdd->Release();540if (m_enumString != NULL)541m_enumString->Release();542}543544int m_ignore_change;545bool m_bUpperCaseRealm;546LPTSTR m_defaultRealm;547LPTSTR m_princStr;548krb5_context m_ctx;549DynEnumString *m_enumString;550IAutoCompleteDropDown *m_acdd;551};552553554555extern "C" void Leash_pec_add_principal(char *principal)556{557// write princ to registry558HKEY hKey;559unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,560LEASH_REGISTRY_PRINCIPALS_KEY_NAME,5610, 0, 0, KEY_WRITE, 0, &hKey, 0);562if (rc) {563// TODO: log failure564return;565}566rc = RegSetValueEx(hKey, principal, 0, REG_NONE, NULL, 0);567if (rc) {568// TODO: log failure569}570if (hKey)571RegCloseKey(hKey);572}573574extern "C" void Leash_pec_clear_history(void *pec)575{576// clear princs from registry577RegDeleteKey(HKEY_CURRENT_USER,578LEASH_REGISTRY_PRINCIPALS_KEY_NAME);579// ...and from the specified widget580static_cast<PrincipalEditControl *>(pec)->ClearHistory();581}582583584extern "C" void *Leash_pec_create(HWND hEdit)585{586return new PrincipalEditControl(587hEdit,588Leash_get_default_uppercaserealm() ? true : false);589}590591extern "C" void Leash_pec_destroy(void *pec)592{593if (pec != NULL)594delete ((PrincipalEditControl *)pec);595}596597598