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