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