xref: /freebsd/crypto/krb5/src/windows/leashdll/lshutil.cpp (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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