1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* leashdll/lshutil.cpp - text manipulation for principal entry */ 3 /* 4 * Copyright (C) 2012 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * 35 * Leash Principal Edit Control 36 * 37 * Edit control customized to enter a principal. 38 * -Autocomplete functionality using history of previously successful 39 * authentications 40 * -Option to automatically convert realm to uppercase as user types 41 * -Suggest default realm when no matches available from history 42 */ 43 44 #include <windows.h> 45 #include <wtypes.h> // LPOLESTR 46 #include <Shldisp.h> // IAutoComplete 47 #include <ShlGuid.h> // CLSID_AutoComplete 48 #include <shobjidl.h> // IAutoCompleteDropDown 49 #include <objbase.h> // CoCreateInstance 50 #include <tchar.h> 51 #include <map> 52 #include <vector> 53 54 #include "leashwin.h" 55 #include "leashdll.h" 56 57 #pragma comment(lib, "ole32.lib") // CoCreateInstance 58 59 // 60 // DynEnumString: 61 // IEnumString implementation that can be dynamically updated after creation. 62 // 63 class DynEnumString : public IEnumString 64 { 65 public: 66 // IUnknown 67 STDMETHODIMP_(ULONG) AddRef() 68 { 69 return ++m_refcount; 70 } 71 72 STDMETHODIMP_(ULONG) Release() 73 { 74 ULONG refcount = --m_refcount; 75 if (refcount == 0) 76 delete this; 77 return refcount; 78 } 79 80 STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) 81 { 82 IUnknown *punk = NULL; 83 if (riid == IID_IUnknown) 84 punk = static_cast<IUnknown*>(this); 85 else if (riid == IID_IEnumString) 86 punk = static_cast<IEnumString*>(this); 87 *ppvObject = punk; 88 if (punk == NULL) 89 return E_NOINTERFACE; 90 punk->AddRef(); 91 return S_OK; 92 } 93 94 // IEnumString 95 public: 96 STDMETHODIMP Clone(IEnumString **ppclone) 97 { 98 LPTSTR *data = m_aStrings.data(); 99 ULONG count = m_aStrings.size(); 100 *ppclone = new DynEnumString(count, data); 101 return S_OK; 102 } 103 104 STDMETHODIMP Next(ULONG count, LPOLESTR *elements, ULONG *pFetched) 105 { 106 ULONG fetched = 0; 107 while (fetched < count) { 108 if (m_iter == m_aStrings.end()) 109 break; 110 LPTSTR src = *m_iter++; 111 // @TODO: add _UNICODE version 112 DWORD nLengthW = ::MultiByteToWideChar(CP_ACP, 113 0, &src[0], -1, NULL, 0); 114 LPOLESTR copy = 115 (LPOLESTR )::CoTaskMemAlloc(sizeof(OLECHAR) * nLengthW); 116 if (copy != NULL) { 117 if (::MultiByteToWideChar(CP_ACP, 118 0, &src[0], -1, copy, nLengthW)) { 119 elements[fetched++] = copy; 120 } else { 121 // failure... 122 // TODO: debug spew 123 ::CoTaskMemFree(copy); 124 copy = NULL; 125 } 126 } 127 } 128 *pFetched = fetched; 129 130 return fetched == count ? S_OK : S_FALSE; 131 } 132 133 STDMETHODIMP Reset() 134 { 135 m_iter = m_aStrings.begin(); 136 return S_OK; 137 } 138 139 STDMETHODIMP Skip(ULONG count) 140 { 141 for (ULONG i=0; i<count; i++) { 142 if (m_iter == m_aStrings.end()) { 143 m_iter = m_aStrings.begin(); 144 break; 145 } 146 m_iter++; 147 } 148 return S_OK; 149 } 150 151 // Custom interface 152 DynEnumString(ULONG count, LPTSTR *strings) 153 { 154 m_aStrings.reserve(count + 1); 155 for (ULONG i = 0; i < count; i++) { 156 AddString(strings[i]); 157 } 158 m_iter = m_aStrings.begin(); 159 m_refcount = 1; 160 } 161 162 virtual ~DynEnumString() 163 { 164 RemoveAll(); 165 } 166 167 void RemoveAll() 168 { 169 for (m_iter = m_aStrings.begin(); 170 m_iter != m_aStrings.end(); 171 m_iter++) 172 delete[] (*m_iter); 173 m_aStrings.erase(m_aStrings.begin(), m_aStrings.end()); 174 } 175 176 void AddString(LPTSTR str) 177 { 178 LPTSTR copy = NULL; 179 if (str) { 180 copy = _tcsdup(str); 181 if (copy) 182 m_aStrings.push_back(copy); 183 } 184 } 185 186 187 void RemoveString(LPTSTR str) 188 { 189 std::vector<LPTSTR>::const_iterator i; 190 for (i = m_aStrings.begin(); i != m_aStrings.end(); i++) { 191 if (_tcscmp(*i, str) == 0) { 192 delete[] (*i); 193 m_aStrings.erase(i); 194 break; 195 } 196 } 197 } 198 199 private: 200 ULONG m_refcount; 201 std::vector<LPTSTR>::iterator m_iter; 202 std::vector<LPTSTR> m_aStrings; 203 }; 204 205 // Registry key to store history of successfully authenticated principals 206 #define LEASH_REGISTRY_PRINCIPALS_KEY_NAME "Software\\MIT\\Leash\\Principals" 207 208 // Free principal list obtained by getPrincipalList() 209 static void freePrincipalList(LPTSTR *princs, int count) 210 { 211 int i; 212 if (count) { 213 for (i = 0; i < count; i++) 214 if (princs[i]) 215 free(princs[i]); 216 delete[] princs; 217 } 218 } 219 220 // Retrieve history of successfully authenticated principals from registry 221 static void getPrincipalList(LPTSTR **outPrincs, int *outPrincCount) 222 { 223 DWORD count = 0; 224 DWORD valCount = 0; 225 DWORD maxLen = 0; 226 LPTSTR tempValName = NULL; 227 LPTSTR *princs = NULL; 228 *outPrincs = NULL; 229 HKEY hKey = NULL; 230 unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER, 231 LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0, 232 0, KEY_READ, 0, &hKey, 0); 233 if (rc == S_OK) { 234 // get string count 235 rc = RegQueryInfoKey( 236 hKey, 237 NULL, // __out_opt LPTSTR lpClass, 238 NULL, // __inout_opt LPDWORD lpcClass, 239 NULL, // __reserved LPDWORD lpReserved, 240 NULL, // __out_opt LPDWORD lpcSubKeys, 241 NULL, // __out_opt LPDWORD lpcMaxSubKeyLen, 242 NULL, // __out_opt LPDWORD lpcMaxClassLen, 243 &valCount, //__out_opt LPDWORD lpcValues, 244 &maxLen, // __out_opt LPDWORD lpcMaxValueNameLen, 245 NULL, // __out_opt LPDWORD lpcMaxValueLen, 246 NULL, // __out_opt LPDWORD lpcbSecurityDescriptor, 247 NULL // __out_opt PFILETIME lpftLastWriteTime 248 ); 249 } 250 if (valCount == 0) 251 goto cleanup; 252 253 princs = new LPTSTR[valCount]; 254 if (princs == NULL) 255 goto cleanup; 256 257 tempValName = new TCHAR[maxLen+1]; 258 if (tempValName == NULL) 259 goto cleanup; 260 261 // enumerate values... 262 for (DWORD iReg = 0; iReg < valCount; iReg++) { 263 LPTSTR princ = NULL; 264 DWORD size = maxLen+1; 265 rc = RegEnumValue(hKey, iReg, tempValName, &size, 266 NULL, NULL, NULL, NULL); 267 if (rc == ERROR_SUCCESS) 268 princ = _tcsdup(tempValName); 269 if (princ != NULL) 270 princs[count++] = princ; 271 } 272 273 *outPrincCount = count; 274 count = 0; 275 *outPrincs = princs; 276 princs = NULL; 277 278 cleanup: 279 if (tempValName) 280 delete[] tempValName; 281 if (princs) 282 freePrincipalList(princs, count); 283 if (hKey) 284 RegCloseKey(hKey); 285 return; 286 } 287 288 289 // HookWindow 290 // Utility class to process messages relating to the specified hwnd 291 class HookWindow 292 { 293 public: 294 typedef std::pair<HWND, HookWindow*> map_elem; 295 typedef std::map<HWND, HookWindow*> map; 296 297 HookWindow(HWND in_hwnd) : m_hwnd(in_hwnd) 298 { 299 // add 'this' to static hash 300 m_ctrl_id = GetDlgCtrlID(in_hwnd); 301 m_parent = ::GetParent(m_hwnd); 302 sm_map.insert(map_elem(m_parent, this)); 303 // grab current window proc and replace with our wndproc 304 m_parent_wndproc = SetWindowLongPtr(m_parent, 305 GWLP_WNDPROC, 306 (ULONG_PTR)(&sWindowProc)); 307 } 308 309 virtual ~HookWindow() 310 { 311 // unhook hwnd and restore old wndproc 312 SetWindowLongPtr(m_parent, GWLP_WNDPROC, m_parent_wndproc); 313 sm_map.erase(m_parent); 314 } 315 316 // Process a message 317 // return 'false' to forward message to parent wndproc 318 virtual bool WindowProc(UINT msg, WPARAM wParam, LPARAM lParam, 319 LRESULT *lr) = 0; 320 321 protected: 322 static LRESULT sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, 323 LPARAM lParam); 324 325 HWND m_hwnd; 326 HWND m_parent; 327 ULONG_PTR m_parent_wndproc; 328 int m_ctrl_id; 329 330 static map sm_map; 331 }; 332 333 HookWindow::map HookWindow::sm_map; 334 335 LRESULT HookWindow::sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, 336 LPARAM lParam) 337 { 338 LRESULT result; 339 // hash hwnd to get object and call actual window proc, 340 // then parent window proc as necessary 341 HookWindow::map::const_iterator iter = sm_map.find(hwnd); 342 if (iter != sm_map.end()) { 343 if (!iter->second->WindowProc(uMsg, wParam, lParam, &result)) 344 result = CallWindowProc((WNDPROC )iter->second->m_parent_wndproc, 345 hwnd, uMsg, wParam, lParam); 346 } else { 347 result = ::DefWindowProc(hwnd, uMsg, wParam, lParam); 348 } 349 return result; 350 } 351 352 // 353 class PrincipalEditControl : public HookWindow 354 { 355 public: 356 PrincipalEditControl(HWND hwnd, bool bUpperCaseRealm) : HookWindow(hwnd) 357 ,m_ignore_change(0) 358 ,m_bUpperCaseRealm(bUpperCaseRealm) 359 ,m_defaultRealm(NULL) 360 ,m_ctx(0) 361 ,m_enumString(NULL) 362 ,m_acdd(NULL) 363 ,m_princStr(NULL) 364 { 365 pkrb5_init_context(&m_ctx); 366 GetDefaultRealm(); 367 InitAutocomplete(); 368 } 369 370 ~PrincipalEditControl() 371 { 372 DestroyAutocomplete(); 373 if (m_princStr) 374 delete[] m_princStr; 375 if (m_ctx && m_defaultRealm) 376 pkrb5_free_default_realm(m_ctx, m_defaultRealm); 377 if (m_ctx) 378 pkrb5_free_context(m_ctx); 379 } 380 381 void ClearHistory() 382 { 383 if (m_enumString != NULL) 384 m_enumString->RemoveAll(); 385 if (m_acdd != NULL) 386 m_acdd->ResetEnumerator(); 387 if (m_princStr != NULL) { 388 delete[] m_princStr; 389 m_princStr = NULL; 390 } 391 } 392 393 protected: 394 // Convert str to upper case 395 // This should be more-or-less _UNICODE-agnostic 396 static bool StrToUpper(LPTSTR str) 397 { 398 bool bChanged = false; 399 int c; 400 if (str != NULL) { 401 while ((c = *str) != NULL) { 402 if (__isascii(c) && islower(c)) { 403 bChanged = true; 404 *str = _toupper(c); 405 } 406 str++; 407 } 408 } 409 return bChanged; 410 } 411 412 void GetDefaultRealm() 413 { 414 // @TODO: _UNICODE support here 415 if ((m_defaultRealm == NULL) && m_ctx) { 416 pkrb5_get_default_realm(m_ctx, &m_defaultRealm); 417 } 418 } 419 420 // Append default realm to user and add to the autocomplete enum string 421 void SuggestDefaultRealm(LPTSTR user) 422 { 423 if (m_defaultRealm == NULL) 424 return; 425 426 int princ_len = _tcslen(user) + _tcslen(m_defaultRealm) + 1; 427 LPTSTR princStr = new TCHAR[princ_len]; 428 if (princStr) { 429 _sntprintf_s(princStr, princ_len, _TRUNCATE, "%s%s", user, 430 m_defaultRealm); 431 if (m_princStr != NULL && (_tcscmp(princStr, m_princStr) == 0)) { 432 // this string is already added, ok to just bail 433 delete[] princStr; 434 } else { 435 if (m_princStr != NULL) { 436 // get rid of the old suggestion 437 m_enumString->RemoveString(m_princStr); 438 delete[] m_princStr; 439 } 440 // add the new one 441 m_enumString->AddString(princStr); 442 if (m_acdd != NULL) 443 m_acdd->ResetEnumerator(); 444 m_princStr = princStr; 445 } 446 } 447 } 448 449 bool AdjustRealmCase(LPTSTR princStr, LPTSTR realmStr) 450 { 451 bool bChanged = StrToUpper(realmStr); 452 if (bChanged) { 453 DWORD selStart, selEnd; 454 ::SendMessage(m_hwnd, EM_GETSEL, (WPARAM)&selStart, 455 (LPARAM)&selEnd); 456 ::SetWindowText(m_hwnd, princStr); 457 ::SendMessage(m_hwnd, EM_SETSEL, (WPARAM)selStart, (LPARAM)selEnd); 458 } 459 return bChanged; 460 } 461 462 bool ProcessText() 463 { 464 bool bChanged = false; 465 int text_len = GetWindowTextLength(m_hwnd); 466 if (text_len > 0) { 467 LPTSTR str = new TCHAR [++text_len]; 468 if (str != NULL) { 469 GetWindowText(m_hwnd, str, text_len); 470 LPTSTR realmStr = strchr(str, '@'); 471 if (realmStr != NULL) { 472 ++realmStr; 473 if (*realmStr == 0) { 474 SuggestDefaultRealm(str); 475 } 476 else if (m_bUpperCaseRealm) { 477 AdjustRealmCase(str, realmStr); 478 bChanged = true; 479 } 480 } 481 delete[] str; 482 } 483 } 484 return bChanged; 485 } 486 487 virtual bool WindowProc(UINT msg, WPARAM wp, LPARAM lp, LRESULT *lr) 488 { 489 bool bChanged = false; 490 switch (msg) { 491 case WM_COMMAND: 492 if ((LOWORD(wp)==m_ctrl_id) && 493 (HIWORD(wp)==EN_CHANGE)) { 494 if ((!m_ignore_change++) && ProcessText()) { 495 bChanged = true; 496 *lr = 0; 497 } 498 m_ignore_change--; 499 } 500 default: 501 break; 502 } 503 return bChanged; 504 } 505 506 void InitAutocomplete() 507 { 508 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 509 510 // read strings from registry 511 LPTSTR *princs = NULL; 512 int count = 0; 513 getPrincipalList(&princs, &count); 514 515 // Create our custom IEnumString implementation 516 HRESULT hRes; 517 DynEnumString *pEnumString = new DynEnumString(count, princs); 518 if (princs) 519 freePrincipalList(princs, count); 520 521 m_enumString = pEnumString; 522 523 // Create and initialize IAutoComplete object using IEnumString 524 IAutoComplete *pac = NULL; 525 hRes = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, 526 IID_PPV_ARGS(&pac)); 527 if (pac != NULL) { 528 pac->Init(m_hwnd, pEnumString, NULL, NULL); 529 530 IAutoCompleteDropDown* pacdd = NULL; 531 hRes = pac->QueryInterface(IID_IAutoCompleteDropDown, (LPVOID*)&pacdd); 532 pac->Release(); 533 m_acdd = pacdd; 534 } 535 } 536 537 void DestroyAutocomplete() 538 { 539 if (m_acdd != NULL) 540 m_acdd->Release(); 541 if (m_enumString != NULL) 542 m_enumString->Release(); 543 } 544 545 int m_ignore_change; 546 bool m_bUpperCaseRealm; 547 LPTSTR m_defaultRealm; 548 LPTSTR m_princStr; 549 krb5_context m_ctx; 550 DynEnumString *m_enumString; 551 IAutoCompleteDropDown *m_acdd; 552 }; 553 554 555 556 extern "C" void Leash_pec_add_principal(char *principal) 557 { 558 // write princ to registry 559 HKEY hKey; 560 unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER, 561 LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 562 0, 0, 0, KEY_WRITE, 0, &hKey, 0); 563 if (rc) { 564 // TODO: log failure 565 return; 566 } 567 rc = RegSetValueEx(hKey, principal, 0, REG_NONE, NULL, 0); 568 if (rc) { 569 // TODO: log failure 570 } 571 if (hKey) 572 RegCloseKey(hKey); 573 } 574 575 extern "C" void Leash_pec_clear_history(void *pec) 576 { 577 // clear princs from registry 578 RegDeleteKey(HKEY_CURRENT_USER, 579 LEASH_REGISTRY_PRINCIPALS_KEY_NAME); 580 // ...and from the specified widget 581 static_cast<PrincipalEditControl *>(pec)->ClearHistory(); 582 } 583 584 585 extern "C" void *Leash_pec_create(HWND hEdit) 586 { 587 return new PrincipalEditControl( 588 hEdit, 589 Leash_get_default_uppercaserealm() ? true : false); 590 } 591 592 extern "C" void Leash_pec_destroy(void *pec) 593 { 594 if (pec != NULL) 595 delete ((PrincipalEditControl *)pec); 596 } 597