/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* prototype/prototype.c - <<< One-line description of file >>> */ /* leashdll/lshutil.cpp - text maniuplation for principal entry */ /* * Copyright (C) 2012 by the Massachusetts Institute of Technology. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * * Leash Principal Edit Control * * Edit control customized to enter a principal. * -Autocomplete functionality using history of previously successful * authentications * -Option to automatically convert realm to uppercase as user types * -Suggest default realm when no matches available from history */ #include #include // LPOLESTR #include // IAutoComplete #include // CLSID_AutoComplete #include // IAutoCompleteDropDown #include // CoCreateInstance #include #include #include #include "leashwin.h" #include "leashdll.h" #pragma comment(lib, "ole32.lib") // CoCreateInstance // // DynEnumString: // IEnumString implementation that can be dynamically updated after creation. // class DynEnumString : public IEnumString { public: // IUnknown STDMETHODIMP_(ULONG) AddRef() { return ++m_refcount; } STDMETHODIMP_(ULONG) Release() { ULONG refcount = --m_refcount; if (refcount == 0) delete this; return refcount; } STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) { IUnknown *punk = NULL; if (riid == IID_IUnknown) punk = static_cast(this); else if (riid == IID_IEnumString) punk = static_cast(this); *ppvObject = punk; if (punk == NULL) return E_NOINTERFACE; punk->AddRef(); return S_OK; } // IEnumString public: STDMETHODIMP Clone(IEnumString **ppclone) { LPTSTR *data = m_aStrings.data(); ULONG count = m_aStrings.size(); *ppclone = new DynEnumString(count, data); return S_OK; } STDMETHODIMP Next(ULONG count, LPOLESTR *elements, ULONG *pFetched) { ULONG fetched = 0; while (fetched < count) { if (m_iter == m_aStrings.end()) break; LPTSTR src = *m_iter++; // @TODO: add _UNICODE version DWORD nLengthW = ::MultiByteToWideChar(CP_ACP, 0, &src[0], -1, NULL, 0); LPOLESTR copy = (LPOLESTR )::CoTaskMemAlloc(sizeof(OLECHAR) * nLengthW); if (copy != NULL) { if (::MultiByteToWideChar(CP_ACP, 0, &src[0], -1, copy, nLengthW)) { elements[fetched++] = copy; } else { // failure... // TODO: debug spew ::CoTaskMemFree(copy); copy = NULL; } } } *pFetched = fetched; return fetched == count ? S_OK : S_FALSE; } STDMETHODIMP Reset() { m_iter = m_aStrings.begin(); return S_OK; } STDMETHODIMP Skip(ULONG count) { for (ULONG i=0; i::const_iterator i; for (i = m_aStrings.begin(); i != m_aStrings.end(); i++) { if (_tcscmp(*i, str) == 0) { delete[] (*i); m_aStrings.erase(i); break; } } } private: ULONG m_refcount; std::vector::iterator m_iter; std::vector m_aStrings; }; // Registry key to store history of successfully authenticated principals #define LEASH_REGISTRY_PRINCIPALS_KEY_NAME "Software\\MIT\\Leash\\Principals" // Free principal list obtained by getPrincipalList() static void freePrincipalList(LPTSTR *princs, int count) { int i; if (count) { for (i = 0; i < count; i++) if (princs[i]) free(princs[i]); delete[] princs; } } // Retrieve history of successfully authenticated principals from registry static void getPrincipalList(LPTSTR **outPrincs, int *outPrincCount) { DWORD count = 0; DWORD valCount = 0; DWORD maxLen = 0; LPTSTR tempValName = NULL; LPTSTR *princs = NULL; *outPrincs = NULL; HKEY hKey = NULL; unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER, LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0, 0, KEY_READ, 0, &hKey, 0); if (rc == S_OK) { // get string count rc = RegQueryInfoKey( hKey, NULL, // __out_opt LPTSTR lpClass, NULL, // __inout_opt LPDWORD lpcClass, NULL, // __reserved LPDWORD lpReserved, NULL, // __out_opt LPDWORD lpcSubKeys, NULL, // __out_opt LPDWORD lpcMaxSubKeyLen, NULL, // __out_opt LPDWORD lpcMaxClassLen, &valCount, //__out_opt LPDWORD lpcValues, &maxLen, // __out_opt LPDWORD lpcMaxValueNameLen, NULL, // __out_opt LPDWORD lpcMaxValueLen, NULL, // __out_opt LPDWORD lpcbSecurityDescriptor, NULL // __out_opt PFILETIME lpftLastWriteTime ); } if (valCount == 0) goto cleanup; princs = new LPTSTR[valCount]; if (princs == NULL) goto cleanup; tempValName = new TCHAR[maxLen+1]; if (tempValName == NULL) goto cleanup; // enumerate values... for (DWORD iReg = 0; iReg < valCount; iReg++) { LPTSTR princ = NULL; DWORD size = maxLen+1; rc = RegEnumValue(hKey, iReg, tempValName, &size, NULL, NULL, NULL, NULL); if (rc == ERROR_SUCCESS) princ = _tcsdup(tempValName); if (princ != NULL) princs[count++] = princ; } *outPrincCount = count; count = 0; *outPrincs = princs; princs = NULL; cleanup: if (tempValName) delete[] tempValName; if (princs) freePrincipalList(princs, count); if (hKey) RegCloseKey(hKey); return; } // HookWindow // Utility class to process messages relating to the specified hwnd class HookWindow { public: typedef std::pair map_elem; typedef std::map map; HookWindow(HWND in_hwnd) : m_hwnd(in_hwnd) { // add 'this' to static hash m_ctrl_id = GetDlgCtrlID(in_hwnd); m_parent = ::GetParent(m_hwnd); sm_map.insert(map_elem(m_parent, this)); // grab current window proc and replace with our wndproc m_parent_wndproc = SetWindowLongPtr(m_parent, GWLP_WNDPROC, (ULONG_PTR)(&sWindowProc)); } virtual ~HookWindow() { // unhook hwnd and restore old wndproc SetWindowLongPtr(m_parent, GWLP_WNDPROC, m_parent_wndproc); sm_map.erase(m_parent); } // Process a message // return 'false' to forward message to parent wndproc virtual bool WindowProc(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *lr) = 0; protected: static LRESULT sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); HWND m_hwnd; HWND m_parent; ULONG_PTR m_parent_wndproc; int m_ctrl_id; static map sm_map; }; HookWindow::map HookWindow::sm_map; LRESULT HookWindow::sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT result; // hash hwnd to get object and call actual window proc, // then parent window proc as necessary HookWindow::map::const_iterator iter = sm_map.find(hwnd); if (iter != sm_map.end()) { if (!iter->second->WindowProc(uMsg, wParam, lParam, &result)) result = CallWindowProc((WNDPROC )iter->second->m_parent_wndproc, hwnd, uMsg, wParam, lParam); } else { result = ::DefWindowProc(hwnd, uMsg, wParam, lParam); } return result; } // class PrincipalEditControl : public HookWindow { public: PrincipalEditControl(HWND hwnd, bool bUpperCaseRealm) : HookWindow(hwnd) ,m_ignore_change(0) ,m_bUpperCaseRealm(bUpperCaseRealm) ,m_defaultRealm(NULL) ,m_ctx(0) ,m_enumString(NULL) ,m_acdd(NULL) ,m_princStr(NULL) { pkrb5_init_context(&m_ctx); GetDefaultRealm(); InitAutocomplete(); } ~PrincipalEditControl() { DestroyAutocomplete(); if (m_princStr) delete[] m_princStr; if (m_ctx && m_defaultRealm) pkrb5_free_default_realm(m_ctx, m_defaultRealm); if (m_ctx) pkrb5_free_context(m_ctx); } void ClearHistory() { if (m_enumString != NULL) m_enumString->RemoveAll(); if (m_acdd != NULL) m_acdd->ResetEnumerator(); if (m_princStr != NULL) { delete[] m_princStr; m_princStr = NULL; } } protected: // Convert str to upper case // This should be more-or-less _UNICODE-agnostic static bool StrToUpper(LPTSTR str) { bool bChanged = false; int c; if (str != NULL) { while ((c = *str) != NULL) { if (__isascii(c) && islower(c)) { bChanged = true; *str = _toupper(c); } str++; } } return bChanged; } void GetDefaultRealm() { // @TODO: _UNICODE support here if ((m_defaultRealm == NULL) && m_ctx) { pkrb5_get_default_realm(m_ctx, &m_defaultRealm); } } // Append default realm to user and add to the autocomplete enum string void SuggestDefaultRealm(LPTSTR user) { if (m_defaultRealm == NULL) return; int princ_len = _tcslen(user) + _tcslen(m_defaultRealm) + 1; LPTSTR princStr = new TCHAR[princ_len]; if (princStr) { _sntprintf_s(princStr, princ_len, _TRUNCATE, "%s%s", user, m_defaultRealm); if (m_princStr != NULL && (_tcscmp(princStr, m_princStr) == 0)) { // this string is already added, ok to just bail delete[] princStr; } else { if (m_princStr != NULL) { // get rid of the old suggestion m_enumString->RemoveString(m_princStr); delete[] m_princStr; } // add the new one m_enumString->AddString(princStr); if (m_acdd != NULL) m_acdd->ResetEnumerator(); m_princStr = princStr; } } } bool AdjustRealmCase(LPTSTR princStr, LPTSTR realmStr) { bool bChanged = StrToUpper(realmStr); if (bChanged) { DWORD selStart, selEnd; ::SendMessage(m_hwnd, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); ::SetWindowText(m_hwnd, princStr); ::SendMessage(m_hwnd, EM_SETSEL, (WPARAM)selStart, (LPARAM)selEnd); } return bChanged; } bool ProcessText() { bool bChanged = false; int text_len = GetWindowTextLength(m_hwnd); if (text_len > 0) { LPTSTR str = new TCHAR [++text_len]; if (str != NULL) { GetWindowText(m_hwnd, str, text_len); LPTSTR realmStr = strchr(str, '@'); if (realmStr != NULL) { ++realmStr; if (*realmStr == 0) { SuggestDefaultRealm(str); } else if (m_bUpperCaseRealm) { AdjustRealmCase(str, realmStr); bChanged = true; } } delete[] str; } } return bChanged; } virtual bool WindowProc(UINT msg, WPARAM wp, LPARAM lp, LRESULT *lr) { bool bChanged = false; switch (msg) { case WM_COMMAND: if ((LOWORD(wp)==m_ctrl_id) && (HIWORD(wp)==EN_CHANGE)) { if ((!m_ignore_change++) && ProcessText()) { bChanged = true; *lr = 0; } m_ignore_change--; } default: break; } return bChanged; } void InitAutocomplete() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // read strings from registry LPTSTR *princs = NULL; int count = 0; getPrincipalList(&princs, &count); // Create our custom IEnumString implementation HRESULT hRes; DynEnumString *pEnumString = new DynEnumString(count, princs); if (princs) freePrincipalList(princs, count); m_enumString = pEnumString; // Create and initialize IAutoComplete object using IEnumString IAutoComplete *pac = NULL; hRes = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pac)); if (pac != NULL) { pac->Init(m_hwnd, pEnumString, NULL, NULL); IAutoCompleteDropDown* pacdd = NULL; hRes = pac->QueryInterface(IID_IAutoCompleteDropDown, (LPVOID*)&pacdd); pac->Release(); // @TODO: auto-suggest; other advanced options? #if 0 IAutoComplete2 *pac2; if (SUCCEEDED(pac->QueryInterface(IID_IAutoComplete2, (LPVOID*)&pac2))) { pac2->SetOptions(ACO_AUTOSUGGEST); pac2->Release(); } #endif m_acdd = pacdd; } } void DestroyAutocomplete() { if (m_acdd != NULL) m_acdd->Release(); if (m_enumString != NULL) m_enumString->Release(); } int m_ignore_change; bool m_bUpperCaseRealm; LPTSTR m_defaultRealm; LPTSTR m_princStr; krb5_context m_ctx; DynEnumString *m_enumString; IAutoCompleteDropDown *m_acdd; }; extern "C" void Leash_pec_add_principal(char *principal) { // write princ to registry HKEY hKey; unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER, LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0, 0, KEY_WRITE, 0, &hKey, 0); if (rc) { // TODO: log failure return; } rc = RegSetValueEx(hKey, principal, 0, REG_NONE, NULL, 0); if (rc) { // TODO: log failure } if (hKey) RegCloseKey(hKey); } extern "C" void Leash_pec_clear_history(void *pec) { // clear princs from registry RegDeleteKey(HKEY_CURRENT_USER, LEASH_REGISTRY_PRINCIPALS_KEY_NAME); // ...and from the specified widget static_cast(pec)->ClearHistory(); } extern "C" void *Leash_pec_create(HWND hEdit) { return new PrincipalEditControl( hEdit, Leash_get_default_uppercaserealm() ? true : false); } extern "C" void Leash_pec_destroy(void *pec) { if (pec != NULL) delete ((PrincipalEditControl *)pec); }