Automatic Prompting for Tickets on Windows

Alexandra Ellwood lxs at MIT.EDU
Tue Dec 2 18:23:54 EST 2003


At Sam's request, I have moved this discussion to krbdev at mit.edu.

Note that this discussion is about very short term changes.  Please 
avoid comments on this thread like "but the whole mechanism is 
completely broken" unless you plan to give us a large cash donation 
and/or a time machine.  Do feel free to start your own thread 
discussing KfM and KfW's automatic prompting for tickets, but don't 
expect any of your feedback to be considered for krb5-1.3.2.


>X-Sieve: CMU Sieve 2.2
>Date: Tue, 25 Nov 2003 15:22:20 -0500
>From: Jeffrey Altman <jaltman at columbia.edu>
>Organization: Columbia University in the City of New York
>X-Accept-Language: en-us, en
>To: Alexandra Ellwood <lxs at MIT.EDU>, Tom Yu <tlyu at MIT.EDU>
>Subject: marshall requests gssapi to produce a kinit dialog when there are
>  no tickets on windows
>
>alexis, tom:
>
>marshall asked me to contact the two of you.  to make umich happy, 
>he would like kfw to display a kinit dialog when there are either 
>"no tgts" or "expired tgts" when gssapi is being called by an 
>application.  in keeping with the desire to match the behavior of 
>kfm and kfw I would like to know how this functionality is 
>implemented in kfm.  I did not see any kfm specific code in the 
>krb5/src/lib/gssapi/krb5/*.c.  How is the failure to obtain a 
>credential hooked?  Is it via a wrapper library?
>
>is there different behavior for the cases: "no tgts" and "expired tgts"?
>
>thanks,
>
>- Jeff
>
>p.s. - the timeframe for this change is 1.3.2.


Yes, the automatic dialog popup on the Mac is currently implemented 
with krb5int_cc_default(), an internal version of krb5_cc_default() 
which pops the dialog if necessary and then calls into 
krb5_cc_default().  Although there are some places in the krb5 code 
where this doesn't work, I think you still want to use it because it 
does cause the right thing to happen for GSSAPI.

You can certainly add your own windows-specific code to 
krb5int_cc_default().  However, if you want to avoid changes to the 
krb5 library, you could define USE_LOGIN_LIBRARY and then implement 
the small number of functions in the KLL which are used by krb5.  A 
KLPrincipal is just a wrapped krb5_principal, and KLDisposeString is 
just a wrapper around free().  This would constrain all your 
Windows-specific code to the Windows sources.

The bulk of the prompting logic is in 
__KLAcquireInitialTicketsForCache().  I've included the source below. 
The names of the functions it calls should be pretty explanatory. 
The actual popping of the dialog is done by 
KLAcquireNewInitialTickets() which is a documented KLL API function. 
See the KLL documentation here: 
<http://web.mit.edu/macdev/KfM/KerberosFramework/KerberosLogin/Documentation/API.html>.

Note that the call to KLRenewInitialTickets() is just in case you 
have some operation (usually checking for mail) that's running 
frequently enough that it notices the tickets expired in the 5 minute 
window where they appear expired to the client but are really still 
legal.  At the time this function was written, I didn't realize that 
you couldn't renew expired tickets -- you can probably leave it out 
in your implementation if you want.

If you have any questions about how this works, feel free to ask:

KLStatus __KLAcquireInitialTicketsForCache (const KLPrincipal    inPrincipal,
                                             KLLoginOptions 
inLoginOptions,
                                             const char          *inCacheName,
                                             KLKerberosVersion 
inKerberosVersion,
                                             KLPrincipal         *outPrincipal,
                                             char               **outCacheName)
{
     KLStatus  lockErr = __KLLockCCache (kReadLock);
     KLStatus  err = lockErr;

     KLBoolean hasAPIPrefix = false;
     cc_ccache_t ccache = NULL;
     KLPrincipal ccachePrincipal = NULL;

     KLPrincipal gotPrincipal = NULL;
     char       *gotCCacheName = NULL;

     if (err == klNoErr) {
         if (inCacheName  == NULL) { err = KLError_ (klParameterErr); }
     }

     if (err == klNoErr) {
         // Make sure we have a Kerberos 5 configuration
         if ((inPrincipal != NULL) &&
             (inKerberosVersion & kerberosVersion_V5) &&
             (__KLPrincipalHasKerberos5 (inPrincipal) == false)) {
             err = KLError_ (klBadPrincipalErr);
         }
         // Make sure we have a Kerberos 4 configuration
         if ((inPrincipal != NULL) &&
             (inKerberosVersion & kerberosVersion_V4) &&
             (__KLPrincipalHasKerberos4 (inPrincipal) == false)) {
             err = KLError_ (klBadPrincipalErr);
         }
     }

     if (err == klNoErr) {
         hasAPIPrefix = (strncmp (inCacheName, "API:", 4) == 0);
     }

     if (err == klNoErr) {
         err = __KLGetCCacheByName ((hasAPIPrefix ? &inCacheName [4] :
                                                    inCacheName),
                                    &ccache);

         if (err == klNoErr) {           
             err = __KLGetPrincipalForCCache (ccache, &ccachePrincipal);
         }

         if (err == klNoErr) {
             if (inPrincipal != NULL) {
                 // the caller passed in a non-nul principal
                 // make sure it is the one in the ccache
                 KLBoolean samePrincipal = false;
                
                 if ((KLComparePrincipal (inPrincipal, ccachePrincipal,
                                          &samePrincipal) == klNoErr) &&
                     !samePrincipal) {
                     // try to find a ccache that matches the principal
                     if (ccache != NULL) {
                         cc_ccache_release (ccache);
                         ccache = NULL;   // Don't leak the old one
                     }
                     err = __KLGetFirstCCacheForPrincipal 
(inPrincipal, &ccache);
                 }
             }
         }

         if (err == klNoErr) {
             err = __KLCacheHasValidTickets (ccache, inKerberosVersion);
             if (err == klNoErr) { // Tickets exist!
                 err = __KLGetPrincipalAndNameForCCache (ccache,
                                                         &gotPrincipal,
                                                         &gotCCacheName);
             } else {
                 if (err == klCredentialsExpiredErr) {
                     // try renewing the credentials
                     err = KLRenewInitialTickets (inPrincipal,
                                                  inLoginOptions,
                                                  &gotPrincipal,
                                                  &gotCCacheName);
                 } else if (err == klCredentialsBadAddressErr) {
                     // try krb524
                     err = __KLAcquireNewKerberos4TicketsFromKerberos5Tickets (
                                inPrincipal, &gotPrincipal, &gotCCacheName);
                 }
             }
         }

         if ((err != klNoErr) && __KLAllowAutomaticPrompting ()) {
             // If searching the credentials cache failed,
             // try popping the dialog if we are allowed to
             err = KLAcquireNewInitialTickets (inPrincipal,
                                               inLoginOptions,
                                               &gotPrincipal,
                                               &gotCCacheName);
         }
     }

     if ((err == klNoErr) && hasAPIPrefix) {
         // restore API: prefix
         err = __KLAddPrefixToString ("API:", &gotCCacheName);
     }

     if (err == klNoErr) {
         if (outPrincipal != NULL) {
             *outPrincipal = gotPrincipal;
             gotPrincipal = NULL;
         }
         if (outCacheName != NULL) {
             *outCacheName = gotCCacheName;
             gotCCacheName = NULL;
         }
     }
    
     if (gotPrincipal    != NULL) { KLDisposePrincipal (gotPrincipal); }
     if (gotCCacheName   != NULL) { KLDisposeString (gotCCacheName); }
     if (ccache          != NULL) { cc_ccache_release (ccache); }
     if (ccachePrincipal != NULL) { KLDisposePrincipal (ccachePrincipal); }

     if (lockErr == klNoErr) { __KLUnlockCCache (); }

     return KLError_ (err);
}



One thing you should note:

There is a deficiency in the GSSAPI implementation (RT 1579 "GSS not 
noticing changes in active principal?") which is exposed when 
automatic prompting is turned on.  The krb5_context used by GSSAPI 
has a cached copy of the current ccache name and current ccache 
principal.  Because the krb5_context is global, the name and 
principal get cached when the library initializes.  This means that 
the library always uses whatever the default ccache was as the time 
the application launched.  Changing the system default ccache 
(whether by CCAPI v3+ or by the Windows registry stuff -- see 
get_from_os() in ccdefname.c) has no impact on the ccache the 
application uses.

For example, let's say the user has an FTP application and wants to 
connect simultaneously to two hosts A and B.  Host A requires tickets 
for user at REALM.A and host B requires tickets for user at REALM.B.  With 
the current bug and automatic prompting, there is no way to do this. 
If the FTP application is launched with tickets for user at REALM.A as 
the default, then the application will always use the tickets in that 
ccache, regardless of whether the user switches a ccache containing 
tickets for user at REALM.B after connecting to host A.  To make matters 
worse, if you destroy the tickets for user at REALM.A and get tickets 
for user at REALM.B in the same ccache, the automatic prompting will end 
up prompting for tickets for user at REALM.A because that is the current 
ccache principal in the krb5_context -- it will consider the tickets 
for user at REALM.B to be useless because they aren't for the principal 
cached in the krb5_context.  Basically the cached ccache name and 
principal, combined with automatic popup, results in some seriously 
annoying and confusing behavior.

As you might have noticed, Fetch and some other GSSAPI applications 
on the Mac don't actually have this behavior.  The reason for this is 
that they can work around the problem by using the private API 
gss_krb5_ccache_name() to manually reset the ccache name in GSSAPI's 
krb5_context.  The problem is, gss_krb5_ccache_name() is a private 
API -- not even part of the GSSAPI C Bindings -- and really should be 
avoided.

So after talking to the rest of the team, we agreed that we should 
fix this problem for 1.3.2 so that Windows doesn't end up with the 
same private API usage that we have on the Mac.

Since we already have automatic popup implemented on the Mac -- 
making it easy to test -- I will offer to do the work to fix it.  Sam 
suggested syncing with the system default (ie: calling 
krb5_cc_set_default_name(context, NULL)) inside krb5int_cc_default(), 
but I may be forced to do it inside gss_acquire_cred() or 
kg_get_defcred() because krb5_fwd_tgt_creds() calls 
krb5int_cc_default(), but shouldn't be picking up the system default 
ccache -- it would be arguably a bug to connect with one set of creds 
and end up forwarding another set...


Hope this helps,

--lxs
-- 
-----------------------------------------------------------------------------
Alexandra Ellwood                                               <lxs at mit.edu>
MIT Information Systems                               http://mit.edu/lxs/www/
-----------------------------------------------------------------------------
--


More information about the krbdev mailing list