krb5 commit: KfW auto-complete support
Benjamin Kaduk
kaduk at MIT.EDU
Fri Aug 24 15:55:27 EDT 2012
https://github.com/krb5/krb5/commit/6201bbc23f1c307e6278af72eaa8e93dc898fedf
commit 6201bbc23f1c307e6278af72eaa8e93dc898fedf
Author: Kevin Wasserman <kevin.wasserman at painless-security.com>
Date: Sat Jun 2 10:34:09 2012 -0400
KfW auto-complete support
Use the registry to store and retrieve principals for auto-complete.
Remember principals from successful autentications.
TODO: combine realm/username in principal; 'remember principal' checkbox;
reset button; add to support 'change password' dialog as well.
Signed-off-by: Kevin Wasserman <kevin.wasserman at painless-security.com>
[kaduk at mit.edu: style cleanup, copyright/license on new file.]
ticket: 7264 (new)
queue: kfw
target_version: 1.10.4
tags: pullup
src/windows/leashdll/Makefile.in | 1 +
src/windows/leashdll/lsh_pwd.c | 13 +
src/windows/leashdll/lshutil.cpp | 586 ++++++++++++++++++++++++++++++++++++++
3 files changed, 600 insertions(+), 0 deletions(-)
diff --git a/src/windows/leashdll/Makefile.in b/src/windows/leashdll/Makefile.in
index 8a7eaa4..e443504 100644
--- a/src/windows/leashdll/Makefile.in
+++ b/src/windows/leashdll/Makefile.in
@@ -29,6 +29,7 @@ OBJS= $(OUTPRE)AFSroutines.$(OBJEXT) \
$(OUTPRE)lsh_pwd.$(OBJEXT) \
$(OUTPRE)lshcallb.$(OBJEXT) \
$(OUTPRE)lshfunc.$(OBJEXT) \
+ $(OUTPRE)lshutil.$(OBJEXT) \
$(OUTPRE)timesync.$(OBJEXT) \
$(OUTPRE)winerr.$(OBJEXT) \
$(OUTPRE)winutil.$(OBJEXT) \
diff --git a/src/windows/leashdll/lsh_pwd.c b/src/windows/leashdll/lsh_pwd.c
index c7d8310..5ea59df 100644
--- a/src/windows/leashdll/lsh_pwd.c
+++ b/src/windows/leashdll/lsh_pwd.c
@@ -1458,6 +1458,9 @@ AdjustOptions(HWND hDialog, int show, int hideDiff)
}
+extern void *lacInit(HWND hEditCtl);
+extern void lacTerm(void *pAutoComplete);
+extern void lacAddPrincipal(char *principal);
/* Callback function for the Authentication Dialog box that initializes and
renews tickets. */
@@ -1487,15 +1490,20 @@ AuthenticateProc(
static HWND hSliderRenew=0;
static RECT dlgRect;
static int hideDiff = 0;
+ static void *pAutoComplete = 0;
char principal[256];
long realm_count = 0;
int disable_noaddresses = 0;
+ HWND hEditCtrl=0;
switch (message) {
case WM_INITDIALOG:
hDlg = hDialog;
+ hEditCtrl = GetDlgItem(hDialog, IDC_EDIT_PRINCIPAL);
+ if (hEditCtrl)
+ pAutoComplete = lacInit(hEditCtrl);
SetVersionInfo(hDialog,IDC_STATIC_VERSION,IDC_STATIC_COPYRIGHT);
hSliderLifetime = GetDlgItem(hDialog, IDC_STATIC_LIFETIME_VALUE);
hSliderRenew = GetDlgItem(hDialog, IDC_STATIC_RENEW_TILL_VALUE);
@@ -1828,6 +1836,10 @@ AuthenticateProc(
CleanupSliders();
memset(password,0,sizeof(password));
RemoveProp(hDialog, "HANDLES_HELP");
+ if (pAutoComplete) {
+ lacTerm(pAutoComplete);
+ pAutoComplete = NULL;
+ }
EndDialog(hDialog, (int)lParam);
return TRUE;
}
@@ -1957,6 +1969,7 @@ AuthenticateProc(
strncpy(lpdi->out.realm, realm, LEASH_REALM_SZ);
lpdi->out.realm[LEASH_REALM_SZ-1] = 0;
}
+ lacAddPrincipal(username);
CloseMe(TRUE); /* success */
return FALSE;
diff --git a/src/windows/leashdll/lshutil.cpp b/src/windows/leashdll/lshutil.cpp
new file mode 100644
index 0000000..2061e93
--- /dev/null
+++ b/src/windows/leashdll/lshutil.cpp
@@ -0,0 +1,586 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* prototype/prototype.c - <<< One-line description of file >>> */
+/* leashdll/lshutil.cpp - text maniuplation for principal entry */
+/*
+ * Copyright (C) 2012 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ *
+ * Leash Principal Edit Control
+ *
+ * Edit control customized to enter a principal.
+ * -Autocomplete functionality using history of previously successful
+ * authentications
+ * -Option to automatically convert realm to uppercase as user types
+ * -Suggest default realm when no matches available from history
+ */
+
+#include <windows.h>
+#include <wtypes.h> // LPOLESTR
+#include <Shldisp.h> // IAutoComplete
+#include <ShlGuid.h> // CLSID_AutoComplete
+#include <shobjidl.h> // IAutoCompleteDropDown
+#include <objbase.h> // CoCreateInstance
+#include <tchar.h>
+#include <map>
+#include <vector>
+
+#include "leashwin.h"
+#include "leashdll.h"
+
+#pragma comment(lib, "ole32.lib") // CoCreateInstance
+
+//
+// DynEnumString:
+// IEnumString implementation that can be dynamically updated after creation.
+//
+class DynEnumString : public IEnumString
+{
+public:
+ // IUnknown
+ STDMETHODIMP_(ULONG) AddRef()
+ {
+ return ++m_refcount;
+ }
+
+ STDMETHODIMP_(ULONG) Release()
+ {
+ ULONG refcount = --m_refcount;
+ if (refcount == 0)
+ delete this;
+ return refcount;
+ }
+
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
+ {
+ IUnknown *punk = NULL;
+ if (riid == IID_IUnknown)
+ punk = static_cast<IUnknown*>(this);
+ else if (riid == IID_IEnumString)
+ punk = static_cast<IEnumString*>(this);
+ *ppvObject = punk;
+ if (punk == NULL)
+ return E_NOINTERFACE;
+ punk->AddRef();
+ return S_OK;
+ }
+
+ // IEnumString
+public:
+ STDMETHODIMP Clone(IEnumString **ppclone)
+ {
+ LPTSTR *data = m_aStrings.data();
+ ULONG count = m_aStrings.size();
+ *ppclone = new DynEnumString(count, data);
+ return S_OK;
+ }
+
+ STDMETHODIMP Next(ULONG count, LPOLESTR *elements, ULONG *pFetched)
+ {
+ ULONG fetched = 0;
+ while (fetched < count) {
+ if (m_iter == m_aStrings.end())
+ break;
+ LPTSTR src = *m_iter++;
+ // @TODO: add _UNICODE version
+ DWORD nLengthW = ::MultiByteToWideChar(CP_ACP,
+ 0, &src[0], -1, NULL, 0);
+ LPOLESTR copy =
+ (LPOLESTR )::CoTaskMemAlloc(sizeof(OLECHAR) * nLengthW);
+ if (copy != NULL) {
+ if (::MultiByteToWideChar(CP_ACP,
+ 0, &src[0], -1, copy, nLengthW)) {
+ elements[fetched++] = copy;
+ } else {
+ // failure...
+ // TODO: debug spew
+ ::CoTaskMemFree(copy);
+ copy = NULL;
+ }
+ }
+ }
+ *pFetched = fetched;
+
+ return fetched == count ? S_OK : S_FALSE;
+ }
+
+ STDMETHODIMP Reset()
+ {
+ m_iter = m_aStrings.begin();
+ return S_OK;
+ }
+
+ STDMETHODIMP Skip(ULONG count)
+ {
+ for (ULONG i=0; i<count; i++) {
+ if (m_iter == m_aStrings.end()) {
+ m_iter = m_aStrings.begin();
+ break;
+ }
+ m_iter++;
+ }
+ return S_OK;
+ }
+
+ // Custom interface
+ DynEnumString(ULONG count, LPTSTR *strings)
+ {
+ m_aStrings.reserve(count + 1);
+ for (ULONG i = 0; i < count; i++) {
+ AddString(strings[i]);
+ }
+ m_iter = m_aStrings.begin();
+ m_refcount = 1;
+ }
+
+ virtual ~DynEnumString()
+ {
+ for (m_iter = m_aStrings.begin();
+ m_iter != m_aStrings.end();
+ m_iter++)
+ delete[] (*m_iter);
+ m_aStrings.erase(m_aStrings.begin(), m_aStrings.end());
+ }
+
+ void AddString(LPTSTR str)
+ {
+ LPTSTR copy = NULL;
+ if (str) {
+ copy = _tcsdup(str);
+ if (copy)
+ m_aStrings.push_back(copy);
+ }
+ }
+
+
+ void RemoveString(LPTSTR str)
+ {
+ std::vector<LPTSTR>::const_iterator i;
+ for (i = m_aStrings.begin(); i != m_aStrings.end(); i++) {
+ if (_tcscmp(*i, str) == 0) {
+ delete[] (*i);
+ m_aStrings.erase(i);
+ break;
+ }
+ }
+ }
+
+private:
+ ULONG m_refcount;
+ std::vector<LPTSTR>::iterator m_iter;
+ std::vector<LPTSTR> m_aStrings;
+};
+
+// Registry key to store history of successfully authenticated principals
+#define LEASH_REGISTRY_PRINCIPALS_KEY_NAME "Software\\MIT\\Leash\\Principals"
+
+// Free principal list obtained by getPrincipalList()
+static void freePrincipalList(LPTSTR *princs, int count)
+{
+ int i;
+ if (count) {
+ for (i = 0; i < count; i++)
+ if (princs[i])
+ free(princs[i]);
+ delete[] princs;
+ }
+}
+
+// Retrieve history of successfully authenticated principals from registry
+static void getPrincipalList(LPTSTR **outPrincs, int *outPrincCount)
+{
+ DWORD count = 0;
+ DWORD valCount = 0;
+ DWORD maxLen = 0;
+ LPTSTR tempValName = NULL;
+ LPTSTR *princs = NULL;
+ *outPrincs = NULL;
+ HKEY hKey = NULL;
+ unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,
+ LEASH_REGISTRY_PRINCIPALS_KEY_NAME, 0, 0,
+ 0, KEY_READ, 0, &hKey, 0);
+ if (rc == S_OK) {
+ // get string count
+ rc = RegQueryInfoKey(
+ hKey,
+ NULL, // __out_opt LPTSTR lpClass,
+ NULL, // __inout_opt LPDWORD lpcClass,
+ NULL, // __reserved LPDWORD lpReserved,
+ NULL, // __out_opt LPDWORD lpcSubKeys,
+ NULL, // __out_opt LPDWORD lpcMaxSubKeyLen,
+ NULL, // __out_opt LPDWORD lpcMaxClassLen,
+ &valCount, //__out_opt LPDWORD lpcValues,
+ &maxLen, // __out_opt LPDWORD lpcMaxValueNameLen,
+ NULL, // __out_opt LPDWORD lpcMaxValueLen,
+ NULL, // __out_opt LPDWORD lpcbSecurityDescriptor,
+ NULL // __out_opt PFILETIME lpftLastWriteTime
+ );
+ }
+ if (valCount == 0)
+ goto cleanup;
+
+ princs = new LPTSTR[valCount];
+ if (princs == NULL)
+ goto cleanup;
+
+ tempValName = new TCHAR[maxLen+1];
+ if (tempValName == NULL)
+ goto cleanup;
+
+ // enumerate values...
+ for (DWORD iReg = 0; iReg < valCount; iReg++) {
+ LPTSTR princ = NULL;
+ DWORD size = maxLen+1;
+ rc = RegEnumValue(hKey, iReg, tempValName, &size,
+ NULL, NULL, NULL, NULL);
+ if (rc == ERROR_SUCCESS)
+ princ = _tcsdup(tempValName);
+ if (princ != NULL)
+ princs[count++] = princ;
+ }
+
+ *outPrincCount = count;
+ count = 0;
+ *outPrincs = princs;
+ princs = NULL;
+
+cleanup:
+ if (tempValName)
+ delete[] tempValName;
+ if (princs)
+ freePrincipalList(princs, count);
+ if (hKey)
+ RegCloseKey(hKey);
+ return;
+}
+
+
+// HookWindow
+// Utility class to process messages relating to the specified hwnd
+class HookWindow
+{
+public:
+ typedef std::pair<HWND, HookWindow*> map_elem;
+ typedef std::map<HWND, HookWindow*> map;
+
+ HookWindow(HWND in_hwnd) : m_hwnd(in_hwnd)
+ {
+ // add 'this' to static hash
+ m_ctrl_id = GetDlgCtrlID(in_hwnd);
+ m_parent = ::GetParent(m_hwnd);
+ sm_map.insert(map_elem(m_parent, this));
+ // grab current window proc and replace with our wndproc
+ m_parent_wndproc = SetWindowLongPtr(m_parent,
+ GWLP_WNDPROC,
+ (ULONG_PTR)(&sWindowProc));
+ }
+
+ virtual ~HookWindow()
+ {
+ // unhook hwnd and restore old wndproc
+ SetWindowLongPtr(m_parent, GWLP_WNDPROC, m_parent_wndproc);
+ sm_map.erase(m_parent);
+ }
+
+ // Process a message
+ // return 'false' to forward message to parent wndproc
+ virtual bool WindowProc(UINT msg, WPARAM wParam, LPARAM lParam,
+ LRESULT *lr) = 0;
+
+protected:
+ static LRESULT sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam);
+
+ HWND m_hwnd;
+ HWND m_parent;
+ ULONG_PTR m_parent_wndproc;
+ int m_ctrl_id;
+
+ static map sm_map;
+};
+
+HookWindow::map HookWindow::sm_map;
+
+LRESULT HookWindow::sWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam)
+{
+ LRESULT result;
+ // hash hwnd to get object and call actual window proc,
+ // then parent window proc as necessary
+ HookWindow::map::const_iterator iter = sm_map.find(hwnd);
+ if (iter != sm_map.end()) {
+ if (!iter->second->WindowProc(uMsg, wParam, lParam, &result))
+ result = CallWindowProc((WNDPROC )iter->second->m_parent_wndproc,
+ hwnd, uMsg, wParam, lParam);
+ } else {
+ result = ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+ }
+ return result;
+}
+
+//
+class PrincipalEditControl : public HookWindow
+{
+public:
+ PrincipalEditControl(HWND hwnd, bool bUpperCaseRealm) : HookWindow(hwnd)
+ ,m_ignore_change(0)
+ ,m_bUpperCaseRealm(bUpperCaseRealm)
+ ,m_defaultRealm(NULL)
+ ,m_ctx(0)
+ ,m_acdd(NULL)
+ ,m_princStr(NULL)
+ {
+ pkrb5_init_context(&m_ctx);
+ GetDefaultRealm();
+ InitAutocomplete();
+ }
+
+ ~PrincipalEditControl()
+ {
+ DestroyAutocomplete();
+ if (m_princStr)
+ delete[] m_princStr;
+ if (m_ctx && m_defaultRealm)
+ pkrb5_free_default_realm(m_ctx, m_defaultRealm);
+ if (m_ctx)
+ pkrb5_free_context(m_ctx);
+ }
+
+protected:
+ // Convert str to upper case
+ // This should be more-or-less _UNICODE-agnostic
+ static bool StrToUpper(LPTSTR str)
+ {
+ bool bChanged = false;
+ int c;
+ if (str != NULL) {
+ while ((c = *str) != NULL) {
+ if (__isascii(c) && islower(c)) {
+ bChanged = true;
+ *str = _toupper(c);
+ }
+ str++;
+ }
+ }
+ return bChanged;
+ }
+
+ void GetDefaultRealm()
+ {
+ // @TODO: _UNICODE support here
+ if ((m_defaultRealm == NULL) && m_ctx) {
+ pkrb5_get_default_realm(m_ctx, &m_defaultRealm);
+ }
+ }
+
+ // Append default realm to user and add to the autocomplete enum string
+ void SuggestDefaultRealm(LPTSTR user)
+ {
+ if (m_defaultRealm == NULL)
+ return;
+
+ int princ_len = _tcslen(user) + _tcslen(m_defaultRealm) + 1;
+ LPTSTR princStr = new TCHAR[princ_len];
+ if (princStr) {
+ _sntprintf_s(princStr, princ_len, _TRUNCATE, "%s%s", user,
+ m_defaultRealm);
+ if (m_princStr != NULL && (_tcscmp(princStr, m_princStr) == 0)) {
+ // this string is already added, ok to just bail
+ delete[] princStr;
+ } else {
+ if (m_princStr != NULL) {
+ // get rid of the old suggestion
+ m_enumString->RemoveString(m_princStr);
+ delete[] m_princStr;
+ }
+ // add the new one
+ m_enumString->AddString(princStr);
+ m_acdd->ResetEnumerator();
+ m_princStr = princStr;
+ }
+ }
+ }
+
+ bool AdjustRealmCase(LPTSTR princStr, LPTSTR realmStr)
+ {
+ bool bChanged = StrToUpper(realmStr);
+ if (bChanged) {
+ DWORD selStart, selEnd;
+ ::SendMessage(m_hwnd, EM_GETSEL, (WPARAM)&selStart,
+ (LPARAM)&selEnd);
+ ::SetWindowText(m_hwnd, princStr);
+ ::SendMessage(m_hwnd, EM_SETSEL, (WPARAM)selStart, (LPARAM)selEnd);
+ }
+ return bChanged;
+ }
+
+ bool ProcessText()
+ {
+ bool bChanged = false;
+ int text_len = GetWindowTextLength(m_hwnd);
+ if (text_len > 0) {
+ LPTSTR str = new TCHAR [++text_len];
+ if (str != NULL) {
+ GetWindowText(m_hwnd, str, text_len);
+ LPTSTR realmStr = strchr(str, '@');
+ if (realmStr != NULL) {
+ ++realmStr;
+ if (*realmStr == 0) {
+ SuggestDefaultRealm(str);
+ }
+ else if (m_bUpperCaseRealm) {
+ AdjustRealmCase(str, realmStr);
+ bChanged = true;
+ }
+ }
+ delete[] str;
+ }
+ }
+ return bChanged;
+ }
+
+ virtual bool WindowProc(UINT msg, WPARAM wp, LPARAM lp, LRESULT *lr)
+ {
+ bool bChanged = false;
+ switch (msg) {
+ case WM_COMMAND:
+ if ((LOWORD(wp)==m_ctrl_id) &&
+ (HIWORD(wp)==EN_CHANGE)) {
+ if ((!m_ignore_change++) && ProcessText()) {
+ bChanged = true;
+ *lr = 0;
+ }
+ m_ignore_change--;
+ }
+ default:
+ break;
+ }
+ return bChanged;
+ }
+
+ void InitAutocomplete()
+ {
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
+
+ // read strings from registry
+ LPTSTR *princs = NULL;
+ int count = 0;
+ getPrincipalList(&princs, &count);
+
+ // Create our custom IEnumString implementation
+ HRESULT hRes;
+ DynEnumString *pEnumString = new DynEnumString(count, princs);
+ if (princs)
+ freePrincipalList(princs, count);
+
+ m_enumString = pEnumString;
+
+ // Create and initialize IAutoComplete object using IEnumString
+ IAutoComplete *pac = NULL;
+ hRes = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&pac));
+ // @TODO: error handling
+
+ pac->Init(m_hwnd, pEnumString, NULL, NULL);
+
+ IAutoCompleteDropDown* pacdd = NULL;
+ hRes = pac->QueryInterface(IID_IAutoCompleteDropDown, (LPVOID*)&pacdd);
+ pac->Release();
+
+ // @TODO: auto-suggest; other advanced options?
+#if 0
+ IAutoComplete2 *pac2;
+
+ if (SUCCEEDED(pac->QueryInterface(IID_IAutoComplete2, (LPVOID*)&pac2)))
+ {
+ pac2->SetOptions(ACO_AUTOSUGGEST);
+ pac2->Release();
+ }
+#endif
+ m_acdd = pacdd;
+ }
+
+ void DestroyAutocomplete()
+ {
+ if (m_acdd != NULL)
+ m_acdd->Release();
+ if (m_enumString != NULL)
+ m_enumString->Release();
+ }
+
+ int m_ignore_change;
+ bool m_bUpperCaseRealm;
+ LPTSTR m_defaultRealm;
+ LPTSTR m_princStr;
+ krb5_context m_ctx;
+ DynEnumString *m_enumString;
+ IAutoCompleteDropDown *m_acdd;
+};
+
+
+
+extern "C" void lacAddPrincipal(char *principal)
+{
+ // write princ to registry
+ HKEY hKey;
+ unsigned long rc = RegCreateKeyEx(HKEY_CURRENT_USER,
+ LEASH_REGISTRY_PRINCIPALS_KEY_NAME,
+ 0, 0, 0, KEY_WRITE, 0, &hKey, 0);
+ if (rc) {
+ // TODO: log failure
+ return;
+ }
+ rc = RegSetValueEx(hKey, principal, 0, REG_NONE, NULL, 0);
+ if (rc) {
+ // TODO: log failure
+ }
+ if (hKey)
+ RegCloseKey(hKey);
+}
+
+extern "C" void lacReset()
+{
+ // clear princs from registry
+ //RegDeleteKeyEx()
+}
+
+
+extern "C" void *lacInit(HWND hEdit)
+{
+ return new PrincipalEditControl(
+ hEdit,
+ Leash_get_default_uppercaserealm() ? true : false);
+}
+
+extern "C" void lacTerm(void *pHook)
+{
+ if (pHook != NULL)
+ delete ((PrincipalEditControl *)pHook);
+}
More information about the cvs-krb5
mailing list