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
AddRef()67 STDMETHODIMP_(ULONG) AddRef()
68 {
69 return ++m_refcount;
70 }
71
Release()72 STDMETHODIMP_(ULONG) Release()
73 {
74 ULONG refcount = --m_refcount;
75 if (refcount == 0)
76 delete this;
77 return refcount;
78 }
79
QueryInterface(REFIID riid,void ** ppvObject)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:
Clone(IEnumString ** ppclone)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
Next(ULONG count,LPOLESTR * elements,ULONG * pFetched)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
Reset()133 STDMETHODIMP Reset()
134 {
135 m_iter = m_aStrings.begin();
136 return S_OK;
137 }
138
Skip(ULONG count)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
DynEnumString(ULONG count,LPTSTR * strings)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
~DynEnumString()162 virtual ~DynEnumString()
163 {
164 RemoveAll();
165 }
166
RemoveAll()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
AddString(LPTSTR str)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
RemoveString(LPTSTR str)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()
freePrincipalList(LPTSTR * princs,int count)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
getPrincipalList(LPTSTR ** outPrincs,int * outPrincCount)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
HookWindow(HWND in_hwnd)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
~HookWindow()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
sWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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:
PrincipalEditControl(HWND hwnd,bool bUpperCaseRealm)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
~PrincipalEditControl()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
ClearHistory()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
StrToUpper(LPTSTR str)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
GetDefaultRealm()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
SuggestDefaultRealm(LPTSTR user)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
AdjustRealmCase(LPTSTR princStr,LPTSTR realmStr)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
ProcessText()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
WindowProc(UINT msg,WPARAM wp,LPARAM lp,LRESULT * lr)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
InitAutocomplete()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
DestroyAutocomplete()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
Leash_pec_add_principal(char * principal)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
Leash_pec_clear_history(void * pec)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
Leash_pec_create(HWND hEdit)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
Leash_pec_destroy(void * pec)592 extern "C" void Leash_pec_destroy(void *pec)
593 {
594 if (pec != NULL)
595 delete ((PrincipalEditControl *)pec);
596 }
597