]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
* Added new krb5_ccache type "MSLSA" for Windows only.
authorJeffrey Altman <jaltman@secure-endpoints.com>
Fri, 12 Dec 2003 22:22:36 +0000 (22:22 +0000)
committerJeffrey Altman <jaltman@secure-endpoints.com>
Fri, 12 Dec 2003 22:22:36 +0000 (22:22 +0000)
   This new ccache type provides an interface for the MIT krb5_cc api
   functions to be used to access the contents of the MS Kerberos LSA
   cache.  The ccache type is read-only because the MS Kerberos LSA
   does not allow third party applications to insert credentials into
   the cache.

   The primary motivation of this work was to encapsulate the complex
   operations necessary to manipulate the MS Kerberos LSA.  The code
   was far from trivial and was often implemented incorrectly.  Worse
   still was the fact that each version of Windows since W2K modified
   the use of the LSA API.

   The code which was originally donated in the form of ms2mit.c had
   many memory and handle leaks which were acceptable for a one time
   application such as ms2mit.c. Unfortunately, this code has started
   to appear in many other applications: KfW's Leash, the AFS Wake
   systray tool, and others.

   By using the new MSLSA ccache the implementation of ms2mit.c went
   from 890 lines to 50 lines of code and comments.  All that is necessary
   is for the MSLSA ccache to be resolved and for its contents to be
   copied with krb5_cc_copy_creds to the default ccache.

   The MSLSA ccache implements all of the functions of a ccache except
   those which would be used to store data into the ccache.  When a
   write attempt is performed the new error KRB5_CC_READONLY is returned.

   The residual portion of the MSLSA ccache name is current ignored
   but preserved.  If you ask for ccache "MSLSA:myname" you will be
   given access to the LSA cache for the current Logon Session.  If
   you later ask for the name of the ccache you will be returned the
   same name.  In the future, the residual might be used to provide
   information necessary to identify a specific logon session whose
   cache it is desired to access.  If this is ever done, the applications
   which use it will have to possess the SeTcbPrivilege privilege.

   Using KfW's Leash it is now possible to set the Krb5 credential
   cache to "MSLSA:" and use it to monitor the contents of the
   MS Kerberos LSA cache.

   As part of adding this functionality, krb5_32.dll is not linked
   against the "secur32.lib" library as the Lsa security sdk routines
   are stored in the SECUR32.DLL file.

ticket: 2049
target_version: 1.3.2
tags: pullup

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@15886 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/ChangeLog
src/lib/Makefile.in
src/lib/krb5/ccache/ChangeLog
src/lib/krb5/ccache/Makefile.in
src/lib/krb5/ccache/cc_mslsa.c [new file with mode: 0644]
src/lib/krb5/ccache/ccbase.c
src/lib/krb5/error_tables/ChangeLog
src/lib/krb5/error_tables/krb5_err.et
src/windows/ms2mit/ChangeLog
src/windows/ms2mit/Makefile.in
src/windows/ms2mit/ms2mit.c

index 6e3b19467424ebb287c745d4d0532326448cec9d..c6777db66451f34cfc21e7b236b46d018cf34bf1 100644 (file)
@@ -1,3 +1,8 @@
+2003-12-11  Jeffrey Altman <jaltman@mit.edu>
+
+        * Makefile.in: Add secur32.lib to libraries necessary to build 
+          krb5_32.dll.  Necessary to support the new MSLSA ccache type.
+
 2003-12-08  Jeffrey Altman <jaltman@mit.edu>
 
         * krb4_32.def: Add exports for functions exported by KfM
index 01a43511dba15bc2cc39e636790080bb37850f24..030beb7bd2e8e90071262697547e3a9e53f226d5 100644 (file)
@@ -52,7 +52,7 @@ KRB5RC = krb5.rc
 VERSIONRC = $(BUILDTOP)\windows\version.rc
 
 WINLIBS = kernel32.lib ws2_32.lib user32.lib shell32.lib oldnames.lib \
-       version.lib advapi32.lib gdi32.lib
+       version.lib secur32.lib advapi32.lib gdi32.lib
 WINDLLFLAGS = $(DLL_LINKOPTS) -base:0x1c000000
 
 NO_GLUE=$(OUTPRE)no_glue.obj
index 9bfb82facd85d99b1341e69c507a0eb56898ab6b..d8a5ec94e7b2f559ae589f21110979853393612b 100644 (file)
@@ -1,3 +1,27 @@
+2003-12-11  Jeffrey Altman <jaltman@mit.edu>
+
+    * Makefile.in, ccbase.c, cc_mslsa.c (new)
+
+    Remove all of the code which was duplicated between ms2mit.c
+    and the KfW Leash libraries (and who knows how many applications
+    shipped by third parties) and use it as the basis for a new
+    krb5_ccache type, "MSLSA:".  The "MSLSA:" ccache type is a
+    read-only ccache which can be used either as a monitor of the
+    contents of the Microsoft LSA cache or as a source for copying
+    the contents to another ccache type.  The purpose of migrating
+    this code to the krb5_32.dll is to avoid the need for applications
+    to be consistently updated each time Microsoft makes a change
+    to the behavior of the LSA cache.  Changes have occurred with
+    the release of 2000, XP, and 2003 so far.  Also, the code for
+    working with the MS LSA cache is not well documented and many
+    mistakes were made in the original versions of the ms2mit.c
+    code base.  Unfortunately, the ms2mit.c code has been copied
+    into many other applications.  
+
+    With access to this new ccache type, the ms2mit.c source file
+    is reduced from 890 lines to 80 lines including the copyright
+    banner.
+
 2003-11-26  Jeffrey Altman <jaltman@mit.edu>
 
     * cc_default.c: Add support for Leash Kinit Dialog on Windows to
index 937c586f3248a3f7332900eee4bd7329cbd03f9b..6b3e4a4413b599e77c083579788b4c31b2f7846b 100644 (file)
@@ -25,6 +25,7 @@ STLIBOBJS= \
        ccdefops.o \
        cc_retr.o \
        cc_file.o cc_memory.o \
+##WIN32##   cc_mslsa.c \
        ccfns.o \
        ser_cc.o
 
@@ -35,6 +36,7 @@ OBJS= $(OUTPRE)ccbase.$(OBJEXT) \
        $(OUTPRE)cc_retr.$(OBJEXT) \
        $(OUTPRE)cc_file.$(OBJEXT) \
        $(OUTPRE)cc_memory.$(OBJEXT) \
+##WIN32##   $(OUTPRE)cc_mslsa.$(OBJEXT) \
        $(OUTPRE)ccfns.$(OBJEXT) \
        $(OUTPRE)ser_cc.$(OBJEXT)
 
@@ -45,6 +47,7 @@ SRCS= $(srcdir)/ccbase.c \
        $(srcdir)/cc_retr.c \
        $(srcdir)/cc_file.c \
        $(srcdir)/cc_memory.c \
+##WIN32##   $(srcdir)cc_mslsa.c \
        $(srcdir)/ccfns.c \
        $(srcdir)/ser_cc.c
 
@@ -139,6 +142,9 @@ cc_memory.so cc_memory.po $(OUTPRE)cc_memory.$(OBJEXT): cc_memory.c $(SRCTOP)/in
   $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS) $(BUILDTOP)/include/profile.h \
   $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \
   $(SRCTOP)/include/krb5/kdb.h
+##WIN32##cc_mslsa.so cc_mslsa.po $(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(SRCTOP)/include/k5-int.h \
+##WIN32##  $(BUILDTOP)/include/krb5/osconf.h $(BUILDTOP)/include/krb5/autoconf.h \
+##WIN32##  $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS)
 ccfns.so ccfns.po $(OUTPRE)ccfns.$(OBJEXT): ccfns.c $(SRCTOP)/include/k5-int.h \
   $(BUILDTOP)/include/krb5/osconf.h $(BUILDTOP)/include/krb5/autoconf.h \
   $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS) $(BUILDTOP)/include/profile.h \
