Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/windows/leashdll/lshutil.cpp
34914 views
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
598