1 line
24 KiB
C
Executable File
1 line
24 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
|
All rights reserved.
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
|
the limitations in the disclaimer below) provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided with the distribution.
|
|
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
|
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE. */
|
|
|
|
#include "sasl.h"
|
|
#define FILE_NUM 143
|
|
/* Copyright (c) 2002 by QUALCOMM Incorporated */
|
|
|
|
typedef struct
|
|
{
|
|
gss_ctx_id_t ctx;
|
|
gss_name_t crname;
|
|
short internalState;
|
|
} SASLGSSAPIContext, *SASLGSSAPIContextPtr, **SASLGSSAPIContextHandle;
|
|
|
|
#ifdef DEBUG // these should be opaque, but I know them for debugging purposes
|
|
|
|
struct _krb5_auth_context {
|
|
krb5_magic magic;
|
|
krb5_address * remote_addr;
|
|
krb5_address * remote_port;
|
|
krb5_address * local_addr;
|
|
krb5_address * local_port;
|
|
krb5_keyblock * keyblock;
|
|
krb5_keyblock * local_subkey;
|
|
krb5_keyblock * remote_subkey;
|
|
|
|
krb5_int32 auth_context_flags;
|
|
krb5_int32 remote_seq_number;
|
|
krb5_int32 local_seq_number;
|
|
krb5_authenticator *authentp; /* mk_req, rd_req, mk_rep, ...*/
|
|
krb5_cksumtype req_cksumtype; /* mk_safe, ... */
|
|
krb5_cksumtype safe_cksumtype; /* mk_safe, ... */
|
|
krb5_pointer i_vector; /* mk_priv, rd_priv only */
|
|
krb5_rcache rcache;
|
|
krb5_enctype * permitted_etypes; /* rd_req */
|
|
};
|
|
|
|
typedef struct _krb5_gss_ctx_id_rec {
|
|
int initiate; /* nonzero if initiating, zero if accepting */
|
|
OM_uint32 gss_flags;
|
|
int seed_init;
|
|
unsigned char seed[16];
|
|
krb5_principal here;
|
|
krb5_principal there;
|
|
krb5_keyblock *subkey;
|
|
int signalg;
|
|
int cksum_size;
|
|
int sealalg;
|
|
krb5_keyblock *enc;
|
|
krb5_keyblock *seq;
|
|
krb5_timestamp endtime;
|
|
krb5_flags krb_flags;
|
|
/* XXX these used to be signed. the old spec is inspecific, and
|
|
the new spec specifies unsigned. I don't believe that the change
|
|
affects the wire encoding. */
|
|
krb5_ui_4 seq_send;
|
|
krb5_ui_4 seq_recv;
|
|
void *seqstate;
|
|
int established;
|
|
int big_endian;
|
|
krb5_auth_context auth_context;
|
|
gss_OID_desc *mech_used;
|
|
int nctypes;
|
|
krb5_cksumtype *ctypes;
|
|
} krb5_gss_ctx_id_rec, *krb5_gss_ctx_id_t;
|
|
|
|
#endif
|
|
|
|
OSErr SASLGSSAPI(PStr service,short rounds,long *state,AccuPtr chalAcc,AccuPtr respAcc);
|
|
OSErr SASLCramMD5(short rounds,AccuPtr chalAcc,AccuPtr respAcc);
|
|
OSErr SASLPlain(short rounds,AccuPtr chalAcc,AccuPtr respAcc);
|
|
OSErr SASLLogin(short rounds,AccuPtr chalAcc,AccuPtr respAcc);
|
|
void SASLGSSAPIReport(OM_uint32 err);
|
|
|
|
/************************************************************************
|
|
* SASLFind - is this a valid SASL mechanism? 0 for no, otherwise an index
|
|
************************************************************************/
|
|
short SASLFind(PStr service, PStr mechStr, SASLEnum mech)
|
|
{
|
|
short foundMech = FindSTRNIndex(SASLStrn,mechStr);
|
|
short outMech;
|
|
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %p %p",mechStr,foundMech?"\punderstood":"\pfrom planet ten by way of the eighth dimension");
|
|
|
|
// Only do GSSAPI if we can
|
|
if (foundMech == saslGSSAPI && !HaveKerbV())
|
|
{
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %p ignored because GSSAPI not installed",mechStr);
|
|
foundMech = 0;
|
|
}
|
|
|
|
// Only do K4 if we can
|
|
if (foundMech == saslKerbIV && !HaveKerbIV())
|
|
{
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %p ignored because KerberosIV not installed",mechStr);
|
|
foundMech = 0;
|
|
}
|
|
|
|
if (foundMech)
|
|
{
|
|
Str255 badMechs;
|
|
Str31 token;
|
|
UPtr spot = badMechs+1;
|
|
Str63 servMech;
|
|
|
|
// Build a service:mech string, and look for that, too
|
|
PSCopy(servMech,service);
|
|
PCatC(servMech,':');
|
|
PSCat(servMech,mechStr);
|
|
|
|
// Look through the list of mechanisms we don't like
|
|
GetRString(badMechs,SASL_DONT);
|
|
while (PToken(badMechs,token,&spot," "))
|
|
if (StringSame(token,mechStr) || StringSame(token,servMech))
|
|
{
|
|
// oops, we don't like this one
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %p VERBOTEN!!! (%p)",mechStr,token);
|
|
foundMech = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundMech==saslKerbIV && !EqualStrRes(service,KERBEROS_IMAP_SERVICE)) foundMech = 0;
|
|
|
|
if (!foundMech)
|
|
outMech = mech;
|
|
else if (!mech)
|
|
outMech = foundMech;
|
|
// the following little dance tells us not to do kerberos unless:
|
|
// a) Kerberos is all that is offered
|
|
// or
|
|
// b) The user has specifically requested it
|
|
else if (KerberosMech(mech) && !KerberosMech(foundMech) && !PrefIsSet(PREF_KERBEROS))
|
|
{
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %r discarded because PREF_KERBEROS not set and non-Kerberos alternative offered",SASLStrn+mech);
|
|
outMech = foundMech;
|
|
}
|
|
else if (!KerberosMech(mech) && KerberosMech(foundMech) && !PrefIsSet(PREF_KERBEROS))
|
|
{
|
|
// leave current mech alone
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech %p ignored because PREF_KERBEROS not set and non-Kerberos alternative offered",mechStr);
|
|
outMech = mech;
|
|
}
|
|
// in general, smaller indices are considered more secure
|
|
else
|
|
outMech = MIN(mech,foundMech);
|
|
|
|
ComposeLogS(LOG_PROTO,nil,"\pSASL mech was %r now %r",SASLStrn+mech,SASLStrn+outMech);
|
|
|
|
return outMech;
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLDo - perform a round of SASL authentication
|
|
************************************************************************/
|
|
OSErr SASLDo(PStr service, SASLEnum mech, short rounds, long *state, AccuPtr chalAcc, AccuPtr respAcc)
|
|
{
|
|
short err = 501;
|
|
|
|
switch (mech)
|
|
{
|
|
case saslGSSAPI: err = SASLGSSAPI(service,rounds,state,chalAcc,respAcc); break;
|
|
case saslCramMD5: err = SASLCramMD5(rounds,chalAcc,respAcc); break;
|
|
case saslPlain: err = SASLPlain(rounds,chalAcc,respAcc); break;
|
|
case saslLogin: err = SASLLogin(rounds,chalAcc,respAcc); break;
|
|
case saslKerbIV: err = 501; break;
|
|
}
|
|
|
|
if (err==501)
|
|
{
|
|
// the auth mechanism thinks life is bad. Cancel the auth
|
|
AccuAddRes(respAcc,SASL_CANCEL);
|
|
err = 0; // let the server give the 501 back to us
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLDone - the authentication has succeeded or failed, and the mechanism
|
|
* should have a chance to react appropriately, possibly by invalidating
|
|
* or validating a password or ticket or whatever
|
|
************************************************************************/
|
|
void SASLDone(PStr service, SASLEnum mech, short rounds, long *state, short err)
|
|
{
|
|
switch (mech)
|
|
{
|
|
case saslGSSAPI:
|
|
{
|
|
SASLGSSAPIContextHandle contextH = (SASLGSSAPIContextHandle)*state;
|
|
OM_uint32 res;
|
|
|
|
if (contextH)
|
|
{
|
|
LDRef(contextH);
|
|
if ((*contextH)->ctx)
|
|
gss_delete_sec_context(&res,&(*contextH)->ctx,nil);
|
|
if ((*contextH)->crname)
|
|
gss_release_name(&res,&(*contextH)->crname);
|
|
ZapHandle(contextH);
|
|
*state = 0;
|
|
}
|
|
}
|
|
break;
|
|
case saslCramMD5:
|
|
case saslPlain:
|
|
case saslLogin:
|
|
if (err==535) InvalidatePasswords(false,true,false);
|
|
break;
|
|
}
|
|
if (err/100 == 2 && !(*CurPers)->popSecure)
|
|
{
|
|
(*CurPers)->popSecure = true;
|
|
(*CurPers)->dirty = true;
|
|
}
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLCramMD5 - perform a round of SASL authentication
|
|
************************************************************************/
|
|
OSErr SASLCramMD5(short rounds,AccuPtr chalAcc,AccuPtr respAcc)
|
|
{
|
|
Str63 pass;
|
|
Str63 user;
|
|
Str255 challenge;
|
|
|
|
if (rounds>1) {ASSERT(0); return 501;}
|
|
|
|
// build the initial command
|
|
if (rounds==0)
|
|
{
|
|
AccuAddRes(respAcc,SASLStrn+saslCramMD5);
|
|
return noErr;
|
|
}
|
|
|
|
// respond to the challenge
|
|
if (rounds==1)
|
|
{
|
|
AccuToStr(chalAcc,challenge);
|
|
PSCopy(pass,(*CurPers)->password);
|
|
GetPOPInfo(user,nil);
|
|
if (*challenge && !DecodeB64String(challenge))
|
|
{
|
|
GenKeyedDigest(challenge,pass,GlobalTemp);
|
|
PCopy(challenge,user);
|
|
PCatC(challenge,' ');
|
|
PCat(challenge,GlobalTemp);
|
|
AccuAddStrB64(respAcc,challenge);
|
|
return noErr;
|
|
}
|
|
else
|
|
{
|
|
// empty/failed challenge. Life is bad
|
|
ASSERT(0);
|
|
return 501;
|
|
}
|
|
}
|
|
|
|
return fnfErr;
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLPlain - perform a round of SASL authentication
|
|
************************************************************************/
|
|
OSErr SASLPlain(short rounds,AccuPtr chalAcc,AccuPtr respAcc)
|
|
{
|
|
Str63 pass;
|
|
Str63 user;
|
|
Str255 raw;
|
|
|
|
if (rounds==0) DealingWithIdiotIMail = false;
|
|
|
|
if (rounds==1)
|
|
{
|
|
// Words cannot begin to express my disdain for the need
|
|
// for the following code:
|
|
AccuToStr(chalAcc,raw);
|
|
DecodeB64String(raw);
|
|
if (EqualStrRes(raw,USERNAME_PROMPT))
|
|
{
|
|
DealingWithIdiotIMail = true;
|
|
ProgressMessageR(kpMessage,IMAIL_DOES_PLAIN_WRONG);
|
|
Pause(60);
|
|
}
|
|
}
|
|
|
|
if (DealingWithIdiotIMail)
|
|
return SASLLogin(rounds,chalAcc,respAcc);
|
|
|
|
// we will use the shortcut method,
|
|
// since some servers insist on it.
|
|
// however, if they ignore our initial argument
|
|
// we can just resend it when we get a challenge, minus
|
|
// the actual mechanism name.
|
|
PSCopy(pass,(*CurPers)->password);
|
|
GetPOPInfo(user,nil);
|
|
ComposeRString(raw,AUTHPLAIN_FMT,PREF_SASL_AUTHORIZE,user,pass);
|
|
|
|
// Here is where we say "PLAIN" if we're doing the
|
|
// initial command
|
|
if (rounds==0)
|
|
{
|
|
AccuAddRes(respAcc,SASLStrn+saslPlain);
|
|
AccuAddChar(respAcc,' ');
|
|
}
|
|
|
|
// Now we tack on the actual data...
|
|
AccuAddStrB64(respAcc,raw);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLLogin - perform a round of SASL authentication
|
|
************************************************************************/
|
|
OSErr SASLLogin(short rounds, AccuPtr chalAcc,AccuPtr respAcc)
|
|
{
|
|
Str63 pass;
|
|
Str63 user;
|
|
|
|
if (rounds>2) {ASSERT(0); return 501;}
|
|
|
|
// round 0: [auth ]login
|
|
if (rounds==0)
|
|
{
|
|
AccuAddRes(respAcc,SASLStrn+saslLogin);
|
|
return noErr;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// purely for interest' sake, what is the prompt?
|
|
DecodeB64Accu(chalAcc,true);
|
|
#endif
|
|
|
|
// round 1: username
|
|
if (rounds==1)
|
|
{
|
|
GetPOPInfo(user,nil);
|
|
AccuAddStrB64(respAcc,user);
|
|
return 0;
|
|
}
|
|
|
|
// round 2: password
|
|
if (rounds==2)
|
|
{
|
|
PSCopy(pass,(*CurPers)->password);
|
|
AccuAddStrB64(respAcc,pass);
|
|
return 0;
|
|
}
|
|
|
|
// Unreachable
|
|
ASSERT(0);
|
|
return 501;
|
|
}
|
|
|
|
#define AUTH_GSSAPI_P_NONE 1
|
|
|
|
static gss_OID_desc oids[] =
|
|
{
|
|
{10, "\052\206\110\206\367\022\001\002\001\004"},
|
|
};
|
|
static gss_OID_desc * gss_nt_service_name = oids+0;
|
|
|
|
OSErr SASLGSSAPIBuildServiceName(PStr fullService,PStr service);
|
|
|
|
#ifdef DEBUG
|
|
// uLong HashCTX(struct _krb5_gss_ctx_id_rec *ctx);
|
|
uLong HashPrincipal(krb5_principal p,uLong hash);
|
|
uLong HashAuthContext(krb5_auth_context a,uLong hash);
|
|
uLong HashKeyblock(krb5_keyblock *k,uLong hash);
|
|
uLong HashK5Address(krb5_address *a,uLong hash);
|
|
// void K5DumpContext(PStr s,struct _krb5_gss_ctx_id_rec *ctx,uLong hashBefore,uLong hashAfter);
|
|
|
|
#define LOG_K5_KEYBLOCK(k) do {\
|
|
if (k) {ComposeLogS(LOG_PROTO,nil,"\pCTX:" #k ": %x %x %x %d",k,k->magic,k->enctype,k->length); \
|
|
if (k->length<500) HexLog(LOG_PROTO,GSSAPI_LOG_FMT,k->contents,k->length);} \
|
|
else ComposeLogS(LOG_PROTO,nil,"\pCTX:" #k ": nil"); \
|
|
} while (0)
|
|
|
|
#ifdef I_EVER_FIX_THIS
|
|
void K5DumpContext(PStr s,struct _krb5_gss_ctx_id_rec *ctx,uLong hashBefore,uLong hashAfter)
|
|
{
|
|
ComposeLogS(LOG_PROTO,nil,"\pCTX %p: ctx %x hashBefore %x hashAfter %x",s,ctx,hashBefore,hashAfter);
|
|
if (0) //if (ctx)
|
|
{
|
|
ComposeLogS(LOG_PROTO,nil,"\pCTX: init %d flags %x seed_init %d seed %x%x%x%x",
|
|
ctx->initiate, ctx->gss_flags,ctx->seed_init,(long*)ctx->seed,((long*)ctx->seed)[1],((long*)ctx->seed)[2],((long*)ctx->seed)[3]);
|
|
ComposeLogS(LOG_PROTO,nil,"\pCTX: here %x %o there %x %o",ctx->here->magic,*(long*)ctx->here->realm.data,ctx->there->magic,*(long*)ctx->there->realm.data);
|
|
ComposeLogS(LOG_PROTO,nil,"\pCTX: seq_send %d seq_recv %d established %d big_endian %d",ctx->seq_send,ctx->seq_recv,ctx->established,ctx->big_endian);
|
|
LOG_K5_KEYBLOCK(ctx->subkey);
|
|
LOG_K5_KEYBLOCK(ctx->enc);
|
|
LOG_K5_KEYBLOCK(ctx->seq);
|
|
LOG_K5_KEYBLOCK(ctx->auth_context->keyblock);
|
|
LOG_K5_KEYBLOCK(ctx->auth_context->local_subkey);
|
|
LOG_K5_KEYBLOCK(ctx->auth_context->remote_subkey);
|
|
}
|
|
}
|
|
|
|
uLong HashCTX(struct _krb5_gss_ctx_id_rec *ctx)
|
|
{
|
|
uLong hash = 0;
|
|
if (ctx)
|
|
{
|
|
hash = HashWithSeedLo(&ctx->gss_flags,sizeof(ctx->gss_flags),hash);
|
|
hash = HashWithSeedLo(ctx->seed,sizeof(ctx->seed),hash);
|
|
hash = HashPrincipal(ctx->here,hash);
|
|
hash = HashPrincipal(ctx->there,hash);
|
|
hash = HashKeyblock(ctx->subkey,hash);
|
|
hash = HashWithSeedLo(&ctx->signalg,sizeof(ctx->signalg),hash);
|
|
hash = HashWithSeedLo(&ctx->cksum_size,sizeof(ctx->cksum_size),hash);
|
|
hash = HashWithSeedLo(&ctx->sealalg,sizeof(ctx->sealalg),hash);
|
|
hash = HashKeyblock(ctx->enc,hash);
|
|
hash = HashKeyblock(ctx->seq,hash);
|
|
hash = HashWithSeedLo(&ctx->big_endian,sizeof(ctx->big_endian),hash);
|
|
hash = HashAuthContext(ctx->auth_context,hash);
|
|
}
|
|
return hash;
|
|
}
|
|
#endif
|
|
|
|
uLong HashPrincipal(krb5_principal p,uLong hash)
|
|
{
|
|
short i;
|
|
if (p)
|
|
{
|
|
hash = HashWithSeedLo(p,sizeof(*p),hash);
|
|
hash = HashWithSeedLo(p->realm.data,p->realm.length,hash);
|
|
for (i=0;i<p->length;i++)
|
|
{
|
|
hash = HashWithSeedLo(p->data+i,sizeof(krb5_data),hash);
|
|
hash = HashWithSeedLo(p->data[i].data,p->data[i].length,hash);
|
|
}
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
uLong HashAuthContext(krb5_auth_context a,uLong hash)
|
|
{
|
|
if (a)
|
|
{
|
|
hash = HashWithSeedLo(a,sizeof(*a),hash);
|
|
hash = HashK5Address(a->remote_addr,hash);
|
|
hash = HashK5Address(a->remote_port,hash);
|
|
hash = HashK5Address(a->local_addr,hash);
|
|
hash = HashK5Address(a->local_port,hash);
|
|
hash = HashKeyblock(a->keyblock,hash);
|
|
hash = HashKeyblock(a->local_subkey,hash);
|
|
hash = HashKeyblock(a->remote_subkey,hash);
|
|
if (a->authentp) hash = HashWithSeedLo(a->authentp,sizeof(*a->authentp),hash);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
uLong HashK5Address(krb5_address *a,uLong hash)
|
|
{
|
|
if (a)
|
|
{
|
|
hash = HashWithSeedLo(a,sizeof(*a),hash);
|
|
if (a->contents) hash = HashWithSeedLo(a->contents,a->length,hash);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
uLong HashKeyblock(krb5_keyblock *k,uLong hash)
|
|
{
|
|
if (k)
|
|
{
|
|
hash = HashWithSeedLo(k,sizeof(*k),hash);
|
|
hash = HashWithSeedLo(k->contents,k->length,hash);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
#endif
|
|
|
|
/************************************************************************
|
|
* SASLGSSAPI - perform a round of SASL authentication
|
|
************************************************************************/
|
|
OSErr SASLGSSAPI(PStr service,short rounds,long *state,AccuPtr chalAcc,AccuPtr respAcc)
|
|
{
|
|
OSErr err = noErr;
|
|
OM_uint32 min, maj;
|
|
gss_buffer_desc buf, chal, resp;
|
|
SASLGSSAPIContextHandle contextH = (SASLGSSAPIContextHandle) *state;
|
|
gss_name_t crname = nil; // gssapi's idea of the service name
|
|
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; // gssapi's idea of a context
|
|
Str255 scratch;
|
|
#ifdef DEBUG
|
|
uLong hashBefore=0, hashAfter=0;
|
|
#endif
|
|
|
|
Zero(chal);
|
|
Zero(resp);
|
|
Zero(buf);
|
|
|
|
if (rounds==0)
|
|
{
|
|
// initialize our context
|
|
contextH = NewZH(SASLGSSAPIContext);
|
|
*state = (long)contextH;
|
|
if (!contextH) return 501;
|
|
|
|
// just tack on "gssapi" for now
|
|
// note that some smtp servers may barf if we don't put the initial
|
|
// response here, too, so be warned. However, clueless dweebs may
|
|
// not be doing gssapi, so this may be safe
|
|
AccuAddRes(respAcc,SASLStrn+saslGSSAPI);
|
|
|
|
return noErr;
|
|
}
|
|
|
|
if (rounds==1)
|
|
{
|
|
// do initialization
|
|
|
|
// get our idea of the service name
|
|
if (err = SASLGSSAPIBuildServiceName(scratch,service))
|
|
return 501;
|
|
|
|
// gssapi has its own idea of the service name
|
|
PTerminate(scratch);
|
|
buf.length = *scratch+1;
|
|
buf.value = scratch+1;
|
|
if (err = (gss_import_name(&min,&buf,gss_nt_service_name,&crname)!=GSS_S_COMPLETE))
|
|
return 501;
|
|
|
|
// Work around bug in kfm 4.0
|
|
Kfm40Hack();
|
|
|
|
// start it up
|
|
Zero(resp);
|
|
#ifdef DEBUG
|
|
// crashes with new K5... hashBefore = HashCTX(ctx);
|
|
#endif
|
|
maj = gss_init_sec_context(
|
|
&min, // [output] "minor" status
|
|
GSS_C_NO_CREDENTIAL, // Use default credential
|
|
&ctx, // [output] our magic context
|
|
crname, // its idea of our service name above
|
|
GSS_C_NO_OID, // Use default mechanism
|
|
GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, // self-authenticate & avoid replay
|
|
0, // Default context validity period
|
|
GSS_C_NO_CHANNEL_BINDINGS, // ???
|
|
GSS_C_NO_BUFFER, // no input for this round
|
|
NIL, // [output] we don't care about actual mech
|
|
&resp, // [output] our initial response
|
|
NIL, // [output] we don't give a darn about flags
|
|
NIL); // [output] we don't give a darn about ticket lifetime
|
|
#ifdef DEBUG
|
|
// crashes with new K5... hashAfter = HashCTX(ctx);
|
|
ComposeLogS(LOG_PROTO,nil,"\pgss_init_sec_context: maj %x resp.length %d",maj,resp.length);
|
|
// K5DumpContext("\pafter gss_init_sec_context",(void*)ctx,hashBefore,hashAfter);
|
|
#endif
|
|
|
|
// Did we fail?
|
|
if (maj != GSS_S_CONTINUE_NEEDED)
|
|
{
|
|
gss_release_buffer(&min,&resp);
|
|
return 501;
|
|
}
|
|
|
|
// Save our context
|
|
(*contextH)->ctx = ctx;
|
|
(*contextH)->crname = crname;
|
|
|
|
// format the response
|
|
//HexLog(LOG_PROTO,GSSAPI_LOG_FMT,resp.value,resp.length);
|
|
err = AccuAddPtrB64(respAcc,resp.value,resp.length);
|
|
gss_release_buffer(&min,&resp);
|
|
|
|
return err ? 501 : noErr;
|
|
}
|
|
|
|
if (rounds>1 && (*contextH)->internalState==0)
|
|
{
|
|
// We will have a challenge. Decode it.
|
|
if (DecodeB64Accu(chalAcc,false)) return 501;
|
|
//HexLog(LOG_PROTO,GSSAPI_LOG_FMT,LDRef(chalAcc->data),chalAcc->offset);
|
|
UL(chalAcc->data);
|
|
|
|
// Need local copy
|
|
ctx = (*contextH)->ctx;
|
|
|
|
// Around we go again
|
|
chal.value = LDRef(chalAcc->data);
|
|
chal.length = chalAcc->offset;
|
|
Zero(resp);
|
|
#ifdef DEBUG
|
|
// hashBefore = HashCTX(ctx);
|
|
#endif
|
|
maj = gss_init_sec_context (
|
|
&min, // [output] "minor" status
|
|
GSS_C_NO_CREDENTIAL, // Use default credential
|
|
&ctx, // [output] our magic context
|
|
(*contextH)->crname, // its idea of our service name above
|
|
GSS_C_NO_OID, // Use default mechanism
|
|
GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, // self-authenticate & avoid replay
|
|
0, // Default context validity period
|
|
GSS_C_NO_CHANNEL_BINDINGS, // ???
|
|
&chal, // challenge
|
|
NIL, // [output] we don't care about actual mech
|
|
&resp, // [output] our initial response
|
|
NIL, // [output] we don't give a darn about flags
|
|
NIL); // [output] we don't give a darn about ticket lifetime
|
|
#ifdef DEBUG
|
|
// hashAfter = HashCTX(ctx);
|
|
ComposeLogS(LOG_PROTO,nil,"\pgss_init_sec_context: maj %x resp.length %d",maj,resp.length);
|
|
// K5DumpContext("\pafter gss_init_sec_context",(void*)ctx,hashBefore,hashAfter);
|
|
#endif
|
|
(*contextH)->ctx = ctx; // save our new context
|
|
|
|
// first unlock the data...
|
|
UL(chalAcc->data);
|
|
|
|
// another round?
|
|
if (maj==GSS_S_CONTINUE_NEEDED || maj==GSS_S_COMPLETE)
|
|
{
|
|
if (resp.length) AccuAddPtrB64(respAcc,resp.value,resp.length);
|
|
gss_release_buffer(&min,&resp);
|
|
if (maj==GSS_S_COMPLETE) (*contextH)->internalState++;
|
|
return noErr;
|
|
}
|
|
|
|
goto fail;
|
|
}
|
|
|
|
if ((*contextH)->internalState==1)
|
|
{
|
|
int conf;
|
|
gss_qop_t qop;
|
|
#ifdef I_EVER_FIX_THIS
|
|
uLong hashBefore, hashAfter;
|
|
#endif
|
|
|
|
// We will have a challenge. Decode it.
|
|
if (DecodeB64Accu(chalAcc,false)) return 501;
|
|
|
|
// Voodoo that means nothing to me
|
|
Zero(resp);
|
|
chal.value = LDRef(chalAcc->data);
|
|
chal.length = chalAcc->offset;
|
|
#ifdef I_EVER_FIX_THIS
|
|
// hashBefore = HashCTX((*contextH)->ctx);
|
|
#endif
|
|
maj = gss_unwrap(&min,(*contextH)->ctx,&chal,&resp,&conf,&qop);
|
|
#ifdef I_EVER_FIX_THIS
|
|
// hashAfter = HashCTX((*contextH)->ctx);
|
|
ComposeLogS(LOG_PROTO,nil,"\pgss_unwrap: maj %x conf %d qop %d",maj,conf,qop);
|
|
// K5DumpContext("\pafter gss_unwrap",(*contextH)->ctx,hashBefore,hashAfter);
|
|
#endif
|
|
UL(chalAcc->data);
|
|
|
|
if (maj==GSS_S_COMPLETE && resp.length>=4 && ((*(char*)resp.value)&AUTH_GSSAPI_P_NONE))
|
|
{
|
|
// re-use the challenge buffer for some temp work
|
|
chalAcc->offset = 0;
|
|
|
|
AccuAddPtr(chalAcc,resp.value,4);
|
|
gss_release_buffer(&min,&resp);
|
|
|
|
// no, we don't want session "protection" (SSL?)
|
|
**chalAcc->data = AUTH_GSSAPI_P_NONE;
|
|
|
|
// tack on username
|
|
GetPOPInfo(scratch,nil);
|
|
AccuAddStr(chalAcc,scratch);
|
|
|
|
// more voodoo
|
|
buf.value = LDRef(chalAcc->data);
|
|
buf.length = chalAcc->offset;
|
|
maj = gss_wrap(&min,(*contextH)->ctx,false,qop,&buf,&conf,&resp);
|
|
UL(chalAcc->data);
|
|
|
|
// did we win?
|
|
if (maj==GSS_S_COMPLETE)
|
|
{
|
|
AccuAddPtrB64(respAcc,resp.value,resp.length);
|
|
gss_release_buffer(&min,&resp);
|
|
(*contextH)->internalState++;
|
|
return noErr;
|
|
}
|
|
}
|
|
else if (maj==GSS_S_COMPLETE && resp.length<4)
|
|
{
|
|
// for Kerberos, this is normal. Return "no data" to server
|
|
// which will happen sneakily if we return a 501 here
|
|
return noErr;
|
|
}
|
|
|
|
goto fail;
|
|
}
|
|
|
|
fail:
|
|
|
|
switch (maj)
|
|
{
|
|
case GSS_S_CREDENTIALS_EXPIRED:
|
|
GetRString(scratch,GSSAPIErrorsStrn+kGSSAPICredExpiredError);
|
|
AlertStr(OK_ALRT,Note,scratch);
|
|
break;
|
|
|
|
case GSS_S_FAILURE:
|
|
if (min == (OM_uint32) KRB5_FCC_NOFILE)
|
|
{
|
|
GetRString(scratch,GSSAPIErrorsStrn+kGSSAPINoCredError);
|
|
AlertStr(OK_ALRT,Note,scratch);
|
|
}
|
|
else
|
|
SASLGSSAPIReport(min);
|
|
break;
|
|
|
|
default: /* miscellaneous errors */
|
|
if (maj) SASLGSSAPIReport(maj);
|
|
if (min) SASLGSSAPIReport(min);
|
|
break;
|
|
}
|
|
|
|
if (resp.value) gss_release_buffer(&min,&resp);
|
|
|
|
return 501;
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLGSSAPIReport - get errors from gssapi
|
|
************************************************************************/
|
|
void SASLGSSAPIReport(OM_uint32 err)
|
|
{
|
|
OM_uint32 mmin, mmaj;
|
|
gss_buffer_desc buf;
|
|
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
|
|
Str255 scratch;
|
|
|
|
do
|
|
{
|
|
mmaj = gss_display_status (&mmin,err,GSS_C_MECH_CODE,GSS_C_NULL_OID,&ctx,&buf);
|
|
switch (mmaj)
|
|
{
|
|
case GSS_S_COMPLETE:
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
ComposeRString(scratch,GSSAPIErrorsStrn+kGSSAPIFailure,buf.value);
|
|
break;
|
|
default:
|
|
ComposeRString(scratch,GSSAPIErrorsStrn+kGSSAPIUnknownFailure,buf.value);
|
|
break;
|
|
}
|
|
AlertStr(OK_ALRT,Note,scratch);
|
|
gss_release_buffer(&mmin,&buf);
|
|
}
|
|
while (mmaj == GSS_S_CONTINUE_NEEDED);
|
|
|
|
gss_delete_sec_context(&mmin,&ctx,nil);
|
|
}
|
|
|
|
/************************************************************************
|
|
* SASLGSSAPIBuildServiceName - build the service name for GSSAPI
|
|
************************************************************************/
|
|
OSErr SASLGSSAPIBuildServiceName(PStr fullService,PStr service)
|
|
{
|
|
Str127 user, host;
|
|
Str63 realm;
|
|
struct hostInfo *hip, hi;
|
|
Str63 shortHost;
|
|
Str31 fmt;
|
|
UPtr spot;
|
|
OSErr err;
|
|
|
|
GetPref(realm,PREF_REALM);
|
|
GetPOPInfo(user,host);
|
|
if (EqualStrRes(service,K5_SMTP_SERVICE))
|
|
GetSMTPInfo(host);
|
|
|
|
if (!PrefIsSet(PREF_K5_STRICT_HOSTNAMES))
|
|
{
|
|
/*
|
|
* the best thing in the world is four million DNS calls
|
|
*/
|
|
if (err=GetHostByName(host,&hip)) return(err);
|
|
if (err=GetHostByAddr(&hi,hip->addr[0])) return(err);
|
|
CtoPCpy(host,hi.cname);
|
|
}
|
|
|
|
/*
|
|
* build the service name
|
|
*/
|
|
spot = host+1; PToken(host,shortHost,&spot,".");
|
|
GetRString(fmt,K5_SERVICE_FMT);
|
|
utl_PlugParams(fmt,fullService,service,host,realm,shortHost);
|
|
return noErr;
|
|
}
|