diff --git a/src/lib/krb5/ccache/cc_mslsa.c b/src/lib/krb5/ccache/cc_mslsa.c
new file mode 100644 (file)
index 0000000..72a2b7f
--- /dev/null
@@ -0,0 +1,1265 @@
+/*
+ * lib/krb5/ccache/cc_mslsa.c
+ *
+ * Copyright 2003 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ * 
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ * 
+ * Copyright 2000 by Carnegie Mellon University
+ *
+ * All Rights Reserved
+ * 
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Carnegie Mellon
+ * University not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission.
+ * 
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
+ * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Implementation of read-only microsoft windows lsa credentials cache
+ */
+
+#ifdef _WIN32
+#define UNICODE
+#define _UNICODE
+
+#include "k5-int.h"
+#include "com_err.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <conio.h>
+#include <time.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <ntsecapi.h>
+
+static VOID
+ShowWinError(LPSTR szAPI, DWORD dwError)
+{
+#define MAX_MSG_SIZE 256
+
+    // TODO - Write errors to event log so that scripts that don't
+    // check for errors will still get something in the event log
+
+    WCHAR szMsgBuf[MAX_MSG_SIZE];
+    DWORD dwRes;
+
+    printf("Error calling function %s: %lu\n", szAPI, dwError);
+
+    dwRes = FormatMessage (
+        FORMAT_MESSAGE_FROM_SYSTEM,
+        NULL,
+        dwError,
+        MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US),
+        szMsgBuf,
+        MAX_MSG_SIZE,
+        NULL);
+    if (0 == dwRes) {
+        printf("FormatMessage failed with %d\n", GetLastError());
+        ExitProcess(EXIT_FAILURE);
+    }
+
+    printf("%S",szMsgBuf);
+}
+
+static VOID
+ShowLsaError(LPSTR szAPI, NTSTATUS Status)
+{
+    //
+    // Convert the NTSTATUS to Winerror. Then call ShowWinError().
+    //
+    ShowWinError(szAPI, LsaNtStatusToWinError(Status));
+}
+
+
+
+static BOOL
+WINAPI
+UnicodeToANSI(LPTSTR lpInputString, LPSTR lpszOutputString, int nOutStringLen)
+{
+    CPINFO CodePageInfo;
+
+    GetCPInfo(CP_ACP, &CodePageInfo);
+
+    if (CodePageInfo.MaxCharSize > 1)
+        // Only supporting non-Unicode strings
+        return FALSE;
+    else if (((LPBYTE) lpInputString)[1] == '\0')
+    {
+        // Looks like unicode, better translate it
+        WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) lpInputString, -1,
+                            lpszOutputString, nOutStringLen, NULL, NULL);
+    }
+    else
+        lstrcpyA(lpszOutputString, (LPSTR) lpInputString);
+    return TRUE;
+}  // UnicodeToANSI
+
+static VOID
+WINAPI
+ANSIToUnicode(LPSTR  lpInputString, LPTSTR lpszOutputString, int nOutStringLen)
+{
+
+    CPINFO CodePageInfo;
+
+    lstrcpy(lpszOutputString, (LPTSTR) lpInputString);
+
+    GetCPInfo(CP_ACP, &CodePageInfo);
+
+    if (CodePageInfo.MaxCharSize > 1)
+        // It must already be a Unicode string
+        return;
+    else if (((LPBYTE) lpInputString)[1] != '\0')
+    {
+        // Looks like ANSI, better translate it
+        MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpInputString, -1,
+                            (LPWSTR) lpszOutputString, nOutStringLen);
+    }
+    else
+        lstrcpy(lpszOutputString, (LPTSTR) lpInputString);
+}  // ANSIToUnicode
+
+
+static void
+MSPrincToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context, krb5_principal *principal)
+{
+    WCHAR princbuf[512],tmpbuf[128];
+    char aname[512];
+    USHORT i;
+    princbuf[0]=0;
+    for (i=0;i<msprinc->NameCount;i++) {
+        wcsncpy(tmpbuf, msprinc->Names[i].Buffer,
+                msprinc->Names[i].Length/sizeof(WCHAR));
+        tmpbuf[msprinc->Names[i].Length/sizeof(WCHAR)]=0;
+        if (princbuf[0])
+            wcscat(princbuf, L"/");
+        wcscat(princbuf, tmpbuf);
+    }
+    wcscat(princbuf, L"@");
+    wcscat(princbuf, realm);
+    UnicodeToANSI(princbuf, aname, sizeof(aname));
+    krb5_parse_name(context, aname, principal);
+}
+
+
+static time_t
+FileTimeToUnixTime(LARGE_INTEGER *ltime)
+{
+    FILETIME filetime, localfiletime;
+    SYSTEMTIME systime;
+    struct tm utime;
+    filetime.dwLowDateTime=ltime->LowPart;
+    filetime.dwHighDateTime=ltime->HighPart;
+    FileTimeToLocalFileTime(&filetime, &localfiletime);
+    FileTimeToSystemTime(&localfiletime, &systime);
+    utime.tm_sec=systime.wSecond;
+    utime.tm_min=systime.wMinute;
+    utime.tm_hour=systime.wHour;
+    utime.tm_mday=systime.wDay;
+    utime.tm_mon=systime.wMonth-1;
+    utime.tm_year=systime.wYear-1900;
+    utime.tm_isdst=-1;
+    return(mktime(&utime));
+}
+
+static void
+MSSessionKeyToMITKeyblock(KERB_CRYPTO_KEY *mskey, krb5_context context, krb5_keyblock *keyblock)
+{
+    krb5_keyblock tmpblock;
+    tmpblock.magic=KV5M_KEYBLOCK;
+    tmpblock.enctype=mskey->KeyType;
+    tmpblock.length=mskey->Length;
+    tmpblock.contents=mskey->Value;
+    krb5_copy_keyblock_contents(context, &tmpblock, keyblock);
+}
+
+
+static void
+MSFlagsToMITFlags(ULONG msflags, ULONG *mitflags)
+{
+    *mitflags=msflags;
+}
+
+static void
+MSTicketToMITTicket(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_data *ticket)
+{
+    krb5_data tmpdata, *newdata;
+    tmpdata.magic=KV5M_DATA;
+    tmpdata.length=msticket->EncodedTicketSize;
+    tmpdata.data=msticket->EncodedTicket;
+    // todo: fix this up a little. this is ugly and will break krb_free_data()
+    krb5_copy_data(context, &tmpdata, &newdata);
+    memcpy(ticket, newdata, sizeof(krb5_data));
+}
+
+static void
+MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_creds *creds)
+{
+    WCHAR wtmp[128];
+    ZeroMemory(creds, sizeof(krb5_creds));
+    creds->magic=KV5M_CREDS;
+    wcsncpy(wtmp, msticket->TargetDomainName.Buffer,
+            msticket->TargetDomainName.Length/sizeof(WCHAR));
+    wtmp[msticket->TargetDomainName.Length/sizeof(WCHAR)]=0;
+    MSPrincToMITPrinc(msticket->ClientName, wtmp, context, &creds->client);
+    wcsncpy(wtmp, msticket->DomainName.Buffer,
+            msticket->DomainName.Length/sizeof(WCHAR));
+    wtmp[msticket->DomainName.Length/sizeof(WCHAR)]=0;
+    MSPrincToMITPrinc(msticket->ServiceName, wtmp, context, &creds->server);
+    MSSessionKeyToMITKeyblock(&msticket->SessionKey, context, 
+                              &creds->keyblock);
+    MSFlagsToMITFlags(msticket->TicketFlags, &creds->ticket_flags);
+    creds->times.starttime=FileTimeToUnixTime(&msticket->StartTime);
+    creds->times.endtime=FileTimeToUnixTime(&msticket->EndTime);
+    creds->times.renew_till=FileTimeToUnixTime(&msticket->RenewUntil);
+
+    /* MS Tickets are addressless.  MIT requires an empty address
+     * not a NULL list of addresses.
+     */
+    creds->addresses = (krb5_address **)malloc(sizeof(krb5_address *));
+    memset(creds->addresses, 0, sizeof(krb5_address *));
+
+    MSTicketToMITTicket(msticket, context, &creds->ticket);
+}
+
+static BOOL
+PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId)
+{
+    LSA_STRING Name;
+    NTSTATUS Status;
+
+    Status = LsaConnectUntrusted(
+        pLogonHandle
+        );
+
+    if (FAILED(Status))
+    {
+        ShowLsaError("LsaConnectUntrusted", Status);
+        return FALSE;
+    }
+
+    Name.Buffer = MICROSOFT_KERBEROS_NAME_A;
+    Name.Length = strlen(Name.Buffer);
+    Name.MaximumLength = Name.Length + 1;
+
+    Status = LsaLookupAuthenticationPackage(
+        *pLogonHandle,
+        &Name,
+        pPackageId
+        );
+
+    if (FAILED(Status))
+    {
+        ShowLsaError("LsaLookupAuthenticationPackage", Status);
+        return FALSE;
+    }
+
+    return TRUE;
+
+}
+
+
+static DWORD
+ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2)
+{
+    //
+    // The buffers for Source1 and Source2 cannot overlap pTarget's
+    // buffer.  Source1.Length + Source2.Length must be <= 0xFFFF,
+    // otherwise we overflow...
+    //
+
+    USHORT TotalSize = Source1.Length + Source2.Length;
+    PBYTE buffer = (PBYTE) pTarget->Buffer;
+
+    if (TotalSize > pTarget->MaximumLength)
+        return ERROR_INSUFFICIENT_BUFFER;
+
+    pTarget->Length = TotalSize;
+    memcpy(buffer, Source1.Buffer, Source1.Length);
+    memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length);
+    return ERROR_SUCCESS;
+}
+
+static BOOL
+get_STRING_from_registry(HKEY hBaseKey, char * key, char * value, char * outbuf, DWORD  outlen)
+{
+    HKEY hKey;
+    DWORD dwCount;
+    LONG rc;
+
+       if (!outbuf || outlen == 0)
+               return FALSE;
+
+    rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey);
+    if (rc)
+        return FALSE;
+
+    dwCount = outlen;
+    rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount);
+    RegCloseKey(hKey);
+
+    return rc?FALSE:TRUE;
+}
+
+static BOOL
+GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData)
+{
+    NTSTATUS Status = 0;
+    HANDLE  TokenHandle;
+    TOKEN_STATISTICS Stats;
+    DWORD   ReqLen;
+    BOOL    Success;
+
+    if (!ppSessionData)
+        return FALSE;
+    *ppSessionData = NULL;
+
+    Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle );
+    if ( !Success )
+        return FALSE;
+
+    Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen );
+    CloseHandle( TokenHandle );
+    if ( !Success )
+        return FALSE;
+
+    Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData );
+    if ( FAILED(Status) || !ppSessionData )
+        return FALSE;
+
+    return TRUE;
+}
+
+//
+// IsKerberosLogon() does not validate whether or not there are valid tickets in the 
+// cache.  It validates whether or not it is reasonable to assume that if we 
+// attempted to retrieve valid tickets we could do so.  Microsoft does not 
+// automatically renew expired tickets.  Therefore, the cache could contain
+// expired or invalid tickets.  Microsoft also caches the user's password 
+// and will use it to retrieve new TGTs if the cache is empty and tickets
+// are requested.
+
+static BOOL
+IsKerberosLogon(VOID)
+{
+    PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+    BOOL    Success = FALSE;
+
+    if ( GetSecurityLogonSessionData(&pSessionData) ) {
+        if ( pSessionData->AuthenticationPackage.Buffer ) {
+            WCHAR buffer[256];
+            WCHAR *usBuffer;
+            int usLength;
+
+            Success = FALSE;
+            usBuffer = (pSessionData->AuthenticationPackage).Buffer;
+            usLength = (pSessionData->AuthenticationPackage).Length;
+            if (usLength < 256)
+            {
+                lstrcpyn (buffer, usBuffer, usLength);
+                lstrcat (buffer,L"");
+                if ( !lstrcmp(L"Kerberos",buffer) )
+                    Success = TRUE;
+            }
+        }
+        LsaFreeReturnBuffer(pSessionData);
+    }
+    return Success;
+}
+
+static NTSTATUS
+ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest, ULONG * outSize)
+{
+    NTSTATUS Status;
+    UNICODE_STRING TargetPrefix;
+    USHORT TargetSize;
+    ULONG RequestSize;
+    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+
+    *outRequest = NULL;
+    *outSize = 0;
+
+    //
+    // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we
+    // can easily concatenate it later.
+    //
+
+    TargetPrefix.Buffer = L"krbtgt/";
+    TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR);
+    TargetPrefix.MaximumLength = TargetPrefix.Length;
+
+    //
+    // We will need to concatenate the "krbtgt/" prefix and the 
+    // Logon Session's DnsDomainName into our request's target name.
+    //
+    // Therefore, first compute the necessary buffer size for that.
+    //
+    // Note that we might theoretically have integer overflow.
+    //
+
+    TargetSize = TargetPrefix.Length + DomainName.Length;
+
+    //
+    // The ticket request buffer needs to be a single buffer.  That buffer
+    // needs to include the buffer for the target name.
+    //
+
+    RequestSize = sizeof(*pTicketRequest) + TargetSize;
+
+    //
+    // Allocate the request buffer and make sure it's zero-filled.
+    //
+
+    pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+    if (!pTicketRequest)
+        return GetLastError();
+
+    //
+    // Concatenate the target prefix with the previous reponse's
+    // target domain.
+    //
+
+    pTicketRequest->TargetName.Length = 0;
+    pTicketRequest->TargetName.MaximumLength = TargetSize;
+    pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+    Status = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName),
+                                        TargetPrefix,
+                                        DomainName);
+    *outRequest = pTicketRequest;
+    *outSize    = RequestSize;
+    return Status;
+}
+
+static BOOL
+PurgeMSTGT(HANDLE LogonHandle, ULONG  PackageId)
+{
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+    KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest;
+
+    PurgeRequest.MessageType = KerbPurgeTicketCacheMessage;
+    PurgeRequest.LogonId.LowPart = 0;
+    PurgeRequest.LogonId.HighPart = 0;
+    PurgeRequest.ServerName.Buffer = L"";
+    PurgeRequest.ServerName.Length = 0;
+    PurgeRequest.ServerName.MaximumLength = 0;
+    PurgeRequest.RealmName.Buffer = L"";
+    PurgeRequest.RealmName.Length = 0;
+    PurgeRequest.RealmName.MaximumLength = 0;
+    Status = LsaCallAuthenticationPackage(LogonHandle,
+                                           PackageId,
+                                           &PurgeRequest,
+                                           sizeof(PurgeRequest),
+                                           NULL,
+                                           NULL,
+                                           &SubStatus
+                                           );
+    if (FAILED(Status) || FAILED(SubStatus))
+        return FALSE;
+    return TRUE;
+}
+
+//
+// #define ENABLE_PURGING
+// to allow the purging of expired tickets from LSA cache.  This is necessary
+// to force the retrieval of new TGTs.  Microsoft does not appear to retrieve
+// new tickets when they expire.  Instead they continue to accept the expired
+// tickets.  I do not want to enable purging of the LSA cache without testing
+// the side effects in a Windows domain with a machine which has been suspended,
+// removed from the network, and resumed after ticket expiration.
+//
+static BOOL
+GetMSTGT(HANDLE LogonHandle, ULONG PackageId,KERB_EXTERNAL_TICKET **ticket)
+{
+    //
+    // INVARIANTS:
+    //
+    //   (FAILED(Status) || FAILED(SubStatus)) ==> error
+    //   bIsLsaError ==> LsaCallAuthenticationPackage() error
+    //
+
+    BOOL bIsLsaError = FALSE;
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+
+    KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest;
+    PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+    ULONG RequestSize;
+    ULONG ResponseSize;
+#ifdef ENABLE_PURGING
+    int    purge_cache = 0;
+#endif /* ENABLE_PURGING */
+    int    ignore_cache = 0;
+
+    CacheRequest.MessageType = KerbRetrieveTicketMessage;
+    CacheRequest.LogonId.LowPart = 0;
+    CacheRequest.LogonId.HighPart = 0;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        &CacheRequest,
+        sizeof(CacheRequest),
+        &pTicketResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    if (FAILED(Status))
+    {
+        // if the call to LsaCallAuthenticationPackage failed we cannot
+        // perform any queries most likely because the Kerberos package 
+        // is not available or we do not have access
+        bIsLsaError = TRUE;
+        goto cleanup;
+    }
+
+    if (FAILED(SubStatus)) {
+        PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+        BOOL    Success = FALSE;
+        OSVERSIONINFOEX verinfo;
+        int supported = 0;
+
+        // SubStatus 0x8009030E is not documented.  However, it appears
+        // to mean there is no TGT
+        if (SubStatus != 0x8009030E) {
+            bIsLsaError = TRUE;
+            goto cleanup;
+        }
+
+        verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+        GetVersionEx((OSVERSIONINFO *)&verinfo);
+        supported = (verinfo.dwMajorVersion > 5) || 
+            (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1);
+
+        // If we could not get a TGT from the cache we won't know what the
+        // Kerberos Domain should have been.  On Windows XP and 2003 Server
+        // we can extract it from the Security Logon Session Data.  However,
+        // the required fields are not supported on Windows 2000.  :(
+        if ( supported && GetSecurityLogonSessionData(&pSessionData) ) {
+            if ( pSessionData->DnsDomainName.Buffer ) {
+                Status = ConstructTicketRequest(pSessionData->DnsDomainName,
+                                                &pTicketRequest, &RequestSize);
+                if ( FAILED(Status) ) {
+                    goto cleanup;
+                }
+            } else {
+                bIsLsaError = TRUE;
+                goto cleanup;
+            }
+            LsaFreeReturnBuffer(pSessionData);
+        } else {
+            CHAR  UserDnsDomain[256];
+            WCHAR UnicodeUserDnsDomain[256];
+            UNICODE_STRING wrapper;
+            if ( !get_STRING_from_registry(HKEY_CURRENT_USER,
+                                          "Volatile Environment",
+                                          "USERDNSDOMAIN",
+                                           UserDnsDomain,
+                                           sizeof(UserDnsDomain)
+                                           ) )
+            {
+                goto cleanup;
+            }
+
+            ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256);
+            wrapper.Buffer = UnicodeUserDnsDomain;
+            wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR);
+            wrapper.MaximumLength = 256;
+
+            Status = ConstructTicketRequest(wrapper,
+                                             &pTicketRequest, &RequestSize);
+            if ( FAILED(Status) ) {
+                goto cleanup;
+            }
+        }
+    } else {
+#ifdef PURGE_ALL
+        purge_cache = 1;
+#else
+        switch (pTicketResponse->Ticket.SessionKey.KeyType) {
+        case KERB_ETYPE_DES_CBC_CRC:
+        case KERB_ETYPE_DES_CBC_MD4:
+        case KERB_ETYPE_DES_CBC_MD5:
+        case KERB_ETYPE_NULL:
+        case KERB_ETYPE_RC4_HMAC_NT: {
+            FILETIME Now, MinLife, EndTime, LocalEndTime;
+            __int64  temp;
+            // FILETIME is in units of 100 nano-seconds
+            // If obtained tickets are either expired or have a lifetime
+            // less than 20 minutes, retry ...
+            GetSystemTimeAsFileTime(&Now);
+            EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart;
+            EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart;
+            FileTimeToLocalFileTime(&EndTime, &LocalEndTime);
+            temp = Now.dwHighDateTime;
+            temp <<= 32;
+            temp = Now.dwLowDateTime;
+            temp += 1200 * 10000;
+            MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF);
+            MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
+            if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) {
+#ifdef ENABLE_PURGING
+                purge_cache = 1;
+#else
+                ignore_cache = 1;
+#endif /* ENABLE_PURGING */
+                break;
+            }
+            if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) {
+                ignore_cache = 1;
+                break;      // invalid, need to attempt a TGT request
+            }
+            goto cleanup;   // all done
+        }
+        case KERB_ETYPE_RC4_MD4:
+        default:
+            // not supported
+            ignore_cache = 1;
+            break;
+        }
+#endif /* PURGE_ALL */
+
+        Status = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName,
+                                        &pTicketRequest, &RequestSize);
+        if ( FAILED(Status) ) {
+            goto cleanup;
+        }
+
+        //
+        // Free the previous response buffer so we can get the new response.
+        //
+
+        if ( pTicketResponse ) {
+            memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+            LsaFreeReturnBuffer(pTicketResponse);
+            pTicketResponse = NULL;
+        }
+
+#ifdef ENABLE_PURGING
+        if ( purge_cache ) {
+            //
+            // Purge the existing tickets which we cannot use so new ones can 
+            // be requested.  It is not possible to purge just the TGT.  All
+            // service tickets must be purged.
+            //
+            PurgeMSTGT(LogonHandle, PackageId);
+        }
+#endif /* ENABLE_PURGING */
+    }
+    
+    //
+    // Intialize the request of the request.
+    //
+
+    pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+    pTicketRequest->LogonId.LowPart = 0;
+    pTicketRequest->LogonId.HighPart = 0;
+    // Note: pTicketRequest->TargetName set up above
+#ifdef ENABLE_PURGING
+    pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ? 
+                                     KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
+#else
+    pTicketRequest->CacheOptions = (ignore_cache ? KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
+#endif /* ENABLE_PURGING */
+    pTicketRequest->TicketFlags = 0L;
+    pTicketRequest->EncryptionType = 0L;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        pTicketRequest,
+        RequestSize,
+        &pTicketResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    if (FAILED(Status) || FAILED(SubStatus))
+    {
+        bIsLsaError = TRUE;
+        goto cleanup;
+    }
+
+    //
+    // Check to make sure the new tickets we received are of a type we support
+    //
+
+    switch (pTicketResponse->Ticket.SessionKey.KeyType) {
+    case KERB_ETYPE_DES_CBC_CRC:
+    case KERB_ETYPE_DES_CBC_MD4:
+    case KERB_ETYPE_DES_CBC_MD5:
+    case KERB_ETYPE_NULL:
+    case KERB_ETYPE_RC4_HMAC_NT:
+        goto cleanup;   // all done
+    case KERB_ETYPE_RC4_MD4:
+    default:
+        // not supported
+        break;
+    }
+
+
+    //
+    // Try once more but this time specify the Encryption Type
+    // (This will not store the retrieved tickets in the LSA cache)
+    //
+    pTicketRequest->EncryptionType = ENCTYPE_DES_CBC_CRC;
+    pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_DONT_USE_CACHE;
+
+    if ( pTicketResponse ) {
+        memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+        LsaFreeReturnBuffer(pTicketResponse);
+        pTicketResponse = NULL;
+    }
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        pTicketRequest,
+        RequestSize,
+        &pTicketResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    if (FAILED(Status) || FAILED(SubStatus))
+    {
+        bIsLsaError = TRUE;
+        goto cleanup;
+    }
+
+  cleanup:
+    if ( pTicketRequest )
+        LsaFreeReturnBuffer(pTicketRequest);
+
+    if (FAILED(Status) || FAILED(SubStatus))
+    {
+        if (bIsLsaError)
+        {
+            // XXX - Will be fixed later
+            if (FAILED(Status))
+                ShowLsaError("LsaCallAuthenticationPackage", Status);
+            if (FAILED(SubStatus))
+                ShowLsaError("LsaCallAuthenticationPackage", SubStatus);
+        }
+        else
+        {
+            ShowWinError("GetMSTGT", Status);
+        }
+
+        if (pTicketResponse) {
+            memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
+            LsaFreeReturnBuffer(pTicketResponse);
+            pTicketResponse = NULL;
+        }
+        return(FALSE);
+    }
+
+    *ticket = &(pTicketResponse->Ticket);
+    return(TRUE);
+}
+
+static BOOL
+GetQueryTktCacheResponse( HANDLE LogonHandle, ULONG PackageId,
+                          PKERB_QUERY_TKT_CACHE_RESPONSE * ppResponse)
+{
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+
+    KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
+    PKERB_QUERY_TKT_CACHE_RESPONSE pQueryResponse = NULL;
+    ULONG ResponseSize;
+    
+    CacheRequest.MessageType = KerbQueryTicketCacheMessage;
+    CacheRequest.LogonId.LowPart = 0;
+    CacheRequest.LogonId.HighPart = 0;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        &CacheRequest,
+        sizeof(CacheRequest),
+        &pQueryResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    if ( !(FAILED(Status) || FAILED(SubStatus)) ) {
+        *ppResponse = pQueryResponse;
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+FreeQueryResponse(PKERB_QUERY_TKT_CACHE_RESPONSE  pResponse)
+{
+    LsaFreeReturnBuffer(pResponse);
+}
+
+static BOOL
+GetMSCacheTicket( HANDLE LogonHandle, ULONG PackageId,
+                  PKERB_TICKET_CACHE_INFO tktinfo, PKERB_EXTERNAL_TICKET *ticket)
+{
+    NTSTATUS Status = 0;
+    NTSTATUS SubStatus = 0;
+    ULONG RequestSize;
+    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
+    PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
+    ULONG ResponseSize;
+
+    RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length;
+
+    pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
+    if (!pTicketRequest)
+        return FALSE;
+
+    pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
+    pTicketRequest->LogonId.LowPart = 0;
+    pTicketRequest->LogonId.HighPart = 0;
+    pTicketRequest->TargetName.Length = tktinfo->ServerName.Length;
+    pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length;
+    pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
+    memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length);
+    pTicketRequest->CacheOptions = 0;
+    pTicketRequest->EncryptionType = tktinfo->EncryptionType;
+    pTicketRequest->TicketFlags = tktinfo->TicketFlags;
+
+    Status = LsaCallAuthenticationPackage(
+        LogonHandle,
+        PackageId,
+        pTicketRequest,
+        RequestSize,
+        &pTicketResponse,
+        &ResponseSize,
+        &SubStatus
+        );
+
+    LsaFreeReturnBuffer(pTicketRequest);
+
+    if (FAILED(Status) || FAILED(SubStatus))
+        return(FALSE);
+    
+    /* otherwise return ticket */
+    *ticket = &(pTicketResponse->Ticket);
+    return(TRUE);
+
+}
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_close
+        (krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_destroy
+        (krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_end_seq_get
+        (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_generate_new
+        (krb5_context, krb5_ccache *id);
+
+static const char * KRB5_CALLCONV krb5_lcc_get_name
+        (krb5_context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_get_principal
+        (krb5_context, krb5_ccache id, krb5_principal *princ);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_initialize
+        (krb5_context, krb5_ccache id, krb5_principal princ);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_next_cred
+        (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor,
+        krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_resolve
+        (krb5_context, krb5_ccache *id, const char *residual);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_retrieve
+        (krb5_context, krb5_ccache id, krb5_flags whichfields,
+        krb5_creds *mcreds, krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_start_seq_get
+        (krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_store
+        (krb5_context, krb5_ccache id, krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV krb5_lcc_set_flags
+        (krb5_context, krb5_ccache id, krb5_flags flags);
+
+extern const krb5_cc_ops krb5_lcc_ops;
+
+krb5_error_code krb5_change_cache (void);
+
+#define KRB5_OK 0
+
+typedef struct _krb5_lcc_data {
+    HANDLE LogonHandle;
+    ULONG  PackageId;
+    char * cc_name;
+    krb5_principal princ;
+} krb5_lcc_data;
+
+typedef struct _krb5_lcc_cursor {
+    PKERB_QUERY_TKT_CACHE_RESPONSE  response;
+    int                             index;
+} krb5_lcc_cursor;
+
+
+/*
+ * Requires:
+ * residual is ignored
+ *
+ * Modifies:
+ * id
+ * 
+ * Effects:
+ * creates a file-based cred cache that will reside in the file
+ * residual.  The cache is not opened, but the filename is reserved.
+ * 
+ * Returns:
+ * A filled in krb5_ccache structure "id".
+ *
+ * Errors:
+ * KRB5_CC_NOMEM - there was insufficient memory to allocate the
+ * 
+ *             krb5_ccache.  id is undefined.
+ * permission errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual)
+{
+    krb5_ccache lid;
+    krb5_lcc_data *data;
+    HANDLE LogonHandle;
+    ULONG  PackageId;
+    KERB_EXTERNAL_TICKET *msticket;
+
+    if (!IsKerberosLogon())
+        return KRB5_FCC_NOFILE;
+
+    if(!PackageConnectLookup(&LogonHandle, &PackageId))
+        return KRB5_FCC_NOFILE;
+
+    lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
+    if (lid == NULL) {
+        CloseHandle(LogonHandle);
+        return KRB5_CC_NOMEM;
+    }
+
+    lid->ops = &krb5_lcc_ops;
+
+    lid->data = (krb5_pointer) malloc(sizeof(krb5_lcc_data));
+    if (lid->data == NULL) {
+        krb5_xfree(lid);
+        CloseHandle(LogonHandle);
+        return KRB5_CC_NOMEM;
+    }
+
+    lid->magic = KV5M_CCACHE;
+    data = (krb5_lcc_data *)lid->data;    
+    data->LogonHandle = LogonHandle;
+    data->PackageId = PackageId;
+
+    data->cc_name = (char *)malloc(strlen(residual)+1);
+    if (data->cc_name == NULL) {
+        krb5_xfree(lid->data);
+        krb5_xfree(lid);
+        CloseHandle(LogonHandle);
+        return KRB5_CC_NOMEM;
+    }
+    strcpy(data->cc_name, residual);
+
+    /*
+     * we must obtain a tgt from the cache in order to determine the principal
+     */
+    if (GetMSTGT(data->LogonHandle, data->PackageId, &msticket)) {
+        /* convert the ticket */
+        krb5_creds creds;
+        MSCredToMITCred(msticket, context, &creds);
+        LsaFreeReturnBuffer(msticket);
+
+        krb5_copy_principal(context, creds.client, &data->princ);
+        krb5_free_cred_contents(context,&creds);
+    } else {
+        data->princ = 0;
+    }
+
+    /*
+     * other routines will get errors on open, and callers must expect them,
+     * if cache is non-existent/unusable 
+     */
+    *id = lid;
+    return KRB5_OK;
+}
+
+/*
+ *  not supported
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+    return KRB5_CC_READONLY;
+}
+
+
+/*
+ * Modifies:
+ * id
+ *
+ * Effects:
+ * Closes the microsoft lsa cache, invalidates the id, and frees any resources
+ * associated with the cache.
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_close(krb5_context context, krb5_ccache id)
+{
+    register int closeval = KRB5_OK;
+    register krb5_lcc_data *data = (krb5_lcc_data *) id->data;
+
+    CloseHandle(data->LogonHandle);
+
+    krb5_xfree(data);
+    krb5_xfree(id);
+
+    return closeval;
+}
+
+/*
+ * Effects:
+ * Destroys the contents of id.
+ *
+ * Errors:
+ * system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_destroy(krb5_context context, krb5_ccache id)
+{
+    register krb5_lcc_data *data = (krb5_lcc_data *) id->data;
+
+    return PurgeMSTGT(data->LogonHandle, data->PackageId) ? KRB5_FCC_INTERNAL : KRB5_OK;
+}
+
+/*
+ * Effects:
+ * Prepares for a sequential search of the credentials cache.
+ * Returns a krb5_cc_cursor to be used with krb5_lcc_next_cred and
+ * krb5_lcc_end_seq_get.
+ *
+ * If the cache is modified between the time of this call and the time
+ * of the final krb5_lcc_end_seq_get, the results are undefined.
+ *
+ * Errors:
+ * KRB5_CC_NOMEM
+ * KRB5_FCC_INTERNAL - system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+    krb5_lcc_cursor *lcursor;
+    krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+
+    lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor));
+    if (lcursor == NULL)
+        return KRB5_CC_NOMEM;
+
+    if ( !GetQueryTktCacheResponse(data->LogonHandle, data->PackageId, &lcursor->response) ) {
+        free(lcursor);
+        KRB5_FCC_INTERNAL;
+    }
+    lcursor->index = 0;
+    *cursor = (krb5_cc_cursor) lcursor;
+
+    return KRB5_OK;
+}
+
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_lcc_start_seq_get.
+ *
+ * Modifes:
+ * cursor
+ * 
+ * Effects:
+ * Fills in creds with the TGT obtained from the MS LSA
+ *
+ * The cursor is updated to indicate TGT retrieval
+ *
+ * Errors:
+ * KRB5_CC_END
+ * KRB5_FCC_INTERNAL - system errors
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, krb5_creds *creds)
+{
+    krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
+    krb5_lcc_data *data = (krb5_lcc_data *)id->data;
+    KERB_EXTERNAL_TICKET *msticket;
+
+    if ( lcursor->index >= lcursor->response->CountOfTickets )
+        return KRB5_CC_END;
+
+    if (!GetMSCacheTicket(data->LogonHandle, data->PackageId,
+                          &lcursor->response->Tickets[lcursor->index++],&msticket))
+        return KRB5_FCC_INTERNAL;
+
+    /* convert the ticket */
+    MSCredToMITCred(msticket, context, creds);
+
+    LsaFreeReturnBuffer(msticket);
+    return KRB5_OK;
+}
+
+/*
+ * Requires:
+ * cursor is a krb5_cc_cursor originally obtained from
+ * krb5_lcc_start_seq_get.
+ *
+ * Modifies:
+ * id, cursor
+ *
+ * Effects:
+ * Finishes sequential processing of the file credentials ccache id,
+ * and invalidates the cursor (it must never be used after this call).
+ */
+/* ARGSUSED */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+    krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor;
+
+    LsaFreeReturnBuffer(lcursor->response);
+    free(*cursor);
+    return KRB5_OK;
+}
+
+
+/*
+ * Errors:
+ * KRB5_CC_READONLY - not supported
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_generate_new (krb5_context context, krb5_ccache *id)
+{
+    return KRB5_CC_READONLY;
+}
+
+/*
+ * Requires:
+ * id is a ms lsa credential cache
+ * 
+ * Returns:
+ *   The ccname specified during the krb5_lcc_resolve call
+ */
+static const char * KRB5_CALLCONV
+krb5_lcc_get_name (krb5_context context, krb5_ccache id)
+{
+    return (char *) ((krb5_lcc_data *) id->data)->cc_name;
+}
+
+/*
+ * Modifies:
+ * id, princ
+ *
+ * Effects:
+ * Retrieves the primary principal from id, as set with
+ * krb5_lcc_initialize.  The principal is returned is allocated
+ * storage that must be freed by the caller via krb5_free_principal.
+ *
+ * Errors:
+ * system errors
+ * KRB5_CC_NOMEM
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
+{
+    krb5_error_code kret = KRB5_OK;
+
+    /* obtain principal */
+    return krb5_copy_principal(context, ((krb5_lcc_data *) id->data)->princ, princ);
+}
+
+     
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields, 
+                  krb5_creds *mcreds, krb5_creds *creds)
+{
+    return krb5_cc_retrieve_cred_default (context, id, whichfields,
+                                         mcreds, creds);
+}
+
+
+/*
+ * Errors:
+ * KRB5_CC_READONLY - not supported
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
+{
+    return KRB5_CC_READONLY;
+}
+
+
+/*
+ * Effects:
+ *   None - ignored
+ */
+static krb5_error_code KRB5_CALLCONV
+krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+    return KRB5_OK;
+}
+
+const krb5_cc_ops krb5_lcc_ops = {
+     0,
+     "MSLSA",
+     krb5_lcc_get_name,
+     krb5_lcc_resolve,
+     krb5_lcc_generate_new,
+     krb5_lcc_initialize,
+     krb5_lcc_destroy,
+     krb5_lcc_close,
+     krb5_lcc_store,
+     krb5_lcc_retrieve,
+     krb5_lcc_get_principal,
+     krb5_lcc_start_seq_get,
+     krb5_lcc_next_cred,
+     krb5_lcc_end_seq_get,
+     NULL, /* krb5_lcc_remove, */
+     krb5_lcc_set_flags
+};
+#endif /* _WIN32 */
\ No newline at end of file
index cfe96ec05743313b3924c6cbce08818a7812405c..f17870663ad20c562242cad331cd733f4c00014e 100644 (file)
@@ -38,14 +38,20 @@ struct krb5_cc_typelist
  };
 extern const krb5_cc_ops krb5_mcc_ops;
 
+#ifdef _WIN32
+extern const krb5_cc_ops krb5_lcc_ops;
+static struct krb5_cc_typelist cc_lcc_entry = { &krb5_lcc_ops, NULL };
+static struct krb5_cc_typelist cc_mcc_entry = { &krb5_mcc_ops, &cc_lcc_entry };
+#else
 static struct krb5_cc_typelist cc_mcc_entry = { &krb5_mcc_ops, NULL };
+#endif
 
 static struct krb5_cc_typelist cc_fcc_entry = { &krb5_cc_file_ops,
                                                &cc_mcc_entry };
 
-
 static struct krb5_cc_typelist *cc_typehead = &cc_fcc_entry;
 
+
 /*
  * Register a new credentials cache type
  * If override is set, replace any existing ccache with that type tag
index 2eb6925ac5dd5d24c81c89b5b68b3ad53439219c..c5f1371b85e6d8e4b389827b456152f67c6b1a7e 100644 (file)
@@ -1,3 +1,7 @@
+2003-12-12  Jeffrey Altman <jaltman@mit.edu>
+
+    * krb5_err.et (KRB5_CC_READONLY) new ccache error code
+
 2003-07-19  Ezra Peisach  <epeisach@mit.edu>
 
        * init_ets.c (krb5_init_ets): Only initialize error tables once -
index 66bdebbfb4b0a78322f9d9043691369296164db9..622143b7de2a1483ae9ea589d59298340e477bbc 100644 (file)
@@ -338,4 +338,6 @@ error_code KRB5_ERR_BAD_S2K_PARAMS, "Invalid key generation parameters from KDC"
 
 error_code KRB5_ERR_NO_SERVICE,        "service not available"
 
+error_code KRB5_CC_READONLY,    "Ccache function not supported: read-only ccache type"
+
 end
index f177bb41d9ad07db1e8c1456dc69b1ae9616fe16..71577d979d82693ab9af2d568f6be300ad5d2999 100644 (file)
@@ -1,3 +1,14 @@
+2003-12-11  Jeffrey Altman <jaltman@mit.edu>
+
+    * ms2mit.c, Makefile.in:
+
+    Remove all of the code that manipulates the MS LSA cache.  Instead
+    of reading in the TGT directly we now take advantage of the new
+    "MSLSA:" krb5_ccache type.  We open the MS LSA cache as a read-only
+    ccache and copy it to the default ccache for the system.
+
+    This removes the dependency on secur32.dll from this file.
+
 2003-10-21  Jeffrey Altman <jaltman@mit.edu>
 
     * ms2mit.c:
index a7c6c1ed961fc8b39c4f9e729cb0884be5a501bc..45004e14a44ebbcb5e9b9be35af1ef3e4ba43f5a 100644 (file)
@@ -13,7 +13,7 @@ PROG_LIBPATH=-L$(TOPLIBD) -L$(KRB5_LIBDIR)
 all-windows:: $(OUTPRE)ms2mit.exe
 
 $(OUTPRE)ms2mit.exe: $(OUTPRE)ms2mit.obj
-    link $(EXE_LINKOPTS) -out:$@ $(OUTPRE)ms2mit.obj user32.lib secur32.lib advapi32.lib $(KLIB) $(CLIB)
+    link $(EXE_LINKOPTS) -out:$@ $(OUTPRE)ms2mit.obj user32.lib advapi32.lib $(KLIB) $(CLIB)
 
 install::
        copy $(OUTPRE)ms2mit.exe $(DESTDIR)
index 12e028e0bc37467b5cd18d8e8865c88b1883d02b..4674a4b71228e822278dd18954f8ae4bb0c0ac08 100644 (file)
@@ -2,28 +2,6 @@
  * ms2mit.c
  *
  */
-/***********************************************************
-        Copyright 2000 by Carnegie Mellon University
-
-                      All Rights Reserved
-
-Permission to use, copy, modify, and distribute this software and its
-documentation for any purpose and without fee is hereby granted,
-provided that the above copyright notice appear in all copies and that
-both that copyright notice and this permission notice appear in
-supporting documentation, and that the name of Carnegie Mellon
-University not be used in advertising or publicity pertaining to
-distribution of the software without specific, written prior
-permission.
-
-CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
-THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
-ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-******************************************************************/
 /*
  * Copyright (C) 2003 by the Massachusetts Institute of Technology.
  * All rights reserved.
@@ -49,797 +27,7 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
  */
 
-#define UNICODE
-#define _UNICODE
-
-#include <windows.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <conio.h>
-#include <time.h>
-#define SECURITY_WIN32
-#include <security.h>
-#include <ntsecapi.h>
-
-#include <krb5.h>
-#include <com_err.h>
-#include <assert.h>
-
-VOID
-ShowWinError(
-    LPSTR szAPI,
-    DWORD dwError
-    )
-{
-#define MAX_MSG_SIZE 256
-
-    // TODO - Write errors to event log so that scripts that don't
-    // check for errors will still get something in the event log
-
-    WCHAR szMsgBuf[MAX_MSG_SIZE];
-    DWORD dwRes;
-
-    printf("Error calling function %s: %lu\n", szAPI, dwError);
-
-    dwRes = FormatMessage (
-        FORMAT_MESSAGE_FROM_SYSTEM,
-        NULL,
-        dwError,
-        MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US),
-        szMsgBuf,
-        MAX_MSG_SIZE,
-        NULL);
-    if (0 == dwRes) {
-        printf("FormatMessage failed with %d\n", GetLastError());
-        ExitProcess(EXIT_FAILURE);
-    }
-
-    printf("%S",szMsgBuf);
-}
-
-VOID
-ShowLsaError(
-    LPSTR szAPI,
-    NTSTATUS Status
-    )
-{
-    //
-    // Convert the NTSTATUS to Winerror. Then call ShowWinError().
-    //
-    ShowWinError(szAPI, LsaNtStatusToWinError(Status));
-}
-
-
-
-BOOL
-WINAPI
-UnicodeToANSI(
-    LPTSTR lpInputString,
-    LPSTR lpszOutputString,
-    int nOutStringLen
-    )
-{
-#ifndef WIN32S
-    CPINFO CodePageInfo;
-
-    GetCPInfo(CP_ACP, &CodePageInfo);
-
-    if (CodePageInfo.MaxCharSize > 1)
-        // Only supporting non-Unicode strings
-        return FALSE;
-    else if (((LPBYTE) lpInputString)[1] == '\0')
-    {
-        // Looks like unicode, better translate it
-        WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) lpInputString, -1,
-                            lpszOutputString, nOutStringLen, NULL, NULL);
-    }
-    else
-        lstrcpyA(lpszOutputString, (LPSTR) lpInputString);
-#else
-    lstrcpy(lpszOutputString, (LPSTR) lpInputString);
-#endif
-    return TRUE;
-}  // UnicodeToANSI
-
-VOID
-WINAPI
-ANSIToUnicode(
-    LPSTR  lpInputString,
-    LPTSTR lpszOutputString,
-    int nOutStringLen
-    )
-{
-
-#ifndef WIN32S
-    CPINFO CodePageInfo;
-
-    lstrcpy(lpszOutputString, (LPTSTR) lpInputString);
-
-    GetCPInfo(CP_ACP, &CodePageInfo);
-
-    if (CodePageInfo.MaxCharSize > 1)
-        // It must already be a Unicode string
-        return;
-    else if (((LPBYTE) lpInputString)[1] != '\0')
-    {
-        // Looks like ANSI, better translate it
-        MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpInputString, -1,
-                            (LPWSTR) lpszOutputString, nOutStringLen);
-    }
-    else
-        lstrcpy(lpszOutputString, (LPTSTR) lpInputString);
-#endif
-}  // ANSIToUnicode
-
-
-void
-MSPrincToMITPrinc(
-    KERB_EXTERNAL_NAME *msprinc,
-    WCHAR *realm,
-    krb5_context context,
-    krb5_principal *principal
-    )
-{
-    WCHAR princbuf[512],tmpbuf[128];
-    char aname[512];
-    USHORT i;
-    princbuf[0]=0;
-    for (i=0;i<msprinc->NameCount;i++) {
-        wcsncpy(tmpbuf, msprinc->Names[i].Buffer,
-                msprinc->Names[i].Length/sizeof(WCHAR));
-        tmpbuf[msprinc->Names[i].Length/sizeof(WCHAR)]=0;
-        if (princbuf[0])
-            wcscat(princbuf, L"/");
-        wcscat(princbuf, tmpbuf);
-    }
-    wcscat(princbuf, L"@");
-    wcscat(princbuf, realm);
-    UnicodeToANSI(princbuf, aname, sizeof(aname));
-    krb5_parse_name(context, aname, principal);
-}
-
-
-time_t
-FileTimeToUnixTime(
-    LARGE_INTEGER *ltime
-    )
-{
-    FILETIME filetime, localfiletime;
-    SYSTEMTIME systime;
-    struct tm utime;
-    filetime.dwLowDateTime=ltime->LowPart;
-    filetime.dwHighDateTime=ltime->HighPart;
-    FileTimeToLocalFileTime(&filetime, &localfiletime);
-    FileTimeToSystemTime(&localfiletime, &systime);
-    utime.tm_sec=systime.wSecond;
-    utime.tm_min=systime.wMinute;
-    utime.tm_hour=systime.wHour;
-    utime.tm_mday=systime.wDay;
-    utime.tm_mon=systime.wMonth-1;
-    utime.tm_year=systime.wYear-1900;
-    utime.tm_isdst=-1;
-    return(mktime(&utime));
-}
-
-void
-MSSessionKeyToMITKeyblock(
-    KERB_CRYPTO_KEY *mskey,
-    krb5_context context,
-    krb5_keyblock *keyblock
-    )
-{
-    krb5_keyblock tmpblock;
-    tmpblock.magic=KV5M_KEYBLOCK;
-    tmpblock.enctype=mskey->KeyType;
-    tmpblock.length=mskey->Length;
-    tmpblock.contents=mskey->Value;
-    krb5_copy_keyblock_contents(context, &tmpblock, keyblock);
-}
-
-
-void
-MSFlagsToMITFlags(
-    ULONG msflags,
-    ULONG *mitflags
-    )
-{
-    *mitflags=msflags;
-}
-
-void
-MSTicketToMITTicket(
-    KERB_EXTERNAL_TICKET *msticket,
-    krb5_context context,
-    krb5_data *ticket
-    )
-{
-    krb5_data tmpdata, *newdata;
-    tmpdata.magic=KV5M_DATA;
-    tmpdata.length=msticket->EncodedTicketSize;
-    tmpdata.data=msticket->EncodedTicket;
-    // todo: fix this up a little. this is ugly and will break krb_free_data()
-    krb5_copy_data(context, &tmpdata, &newdata);
-    memcpy(ticket, newdata, sizeof(krb5_data));
-}
-
-void
-MSCredToMITCred(
-    KERB_EXTERNAL_TICKET *msticket,
-    krb5_context context,
-    krb5_creds *creds
-    )
-{
-    WCHAR wtmp[128];
-    ZeroMemory(creds, sizeof(krb5_creds));
-    creds->magic=KV5M_CREDS;
-    wcsncpy(wtmp, msticket->TargetDomainName.Buffer,
-            msticket->TargetDomainName.Length/sizeof(WCHAR));
-    wtmp[msticket->TargetDomainName.Length/sizeof(WCHAR)]=0;
-    MSPrincToMITPrinc(msticket->ClientName, wtmp, context, &creds->client);
-    wcsncpy(wtmp, msticket->DomainName.Buffer,
-            msticket->DomainName.Length/sizeof(WCHAR));
-    wtmp[msticket->DomainName.Length/sizeof(WCHAR)]=0;
-    MSPrincToMITPrinc(msticket->ServiceName, wtmp, context, &creds->server);
-    MSSessionKeyToMITKeyblock(&msticket->SessionKey, context, 
-                              &creds->keyblock);
-    MSFlagsToMITFlags(msticket->TicketFlags, &creds->ticket_flags);
-    creds->times.starttime=FileTimeToUnixTime(&msticket->StartTime);
-    creds->times.endtime=FileTimeToUnixTime(&msticket->EndTime);
-    creds->times.renew_till=FileTimeToUnixTime(&msticket->RenewUntil);
-
-    /* MS Tickets are addressless.  MIT requires an empty address
-     * not a NULL list of addresses.
-     */
-    creds->addresses = (krb5_address **)malloc(sizeof(krb5_address *));
-    memset(creds->addresses, 0, sizeof(krb5_address *));
-
-    MSTicketToMITTicket(msticket, context, &creds->ticket);
-}
-
-BOOL
-PackageConnectLookup(
-    HANDLE *pLogonHandle,
-    ULONG *pPackageId
-    )
-{
-    LSA_STRING Name;
-    NTSTATUS Status;
-
-    Status = LsaConnectUntrusted(
-        pLogonHandle
-        );
-
-    if (FAILED(Status))
-    {
-        ShowLsaError("LsaConnectUntrusted", Status);
-        return FALSE;
-    }
-
-    Name.Buffer = MICROSOFT_KERBEROS_NAME_A;
-    Name.Length = strlen(Name.Buffer);
-    Name.MaximumLength = Name.Length + 1;
-
-    Status = LsaLookupAuthenticationPackage(
-        *pLogonHandle,
-        &Name,
-        pPackageId
-        );
-
-    if (FAILED(Status))
-    {
-        ShowLsaError("LsaLookupAuthenticationPackage", Status);
-        return FALSE;
-    }
-
-    return TRUE;
-
-}
-
-
-DWORD
-ConcatenateUnicodeStrings(
-    UNICODE_STRING *pTarget,
-    UNICODE_STRING Source1,
-    UNICODE_STRING Source2
-    )
-{
-    //
-    // The buffers for Source1 and Source2 cannot overlap pTarget's
-    // buffer.  Source1.Length + Source2.Length must be <= 0xFFFF,
-    // otherwise we overflow...
-    //
-
-    USHORT TotalSize = Source1.Length + Source2.Length;
-    PBYTE buffer = (PBYTE) pTarget->Buffer;
-
-    if (TotalSize > pTarget->MaximumLength)
-        return ERROR_INSUFFICIENT_BUFFER;
-
-    pTarget->Length = TotalSize;
-    memcpy(buffer, Source1.Buffer, Source1.Length);
-    memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length);
-    return ERROR_SUCCESS;
-}
-
-static BOOL
-get_STRING_from_registry(
-    HKEY hBaseKey,
-    char * key,
-    char * value,
-    char * outbuf,
-    DWORD  outlen
-    )
-{
-    HKEY hKey;
-    DWORD dwCount;
-    LONG rc;
-
-       if (!outbuf || outlen == 0)
-               return FALSE;
-
-    rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey);
-    if (rc)
-        return FALSE;
-
-    dwCount = outlen;
-    rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount);
-    RegCloseKey(hKey);
-
-    return rc?FALSE:TRUE;
-}
-
-static BOOL
-GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData)
-{
-    NTSTATUS Status = 0;
-    HANDLE  TokenHandle;
-    TOKEN_STATISTICS Stats;
-    DWORD   ReqLen;
-    BOOL    Success;
-
-    if (!ppSessionData)
-        return FALSE;
-    *ppSessionData = NULL;
-
-    Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle );
-    if ( !Success )
-        return FALSE;
-
-    Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen );
-    CloseHandle( TokenHandle );
-    if ( !Success )
-        return FALSE;
-
-    Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData );
-    if ( FAILED(Status) || !ppSessionData )
-        return FALSE;
-
-    return TRUE;
-}
-
-//
-// IsKerberosLogon() does not validate whether or not there are valid tickets in the 
-// cache.  It validates whether or not it is reasonable to assume that if we 
-// attempted to retrieve valid tickets we could do so.  Microsoft does not 
-// automatically renew expired tickets.  Therefore, the cache could contain
-// expired or invalid tickets.  Microsoft also caches the user's password 
-// and will use it to retrieve new TGTs if the cache is empty and tickets
-// are requested.
-
-static BOOL
-IsKerberosLogon(VOID)
-{
-    PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
-    BOOL    Success = FALSE;
-
-    if ( GetSecurityLogonSessionData(&pSessionData) ) {
-        if ( pSessionData->AuthenticationPackage.Buffer ) {
-            WCHAR buffer[256];
-            WCHAR *usBuffer;
-            int usLength;
-
-            Success = FALSE;
-            usBuffer = (pSessionData->AuthenticationPackage).Buffer;
-            usLength = (pSessionData->AuthenticationPackage).Length;
-            if (usLength < 256)
-            {
-                lstrcpyn (buffer, usBuffer, usLength);
-                lstrcat (buffer,L"");
-                if ( !lstrcmp(L"Kerberos",buffer) )
-                    Success = TRUE;
-            }
-        }
-        LsaFreeReturnBuffer(pSessionData);
-    }
-    return Success;
-}
-
-static NTSTATUS
-ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest,
-                       ULONG * outSize)
-{
-    NTSTATUS Status;
-    UNICODE_STRING TargetPrefix;
-    USHORT TargetSize;
-    ULONG RequestSize;
-    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL;
-
-    *outRequest = NULL;
-    *outSize = 0;
-
-    //
-    // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we
-    // can easily concatenate it later.
-    //
-
-    TargetPrefix.Buffer = L"krbtgt/";
-    TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR);
-    TargetPrefix.MaximumLength = TargetPrefix.Length;
-
-    //
-    // We will need to concatenate the "krbtgt/" prefix and the 
-    // Logon Session's DnsDomainName into our request's target name.
-    //
-    // Therefore, first compute the necessary buffer size for that.
-    //
-    // Note that we might theoretically have integer overflow.
-    //
-
-    TargetSize = TargetPrefix.Length + DomainName.Length;
-
-    //
-    // The ticket request buffer needs to be a single buffer.  That buffer
-    // needs to include the buffer for the target name.
-    //
-
-    RequestSize = sizeof(*pTicketRequest) + TargetSize;
-
-    //
-    // Allocate the request buffer and make sure it's zero-filled.
-    //
-
-    pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize);
-    if (!pTicketRequest)
-        return GetLastError();
-
-    //
-    // Concatenate the target prefix with the previous reponse's
-    // target domain.
-    //
-
-    pTicketRequest->TargetName.Length = 0;
-    pTicketRequest->TargetName.MaximumLength = TargetSize;
-    pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1);
-    Status = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName),
-                                        TargetPrefix,
-                                        DomainName);
-    assert(SUCCEEDED(Status));
-    *outRequest = pTicketRequest;
-    *outSize    = RequestSize;
-    return Status;
-}
-
-//
-// #define ENABLE_PURGING
-// to allow the purging of expired tickets from LSA cache.  This is necessary
-// to force the retrieval of new TGTs.  Microsoft does not appear to retrieve
-// new tickets when they expire.  Instead they continue to accept the expired
-// tickets.  I do not want to enable purging of the LSA cache without testing
-// the side effects in a Windows domain with a machine which has been suspended,
-// removed from the network, and resumed after ticket expiration.
-//
-static BOOL
-GetMSTGT(
-    HANDLE LogonHandle,
-    ULONG PackageId,
-    KERB_EXTERNAL_TICKET **ticket
-    )
-{
-    //
-    // INVARIANTS:
-    //
-    //   (FAILED(Status) || FAILED(SubStatus)) ==> error
-    //   bIsLsaError ==> LsaCallAuthenticationPackage() error
-    //
-
-    BOOL bIsLsaError = FALSE;
-    NTSTATUS Status = 0;
-    NTSTATUS SubStatus = 0;
-
-    KERB_QUERY_TKT_CACHE_REQUEST CacheRequest;
-    PKERB_RETRIEVE_TKT_REQUEST pTicketRequest;
-    PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL;
-    ULONG RequestSize;
-    ULONG ResponseSize;
-#ifdef ENABLE_PURGING
-    KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest;
-    int    purge_cache = 0;
-#endif /* ENABLE_PURGING */
-    int    ignore_cache = 0;
-
-    CacheRequest.MessageType = KerbRetrieveTicketMessage;
-    CacheRequest.LogonId.LowPart = 0;
-    CacheRequest.LogonId.HighPart = 0;
-
-    Status = LsaCallAuthenticationPackage(
-        LogonHandle,
-        PackageId,
-        &CacheRequest,
-        sizeof(CacheRequest),
-        &pTicketResponse,
-        &ResponseSize,
-        &SubStatus
-        );
-
-    if (FAILED(Status))
-    {
-        // if the call to LsaCallAuthenticationPackage failed we cannot
-        // perform any queries most likely because the Kerberos package 
-        // is not available or we do not have access
-        bIsLsaError = TRUE;
-        goto cleanup;
-    }
-
-    if (FAILED(SubStatus)) {
-        PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
-        BOOL    Success = FALSE;
-        OSVERSIONINFOEX verinfo;
-        int supported = 0;
-
-        // SubStatus 0x8009030E is not documented.  However, it appears
-        // to mean there is no TGT
-        if (SubStatus != 0x8009030E) {
-            bIsLsaError = TRUE;
-            goto cleanup;
-        }
-
-        verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
-        GetVersionEx((OSVERSIONINFO *)&verinfo);
-        supported = (verinfo.dwMajorVersion > 5) || 
-            (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1);
-
-        // If we could not get a TGT from the cache we won't know what the
-        // Kerberos Domain should have been.  On Windows XP and 2003 Server
-        // we can extract it from the Security Logon Session Data.  However,
-        // the required fields are not supported on Windows 2000.  :(
-        if ( supported && GetSecurityLogonSessionData(&pSessionData) ) {
-            if ( pSessionData->DnsDomainName.Buffer ) {
-                Status = ConstructTicketRequest(pSessionData->DnsDomainName,
-                                                &pTicketRequest, &RequestSize);
-                if ( FAILED(Status) ) {
-                    goto cleanup;
-                }
-            } else {
-                bIsLsaError = TRUE;
-                goto cleanup;
-            }
-            LsaFreeReturnBuffer(pSessionData);
-        } else {
-            CHAR  UserDnsDomain[256];
-            WCHAR UnicodeUserDnsDomain[256];
-            UNICODE_STRING wrapper;
-            if ( !get_STRING_from_registry(HKEY_CURRENT_USER,
-                                          "Volatile Environment",
-                                          "USERDNSDOMAIN",
-                                           UserDnsDomain,
-                                           sizeof(UserDnsDomain)
-                                           ) )
-            {
-                goto cleanup;
-            }
-
-            ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256);
-            wrapper.Buffer = UnicodeUserDnsDomain;
-            wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR);
-            wrapper.MaximumLength = 256;
-
-            Status = ConstructTicketRequest(wrapper,
-                                             &pTicketRequest, &RequestSize);
-            if ( FAILED(Status) ) {
-                goto cleanup;
-            }
-        }
-    } else {
-#ifdef PURGE_ALL
-        purge_cache = 1;
-#else
-        switch (pTicketResponse->Ticket.SessionKey.KeyType) {
-        case KERB_ETYPE_DES_CBC_CRC:
-        case KERB_ETYPE_DES_CBC_MD4:
-        case KERB_ETYPE_DES_CBC_MD5:
-        case KERB_ETYPE_NULL:
-        case KERB_ETYPE_RC4_HMAC_NT: {
-            FILETIME Now, MinLife, EndTime, LocalEndTime;
-            __int64  temp;
-            // FILETIME is in units of 100 nano-seconds
-            // If obtained tickets are either expired or have a lifetime
-            // less than 20 minutes, retry ...
-            GetSystemTimeAsFileTime(&Now);
-            EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart;
-            EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart;
-            FileTimeToLocalFileTime(&EndTime, &LocalEndTime);
-            temp = Now.dwHighDateTime;
-            temp <<= 32;
-            temp = Now.dwLowDateTime;
-            temp += 1200 * 10000;
-            MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF);
-            MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
-            if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) {
-#ifdef ENABLE_PURGING
-                purge_cache = 1;
-#else
-                ignore_cache = 1;
-#endif /* ENABLE_PURGING */
-                break;
-            }
-            if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) {
-                ignore_cache = 1;
-                break;      // invalid, need to attempt a TGT request
-            }
-            goto cleanup;   // all done
-        }
-        case KERB_ETYPE_RC4_MD4:
-        default:
-            // not supported
-            ignore_cache = 1;
-            break;
-        }
-#endif /* PURGE_ALL */
-
-        Status = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName,
-                                        &pTicketRequest, &RequestSize);
-        if ( FAILED(Status) ) {
-            goto cleanup;
-        }
-
-        //
-        // Free the previous response buffer so we can get the new response.
-        //
-
-        if ( pTicketResponse ) {
-            memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
-            LsaFreeReturnBuffer(pTicketResponse);
-            pTicketResponse = NULL;
-        }
-
-#ifdef ENABLE_PURGING
-        if ( purge_cache ) {
-            //
-            // Purge the existing tickets which we cannot use so new ones can 
-            // be requested.  It is not possible to purge just the TGT.  All
-            // service tickets must be purged.
-            //
-            PurgeRequest.MessageType = KerbPurgeTicketCacheMessage;
-            PurgeRequest.LogonId.LowPart = 0;
-            PurgeRequest.LogonId.HighPart = 0;
-            PurgeRequest.ServerName.Buffer = L"";
-            PurgeRequest.ServerName.Length = 0;
-            PurgeRequest.ServerName.MaximumLength = 0;
-            PurgeRequest.RealmName.Buffer = L"";
-            PurgeRequest.RealmName.Length = 0;
-            PurgeRequest.RealmName.MaximumLength = 0;
-            Status = LsaCallAuthenticationPackage(LogonHandle,
-                                                    PackageId,
-                                                    &PurgeRequest,
-                                                    sizeof(PurgeRequest),
-                                                    NULL,
-                                                    NULL,
-                                                    &SubStatus
-                                                    );
-        }
-#endif /* ENABLE_PURGING */
-    }
-    
-    //
-    // Intialize the request of the request.
-    //
-
-    pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage;
-    pTicketRequest->LogonId.LowPart = 0;
-    pTicketRequest->LogonId.HighPart = 0;
-    // Note: pTicketRequest->TargetName set up above
-#ifdef ENABLE_PURGING
-    pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ? 
-                                     KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
-#else
-    pTicketRequest->CacheOptions = (ignore_cache ? KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L);
-#endif /* ENABLE_PURGING */
-    pTicketRequest->TicketFlags = 0L;
-    pTicketRequest->EncryptionType = 0L;
-
-    Status = LsaCallAuthenticationPackage(
-        LogonHandle,
-        PackageId,
-        pTicketRequest,
-        RequestSize,
-        &pTicketResponse,
-        &ResponseSize,
-        &SubStatus
-        );
-
-    if (FAILED(Status) || FAILED(SubStatus))
-    {
-        bIsLsaError = TRUE;
-        goto cleanup;
-    }
-
-    //
-    // Check to make sure the new tickets we received are of a type we support
-    //
-
-    switch (pTicketResponse->Ticket.SessionKey.KeyType) {
-    case KERB_ETYPE_DES_CBC_CRC:
-    case KERB_ETYPE_DES_CBC_MD4:
-    case KERB_ETYPE_DES_CBC_MD5:
-    case KERB_ETYPE_NULL:
-    case KERB_ETYPE_RC4_HMAC_NT:
-        goto cleanup;   // all done
-    case KERB_ETYPE_RC4_MD4:
-    default:
-        // not supported
-        break;
-    }
-
-
-    //
-    // Try once more but this time specify the Encryption Type
-    // (This will not store the retrieved tickets in the LSA cache)
-    //
-    pTicketRequest->EncryptionType = ENCTYPE_DES_CBC_CRC;
-    pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_DONT_USE_CACHE;
-
-    if ( pTicketResponse ) {
-        memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
-        LsaFreeReturnBuffer(pTicketResponse);
-        pTicketResponse = NULL;
-    }
-
-    Status = LsaCallAuthenticationPackage(
-        LogonHandle,
-        PackageId,
-        pTicketRequest,
-        RequestSize,
-        &pTicketResponse,
-        &ResponseSize,
-        &SubStatus
-        );
-
-    if (FAILED(Status) || FAILED(SubStatus))
-    {
-        bIsLsaError = TRUE;
-        goto cleanup;
-    }
-
-  cleanup:
-    if ( pTicketRequest )
-        LsaFreeReturnBuffer(pTicketRequest);
-
-    if (FAILED(Status) || FAILED(SubStatus))
-    {
-        if (bIsLsaError)
-        {
-            // XXX - Will be fixed later
-            if (FAILED(Status))
-                ShowLsaError("LsaCallAuthenticationPackage", Status);
-            if (FAILED(SubStatus))
-                ShowLsaError("LsaCallAuthenticationPackage", SubStatus);
-        }
-        else
-        {
-            ShowWinError("GetMSTGT", Status);
-        }
-
-        if (pTicketResponse) {
-            memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE));
-            LsaFreeReturnBuffer(pTicketResponse);
-            pTicketResponse = NULL;
-        }
-        return(FALSE);
-    }
-
-    *ticket = &(pTicketResponse->Ticket);
-    return(TRUE);
-}
+#include "krb5.h"
 
 void
 main(
@@ -849,38 +37,44 @@ main(
 {
     krb5_context kcontext;
     krb5_error_code code;
-    krb5_creds creds;
     krb5_ccache ccache=NULL;
+    krb5_ccache mslsa_ccache=NULL;
     krb5_get_init_creds_opt opts;
-    char *cache_name=NULL;
-    HANDLE LogonHandle=NULL;
-    ULONG PackageId;
+    krb5_principal princ;
 
-    KERB_EXTERNAL_TICKET *msticket;
-    if(!PackageConnectLookup(&LogonHandle, &PackageId))
-        exit(1);
-
-    if (GetMSTGT(LogonHandle, PackageId, &msticket)==FALSE)
-        exit(1);
     if (code = krb5_init_context(&kcontext)) {
         com_err(argv[0], code, "while initializing kerberos library");
         exit(1);
     }
     krb5_get_init_creds_opt_init(&opts);
-    MSCredToMITCred(msticket, kcontext, &creds);
+  
+    if (code = krb5_cc_resolve(kcontext, "MSLSA:", &mslsa_ccache)) {
+        com_err(argv[0], code, "while opening MS LSA ccache");
+        exit(1);
+    }
+
+    if (code = krb5_cc_get_principal(kcontext, mslsa_ccache, &princ)) {
+        com_err(argv[0], code, "while obtaining MS LSA principal");
+        exit(1);
+    }
+
     if (code = krb5_cc_default(kcontext, &ccache)) {
         com_err(argv[0], code, "while getting default ccache");
         exit(1);
     }
-    if (code = krb5_cc_initialize(kcontext, ccache, creds.client)) {
-        com_err (argv[0], code, "when initializing cache %s",
-                 cache_name?cache_name:"");
+    if (code = krb5_cc_initialize(kcontext, ccache, princ)) {
+        com_err (argv[0], code, "when initializing ccache");
         exit(1);
     }
-    if (code = krb5_cc_store_cred(kcontext, ccache, &creds)) {
-        com_err (argv[0], code, "while storing credentials");
+
+    if (code = krb5_cc_copy_creds(kcontext, mslsa_ccache, ccache)) {
+        com_err (argv[0], code, "while copying MS LSA ccache to default ccache");
         exit(1);
     }
+
+    krb5_free_principal(kcontext, princ);
     krb5_cc_close(kcontext, ccache);
+    krb5_cc_close(kcontext, mslsa_ccache);
     krb5_free_context(kcontext);
+    return(0);
 }