1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* prototype/prototype.c - <<< One-line description of file >>> */
3 /* leashdll/lshutil.cpp - text maniuplation for principal entry */
5 * Copyright (C) 2012 by the Massachusetts Institute of Technology.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31 * OF THE POSSIBILITY OF SUCH DAMAGE.
36 * Leash Principal Edit Control
38 * Edit control customized to enter a principal.
39 * -Autocomplete functionality using history of previously successful
41 * -Option to automatically convert realm to uppercase as user types
42 * -Suggest default realm when no matches available from history
46 #include <wtypes.h> // LPOLESTR
47 #include <Shldisp.h> // IAutoComplete
48 #include <ShlGuid.h> // CLSID_AutoComplete
49 #include <shobjidl.h> // IAutoCompleteDropDown
50 #include <objbase.h> // CoCreateInstance
58 #pragma comment(lib, "ole32.lib") // CoCreateInstance
62 // IEnumString implementation that can be dynamically updated after creation.
64 class DynEnumString : public IEnumString
68 STDMETHODIMP_(ULONG) AddRef()
73 STDMETHODIMP_(ULONG) Release()
75 ULONG refcount = --m_refcount;
81 STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
83 IUnknown *punk = NULL;
84 if (riid == IID_IUnknown)
85 punk = static_cast<IUnknown*>(this);
86 else if (riid == IID_IEnumString)
87 punk = static_cast<IEnumString*>(this);
97 STDMETHODIMP Clone(IEnumString **ppclone)
99 LPTSTR *data = m_aStrings.data();
100 ULONG count = m_aStrings.size();
101 *ppclone = new DynEnumString(count, data);
105 STDMETHODIMP Next(ULONG count, LPOLESTR *elements, ULONG *pFetched)
108 while (fetched < count) {
109 if (m_iter == m_aStrings.end())
111 LPTSTR src = *m_iter++;
112 // @TODO: add _UNICODE version
113 DWORD nLengthW = ::MultiByteToWideChar(CP_ACP,
114 0, &src[0], -1, NULL, 0);
116 (LPOLESTR )::CoTaskMemAlloc(sizeof(OLECHAR) * nLengthW);
118 if (::MultiByteToWideChar(CP_ACP,
119 0, &src[0], -1, copy, nLengthW)) {
120 elements[fetched++] = copy;
124 ::CoTaskMemFree(copy);
131 return fetched == count ? S_OK : S_FALSE;
136 m_iter = m_aStrings.begin();
140 STDMETHODIMP Skip(ULONG count)
142 for (ULONG i=0; i<count; i++) {
143 if (m_iter == m_aStrings.end()) {
144 m_iter = m_aStrings.begin();
153 DynEnumString(ULONG count, LPTSTR *strings)
155 m_aStrings.reserve(count + 1);
156 for (ULONG i = 0; i < count; i++) {
157 AddString(strings[i]);
159 m_iter = m_aStrings.begin();
163 virtual ~DynEnumString()
170 for (m_iter = m_aStrings.begin();
171 m_iter != m_aStrings.end();
174 m_aStrings.erase(m_aStrings.begin(), m_aStrings.end());
177 void AddString(LPTSTR str)
183 m_aStrings.push_back(copy);
188 void RemoveString(LPTSTR str)
190 std::vector<LPTSTR>::const_iterator i;
191 for (i = m_aStrings.begin(); i != m_aStrings.end(); i++) {
192 if (_tcscmp(*i, str) == 0) {
202 std::vector<LPTSTR>::iterator m_iter;
203 std::vector<LPTSTR> m_aStrings;
206 // Registry key to store history of successfully authenticated principals
207 #define LEASH_REGISTRY_PRINCIPALS_KEY_NAME "Software\\MIT\\Leash\\Principals"
209 // Free principal list obtained by getPrincipalList()
210 static void freePrincipalList(LPTSTR *princs, int count)
214 for (i = 0; i < count; i++)
221 // Retrieve history of successfully authenticated principals from registry
222 static void getPrincipalList(LPTSTR **outPrincs, int *outPrincCount)
227 LPTSTR tempValName = NULL;
228 LPTSTR *princs = NULL;
231 unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,
232 LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0,
233 0, KEY_READ, 0, &hKey, 0);
236 rc = RegQueryInfoKey(
238 NULL, // __out_opt LPTSTR lpClass,
239 NULL, // __inout_opt LPDWORD lpcClass,
240 NULL, // __reserved LPDWORD lpReserved,
241 NULL, // __out_opt LPDWORD lpcSubKeys,
242 NULL, // __out_opt LPDWORD lpcMaxSubKeyLen,
243 NULL, // __out_opt LPDWORD lpcMaxClassLen,
244 &valCount, //__out_opt LPDWORD lpcValues,
245 &maxLen, // __out_opt LPDWORD lpcMaxValueNameLen,
246 NULL, // __out_opt LPDWORD lpcMaxValueLen,
247 NULL, // __out_opt LPDWORD lpcbSecurityDescriptor,
248 NULL // __out_opt PFILETIME lpftLastWriteTime
254 princs = new LPTSTR[valCount];
258 tempValName = new TCHAR[maxLen+1];
259 if (tempValName == NULL)
262 // enumerate values...
263 for (DWORD iReg = 0; iReg < valCount; iReg++) {
265 DWORD size = maxLen+1;
266 rc = RegEnumValue(hKey, iReg, tempValName, &size,
267 NULL, NULL, NULL, NULL);
268 if (rc == ERROR_SUCCESS)
269 princ = _tcsdup(tempValName);
271 princs[count++] = princ;
274 *outPrincCount = count;
281 delete[] tempValName;
283 freePrincipalList(princs, count);
291 // Utility class to process messages relating to the specified hwnd
295 typedef std::pair<HWND, HookWindow*> map_elem;
296 typedef std::map<HWND, HookWindow*> map;
298 HookWindow(HWND in_hwnd) : m_hwnd(in_hwnd)
300 // add 'this' to static hash
301 m_ctrl_id = GetDlgCtrlID(in_hwnd);
302 m_parent = ::GetParent(m_hwnd);
303 sm_map.insert(map_elem(m_parent, this));
304 // grab current window proc and replace with our wndproc
305 m_parent_wndproc = SetWindowLongPtr(m_parent,
307 (ULONG_PTR)(&sWindowProc));
310 virtual ~HookWindow()
312 // unhook hwnd and restore old wndproc
313 SetWindowLongPtr(m_parent, GWLP_WNDPROC, m_parent_wndproc);
314 sm_map.erase(m_parent);
318 // return 'false' to forward message to parent wndproc
319 virtual bool WindowProc(UINT msg, WPARAM wParam, LPARAM lParam,
323 static LRESULT sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
328 ULONG_PTR m_parent_wndproc;
334 HookWindow::map HookWindow::sm_map;
336 LRESULT HookWindow::sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
340 // hash hwnd to get object and call actual window proc,
341 // then parent window proc as necessary
342 HookWindow::map::const_iterator iter = sm_map.find(hwnd);
343 if (iter != sm_map.end()) {
344 if (!iter->second->WindowProc(uMsg, wParam, lParam, &result))
345 result = CallWindowProc((WNDPROC )iter->second->m_parent_wndproc,
346 hwnd, uMsg, wParam, lParam);
348 result = ::DefWindowProc(hwnd, uMsg, wParam, lParam);
354 class PrincipalEditControl : public HookWindow
357 PrincipalEditControl(HWND hwnd, bool bUpperCaseRealm) : HookWindow(hwnd)
359 ,m_bUpperCaseRealm(bUpperCaseRealm)
360 ,m_defaultRealm(NULL)
366 pkrb5_init_context(&m_ctx);
371 ~PrincipalEditControl()
373 DestroyAutocomplete();
376 if (m_ctx && m_defaultRealm)
377 pkrb5_free_default_realm(m_ctx, m_defaultRealm);
379 pkrb5_free_context(m_ctx);
384 if (m_enumString != NULL)
385 m_enumString->RemoveAll();
387 m_acdd->ResetEnumerator();
388 if (m_princStr != NULL) {
395 // Convert str to upper case
396 // This should be more-or-less _UNICODE-agnostic
397 static bool StrToUpper(LPTSTR str)
399 bool bChanged = false;
402 while ((c = *str) != NULL) {
403 if (__isascii(c) && islower(c)) {
413 void GetDefaultRealm()
415 // @TODO: _UNICODE support here
416 if ((m_defaultRealm == NULL) && m_ctx) {
417 pkrb5_get_default_realm(m_ctx, &m_defaultRealm);
421 // Append default realm to user and add to the autocomplete enum string
422 void SuggestDefaultRealm(LPTSTR user)
424 if (m_defaultRealm == NULL)
427 int princ_len = _tcslen(user) + _tcslen(m_defaultRealm) + 1;
428 LPTSTR princStr = new TCHAR[princ_len];
430 _sntprintf_s(princStr, princ_len, _TRUNCATE, "%s%s", user,
432 if (m_princStr != NULL && (_tcscmp(princStr, m_princStr) == 0)) {
433 // this string is already added, ok to just bail
436 if (m_princStr != NULL) {
437 // get rid of the old suggestion
438 m_enumString->RemoveString(m_princStr);
442 m_enumString->AddString(princStr);
444 m_acdd->ResetEnumerator();
445 m_princStr = princStr;
450 bool AdjustRealmCase(LPTSTR princStr, LPTSTR realmStr)
452 bool bChanged = StrToUpper(realmStr);
454 DWORD selStart, selEnd;
455 ::SendMessage(m_hwnd, EM_GETSEL, (WPARAM)&selStart,
457 ::SetWindowText(m_hwnd, princStr);
458 ::SendMessage(m_hwnd, EM_SETSEL, (WPARAM)selStart, (LPARAM)selEnd);
465 bool bChanged = false;
466 int text_len = GetWindowTextLength(m_hwnd);
468 LPTSTR str = new TCHAR [++text_len];
470 GetWindowText(m_hwnd, str, text_len);
471 LPTSTR realmStr = strchr(str, '@');
472 if (realmStr != NULL) {
474 if (*realmStr == 0) {
475 SuggestDefaultRealm(str);
477 else if (m_bUpperCaseRealm) {
478 AdjustRealmCase(str, realmStr);
488 virtual bool WindowProc(UINT msg, WPARAM wp, LPARAM lp, LRESULT *lr)
490 bool bChanged = false;
493 if ((LOWORD(wp)==m_ctrl_id) &&
494 (HIWORD(wp)==EN_CHANGE)) {
495 if ((!m_ignore_change++) && ProcessText()) {
507 void InitAutocomplete()
509 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
511 // read strings from registry
512 LPTSTR *princs = NULL;
514 getPrincipalList(&princs, &count);
516 // Create our custom IEnumString implementation
518 DynEnumString *pEnumString = new DynEnumString(count, princs);
520 freePrincipalList(princs, count);
522 m_enumString = pEnumString;
524 // Create and initialize IAutoComplete object using IEnumString
525 IAutoComplete *pac = NULL;
526 hRes = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
529 pac->Init(m_hwnd, pEnumString, NULL, NULL);
531 IAutoCompleteDropDown* pacdd = NULL;
532 hRes = pac->QueryInterface(IID_IAutoCompleteDropDown, (LPVOID*)&pacdd);
535 // @TODO: auto-suggest; other advanced options?
537 IAutoComplete2 *pac2;
539 if (SUCCEEDED(pac->QueryInterface(IID_IAutoComplete2,
541 pac2->SetOptions(ACO_AUTOSUGGEST);
549 void DestroyAutocomplete()
553 if (m_enumString != NULL)
554 m_enumString->Release();
558 bool m_bUpperCaseRealm;
559 LPTSTR m_defaultRealm;
562 DynEnumString *m_enumString;
563 IAutoCompleteDropDown *m_acdd;
568 extern "C" void Leash_pec_add_principal(char *principal)
570 // write princ to registry
572 unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,
573 LEASH_REGISTRY_PRINCIPALS_KEY_NAME,
574 0, 0, 0, KEY_WRITE, 0, &hKey, 0);
579 rc = RegSetValueEx(hKey, principal, 0, REG_NONE, NULL, 0);
587 extern "C" void Leash_pec_clear_history(void *pec)
589 // clear princs from registry
590 RegDeleteKey(HKEY_CURRENT_USER,
591 LEASH_REGISTRY_PRINCIPALS_KEY_NAME);
592 // ...and from the specified widget
593 static_cast<PrincipalEditControl *>(pec)->ClearHistory();
597 extern "C" void *Leash_pec_create(HWND hEdit)
599 return new PrincipalEditControl(
601 Leash_get_default_uppercaserealm() ? true : false);
604 extern "C" void Leash_pec_destroy(void *pec)
607 delete ((PrincipalEditControl *)pec);