1 line
31 KiB
C
Executable File
1 line
31 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. */
|
|
|
|
/* Copyright (c) 1999 by QUALCOMM Incorporated */
|
|
#define FILE_NUM 120
|
|
|
|
|
|
/**********************************************************************
|
|
* imapauth.c
|
|
*
|
|
* This file contains the functions needed to do IMAP authentication.
|
|
*
|
|
*
|
|
* Currently Supported:
|
|
*
|
|
* CRAM-MD5
|
|
* KERBEROS_V4
|
|
* GSSAPI (Kerberos V5)
|
|
**********************************************************************/
|
|
#include "imapauth.h"
|
|
|
|
/**********************************************************************
|
|
* CRAM-MD5
|
|
*
|
|
* These routines taken straight from the windows code with only minor
|
|
* modifications. Thanks JOK!
|
|
**********************************************************************/
|
|
#pragma mark ~~~CRAM_MD5~~~
|
|
|
|
unsigned char * GetStamp(unsigned char * stamp,unsigned char * banner);
|
|
int produce_hmac(unsigned char* text,unsigned char* key ,char* digest);
|
|
|
|
/**********************************************************************
|
|
* CramMD5Authenticator
|
|
*
|
|
* Implement the CRAM-MD5 authenticator. This is called by imap_auth.
|
|
* It uses "challenger" to get the latest challenge the challenger
|
|
* ( the IMAP server in the case of IMAP), and "responnder" to send the
|
|
* response.
|
|
*
|
|
**********************************************************************/
|
|
long CramMD5Authenticator (authchallenge_t challenger, authrespond_t responder, NETMBX *mb, void *s, unsigned long *trial, char *user)
|
|
{
|
|
MAILSTREAM *stream = (MAILSTREAM *) s;
|
|
char tmp[MAILTMPLEN];
|
|
unsigned char szDigest[1024];
|
|
unsigned long len;
|
|
unsigned char *pChallenge = NULL;
|
|
long ret = NIL;
|
|
unsigned char dlen;
|
|
unsigned char i;
|
|
|
|
// Sanity:
|
|
if (! (challenger && responder && mb && s && trial && user) )
|
|
{
|
|
ASSERT (0);
|
|
return NIL;
|
|
}
|
|
|
|
//
|
|
// This authenticator uses a login password. The caller manages the
|
|
// number of allowed trials. Here, we simply, (and always), get the password
|
|
// and do the CRAM-MD5 thing.
|
|
//
|
|
tmp[0] = 0; /* get password in this */
|
|
|
|
//
|
|
// JOK - Call back to upper layers. to get user and password.
|
|
//
|
|
mm_login (mb, user, tmp, *trial);
|
|
|
|
//
|
|
// Must have both a user and a password.
|
|
//
|
|
if (! (tmp[0] && user[0]) )
|
|
{
|
|
//
|
|
// user refused to give a password
|
|
//
|
|
|
|
// Don't allow any more trials.
|
|
|
|
*trial = MAXLOGINTRIALS + 1;
|
|
|
|
mm_log ("CRAM-MD5 authenticator aborted", IMAP_ERROR);
|
|
return NIL;
|
|
}
|
|
|
|
//
|
|
// Get the first ready response.
|
|
//
|
|
len = 0;
|
|
pChallenge = (unsigned char *)(*challenger) (stream, &len);
|
|
|
|
// Must have a challenge string.
|
|
if ( NULL == pChallenge || len == 0)
|
|
return NIL;
|
|
|
|
//
|
|
// Formulate our response. The password returned in "tmp"
|
|
// is used as the key.
|
|
// This returns the response in szDigest, starting at szDigest[1].
|
|
// The size of the digest in returned in szDigest[0].
|
|
//
|
|
memset(szDigest, 0, sizeof(szDigest));
|
|
|
|
produce_hmac( (unsigned char* )pChallenge, (unsigned char* )tmp, (char *)szDigest);
|
|
|
|
if ( !*szDigest )
|
|
{
|
|
ASSERT (0);
|
|
goto end;
|
|
}
|
|
|
|
// Formulate response in the for: user<sp>digest.
|
|
sprintf (tmp, "%s ",user);
|
|
|
|
// So far we've just got text in "tmp".
|
|
len = strlen (tmp);
|
|
|
|
// szDigest is not necessarily text:
|
|
dlen = szDigest[0];
|
|
|
|
for (i = 0 ; i < dlen; i++)
|
|
tmp[len + i] = szDigest[i + 1];
|
|
|
|
// Total length.
|
|
len += dlen;
|
|
|
|
// Increment trial
|
|
(*trial)++;
|
|
|
|
// Ok. Send the response.
|
|
if (! (*responder) (stream, tmp, len ) )
|
|
{
|
|
ret = NIL;
|
|
goto end;
|
|
}
|
|
|
|
// If we get here, we've done our bit.
|
|
ret = 1;
|
|
|
|
end:
|
|
// Cleanup!!
|
|
|
|
if (pChallenge)
|
|
fs_give ((void **) &pChallenge);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/************************************************************************
|
|
* GetStamp - grab the timestamp out of a CRAM banner
|
|
************************************************************************/
|
|
unsigned char *
|
|
GetStamp(unsigned char * stamp,unsigned char * banner)
|
|
{
|
|
unsigned char * cp1,*cp2;
|
|
int len = 0;
|
|
//*stamp = 0;
|
|
|
|
//banner[*banner+1] = 0;
|
|
|
|
if (cp1 = (unsigned char *)strchr((char *)banner,'<'))
|
|
{
|
|
if (cp2=(unsigned char *)strchr((char *)cp1+1,'>'))
|
|
{
|
|
len = cp2-cp1+1;
|
|
strncpy((char *)stamp,(char *)cp1,len);
|
|
}
|
|
}
|
|
|
|
return (stamp);
|
|
}
|
|
|
|
/************************************************************************
|
|
* produce hmac using md5 - based on the acap stuff.
|
|
*
|
|
* The result is returned in "digest", starting at byte 1. The size of
|
|
* the digest is returned in digets[0] - yuck. This should change.
|
|
************************************************************************/
|
|
int produce_hmac(unsigned char* text, unsigned char* key, char* digest)
|
|
{
|
|
MD5_CTX context;
|
|
unsigned char stamp[255];
|
|
|
|
unsigned char k_ipad[64];
|
|
unsigned char k_opad[64]; /* outer padding - key XORd with opad */
|
|
unsigned char tk[16];
|
|
static char hex[]="0123456789abcdef";
|
|
int i;
|
|
int ntext=strlen((const char* )text);
|
|
int nkey=strlen((const char* )key);
|
|
|
|
memset(stamp,0,sizeof(stamp));
|
|
|
|
|
|
if (!*GetStamp((unsigned char *)stamp,(unsigned char *)text))
|
|
{
|
|
// Error_Notify(IDS_APOP_NO_BANNER_ERROR, -1);
|
|
return (FALSE);
|
|
}
|
|
|
|
text=stamp;
|
|
|
|
if (nkey > 64)
|
|
{
|
|
MD5_CTX tctx;
|
|
MD5Init(&tctx);
|
|
MD5Update(&tctx,(unsigned char*) key, nkey);
|
|
MD5Final(&tctx);
|
|
|
|
key = (unsigned char* )tk;
|
|
nkey = 16;
|
|
}
|
|
|
|
memset( k_ipad,0, sizeof(k_ipad));
|
|
memset( k_opad,0, sizeof(k_opad));
|
|
memcpy( k_ipad,key, nkey);
|
|
memcpy( k_opad,key,nkey);
|
|
|
|
|
|
for (i=0; i<64; i++)
|
|
{
|
|
k_ipad[i] ^= 0x36;
|
|
k_opad[i] ^= 0x5C;
|
|
}
|
|
|
|
MD5Init(&context);
|
|
MD5Update(&context, k_ipad, 64);
|
|
MD5Update(&context, text, ntext);
|
|
MD5Final(&context);
|
|
|
|
|
|
MD5Init(&context);
|
|
MD5Update(&context, k_opad, 64);
|
|
MD5Update(&context, (unsigned char* )context.digest, 16);
|
|
MD5Final(&context);
|
|
|
|
for (i = 0; i < sizeof(context.digest); i++)
|
|
{
|
|
digest[2*i+1] = hex[(context.digest[i]>>4)&0xf];
|
|
digest[2*i+2] = hex[context.digest[i]&0xf];
|
|
}
|
|
|
|
digest[0] = 2 * sizeof(context.digest);
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* KERBEROS_V4
|
|
*
|
|
* The algorithm for these routines taken from Windows. Major mods
|
|
* required to work with Macintosh KClient.
|
|
**********************************************************************/
|
|
#pragma mark ~~~KERBEROS_V4~~~
|
|
|
|
Boolean gIMAPAuthedKerberos = false; // set to true once KClient is invoked.
|
|
|
|
// CKrbAuthData - struct to hold info for IMAP KV4 authentication
|
|
typedef struct CKrbAuthData_ CKrbAuthData, *CKrbAuthPtr;
|
|
struct CKrbAuthData_
|
|
{
|
|
authchallenge_t challenger; // routine to receive challeng from the IMAP server
|
|
authrespond_t responder; // routine to send response to the IMAP server
|
|
MAILSTREAM* mailstream; // mailstream to authenticate
|
|
|
|
KClientSessionInfo session; // session information
|
|
KClientKey clientKey;
|
|
|
|
Str255 pService;
|
|
Str255 pInstance;
|
|
char realm[REALM_SZ];
|
|
Str255 pUser;
|
|
Str255 pServerFQDN;
|
|
};
|
|
|
|
typedef void *(*authchallenge_t) (void *stream,unsigned long *len);
|
|
typedef char *(*authresponse_t) (void *challenge,unsigned long clen,unsigned long *rlen);
|
|
typedef struct mail_stream MAILSTREAM;
|
|
|
|
OSErr NewKrbV4(CKrbAuthPtr cKrbAuth, authchallenge_t challenger, authrespond_t responder, PStr fqdn, MAILSTREAM* s, PStr user);
|
|
void ZapKrbV4(CKrbAuthPtr cKrbAuth);
|
|
long KrbV4Authenticate(CKrbAuthPtr cKrbAuth);
|
|
Boolean KrbV4Stage0Challenge(CKrbAuthPtr cKrbAuth, unsigned long *ulRndNum);
|
|
Boolean KrbV4Stage1Response(CKrbAuthPtr cKrbAuth, unsigned long ulRndNum);
|
|
Boolean KrbV4Stage2Challenge(CKrbAuthPtr cKrbAuth, unsigned long ulRndNum);
|
|
OSErr KerbGetTicketForIMAP(PStr serviceName,PStr inHost,PStr realm,unsigned long rndNum,UHandle *ticket,CKrbAuthPtr cKrb4Data);
|
|
OSErr KerbGetCredentials(CKrbAuthPtr cKrbAuth, CREDENTIALS *credentials);
|
|
|
|
/***************************************************************************************
|
|
* KrbV4Authenticator - Implement the Kerbv4 authenticator. This is called by imap_auth.
|
|
***************************************************************************************/
|
|
long KrbV4Authenticator (authchallenge_t challenger, authrespond_t responder, NETMBX *mb, void *s,unsigned long *trial, char *user)
|
|
{
|
|
MAILSTREAM *stream = (MAILSTREAM *) s;
|
|
CKrbAuthData kerbData;
|
|
long result;
|
|
Str255 pUser, pHost;
|
|
OSErr err = noErr;
|
|
|
|
if (!challenger || !responder || !mb || !s || !user) return (0);
|
|
|
|
// Get host and username
|
|
GetPOPInfo(pUser, pHost);
|
|
|
|
// Build a CKrb4 to do all the work.
|
|
if ((err=NewKrbV4(&kerbData, challenger, responder, pHost, stream, pUser))!=noErr)
|
|
{
|
|
WarnUser(NO_KERBEROS,err);
|
|
return (false);
|
|
}
|
|
|
|
// remember that we started up Kerberos. We might have to kill the beast later.
|
|
UsedKerberos();
|
|
|
|
// return the user name
|
|
user = PtoCcpy(user, pUser);
|
|
|
|
// do the work
|
|
result = KrbV4Authenticate(&kerbData);
|
|
|
|
// clean up
|
|
ZapKrbV4(&kerbData);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* KrbV4Authenticate - authenticate
|
|
***************************************************************************************/
|
|
long KrbV4Authenticate(CKrbAuthPtr cKrbAuth)
|
|
{
|
|
unsigned long ulRndNum = 0;
|
|
|
|
if (!KrbV4Stage0Challenge(cKrbAuth, &ulRndNum)) return (0);
|
|
|
|
if (!KrbV4Stage1Response(cKrbAuth, ulRndNum)) return (0);
|
|
|
|
if (!KrbV4Stage2Challenge(cKrbAuth, ulRndNum)) return (0);
|
|
|
|
// successfully authenticated
|
|
return (1);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* NewKrbV4 - build a KrbV4 object to do the authentication work
|
|
***************************************************************************************/
|
|
OSErr NewKrbV4(CKrbAuthPtr cKrbAuth, authchallenge_t challenger, authrespond_t responder, PStr fqdn, MAILSTREAM* s, PStr user)
|
|
{
|
|
OSErr err = fnfErr;
|
|
Str255 serverFQDN;
|
|
|
|
WriteZero(cKrbAuth, sizeof(CKrbAuthData));
|
|
|
|
cKrbAuth->challenger = challenger;
|
|
cKrbAuth->responder = responder;
|
|
cKrbAuth->mailstream = s;
|
|
PCopy(cKrbAuth->pUser,user);
|
|
GetRString(cKrbAuth->pService,KERBEROS_IMAP_SERVICE);
|
|
PCopy(cKrbAuth->pServerFQDN, fqdn);
|
|
|
|
if (DESLibraryIsPresent()) // the DES library must be preset to do IMAP Kerberos V4 authentication
|
|
{
|
|
// start a new session
|
|
err = KClientNewSessionCompat(&(cKrbAuth->session), 1/*lAddr*/, GetRLong(IMAP_PORT), 2/*fAddr*/, GetRLong(IMAP_PORT));
|
|
if (err == noErr)
|
|
{
|
|
// figure out the server realm
|
|
PtoCcpy(serverFQDN, cKrbAuth->pServerFQDN);
|
|
err = KClientGetRealmDeprecated(serverFQDN, cKrbAuth->realm);
|
|
|
|
// log in if we have to
|
|
if (err == cKrbNotLoggedIn) err = KClientLoginCompat(&(cKrbAuth->session), &(cKrbAuth->clientKey));
|
|
|
|
// grab the user name
|
|
if (err = noErr) err = KClientGetSessionUserNameCompat(&(cKrbAuth->session), cKrbAuth->pUser, 0);
|
|
}
|
|
}
|
|
|
|
return (err);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* ZapKrbV4 - clean up after a kerb object
|
|
***************************************************************************************/
|
|
void ZapKrbV4(CKrbAuthPtr cKrbAuth)
|
|
{
|
|
OSErr err = noErr;
|
|
|
|
err = KClientDisposeSessionCompat(&(cKrbAuth->session));
|
|
|
|
return;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* Stage0Challenge - grab the first ready response and return it. It should be a random
|
|
* 4-byte integer in network byte order that becomes the CRC check.
|
|
***************************************************************************************/
|
|
Boolean KrbV4Stage0Challenge(CKrbAuthPtr cKrbAuth, unsigned long *ulRndNum)
|
|
{
|
|
unsigned long len = 0;
|
|
unsigned char* challenge;
|
|
|
|
// Must have had a MAILSTREAM and challenger ....
|
|
if (!cKrbAuth->mailstream || !cKrbAuth->challenger) return (false);
|
|
|
|
// grab the first ready response ...
|
|
challenge = (unsigned char *)(*(cKrbAuth->challenger))(cKrbAuth->mailstream, &len);
|
|
|
|
// Must have grabbed something ...
|
|
if (!challenge || len != 4) return false;
|
|
|
|
// save the number
|
|
BMD(challenge, (void *)ulRndNum, 4);
|
|
|
|
// Cleanup
|
|
fs_give ((void **) &challenge);
|
|
|
|
return true;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* KrbV4Stage1Response - given the random number returned from the server in Stage0,
|
|
* contact the Kerberos library and build a ticket using that checksum. Send the
|
|
* ticket to the server.
|
|
***************************************************************************************/
|
|
Boolean KrbV4Stage1Response(CKrbAuthPtr cKrbAuth, unsigned long ulRndNum)
|
|
{
|
|
Boolean result = false;
|
|
OSErr err = noErr;
|
|
Handle hTicket = nil;
|
|
KTEXT_ST *ticket;
|
|
|
|
// Must have had a mailstream and responder
|
|
if (!(cKrbAuth->mailstream) || !(cKrbAuth->responder)) return (false);
|
|
|
|
// get the ticket from the Kerberos extension, using the random number we got
|
|
err = KerbGetTicketForIMAP(cKrbAuth->pService,cKrbAuth->pServerFQDN,cKrbAuth->realm,ulRndNum,&hTicket,cKrbAuth);
|
|
if ((err == noErr) && (hTicket && *hTicket))
|
|
{
|
|
LDRef(hTicket);
|
|
ticket = (KTEXT_ST*)*hTicket;
|
|
|
|
// send the ticket data to the IMAP server. I'm just following orders (RFC 1731)
|
|
if (ticket->length!=0)
|
|
{
|
|
result = (*(cKrbAuth->responder))((void *)(cKrbAuth->mailstream), ticket->dat, ticket->length) != 0;
|
|
}
|
|
UL(hTicket);
|
|
}
|
|
|
|
// Cleanup
|
|
ZapHandle(hTicket);
|
|
|
|
return (result);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* KrbV4Stage2Challenge - given the random number returned from the server in Stage0,
|
|
* contact the Kerberos library and build a ticket using that checksum. Send the
|
|
* ticket to the server.
|
|
***************************************************************************************/
|
|
Boolean KrbV4Stage2Challenge(CKrbAuthPtr cKrbAuth, unsigned long ulRndNum)
|
|
{
|
|
Boolean result = false;
|
|
OSErr err = noErr;
|
|
unsigned long len = 0;
|
|
unsigned char *challenge = nil;
|
|
CREDENTIALS credentials;
|
|
unsigned char sout[1024];
|
|
unsigned char in [8];
|
|
short i;
|
|
unsigned long testnum;
|
|
char userid[512];
|
|
void *clientOut;
|
|
|
|
des_key_schedule keysched;
|
|
|
|
// Must have had a mailstream and a challenger
|
|
if (!(cKrbAuth->mailstream) || !(cKrbAuth->challenger)) return (false);
|
|
|
|
//
|
|
// Get the second ready response. Length is now 8 bytes.
|
|
//
|
|
|
|
challenge = (unsigned char *)(*(cKrbAuth->challenger)) (cKrbAuth->mailstream, &len);
|
|
|
|
// Must have received an 8 byte challenge string from the server
|
|
if (!challenge || len != 8) return (false);
|
|
|
|
// make a copy of the server's challenge
|
|
for (i=0; i<8; i++) in[i] = challenge[i];
|
|
|
|
// we can now throw away the challenge
|
|
fs_give ((void **)&challenge);
|
|
challenge = NULL;
|
|
|
|
//
|
|
// check the server response
|
|
//
|
|
|
|
// get the credentials
|
|
if (KerbGetCredentials(cKrbAuth, &credentials)!=noErr) return (false);
|
|
|
|
// get the keyschedule
|
|
des_key_sched(credentials.session, keysched);
|
|
|
|
// verify data 1st 4 octets must be equal to chal+1
|
|
des_ecb_encrypt((des_cblock *)in, (des_cblock *)in, keysched, DES_DECRYPT);
|
|
|
|
testnum = (in[0]*256*256*256)+(in[1]*256*256)+(in[2]*256)+in[3];
|
|
if (testnum != ulRndNum + 1) return (false);
|
|
|
|
//
|
|
// build an answer to send back to the server
|
|
//
|
|
//
|
|
// According to RFC 1731, the final response from the client is composed of
|
|
// 8 octets and the user name, encrypted. The 8 octects are:
|
|
//
|
|
// 1-4 are the original checksum
|
|
// 5 is bitmask specifying selected protection mechanism
|
|
// 6-8 max cipher text buffer size
|
|
//
|
|
|
|
BMD(&ulRndNum, sout, 4);
|
|
sout[4] = 1; // no protection mechanism at this time
|
|
sout[5] = 0x00; // max ciphertext buffer size
|
|
sout[6] = 0x04;
|
|
sout[7] = 0x00;
|
|
|
|
// add the user name to the response
|
|
PtoCcpy(userid, cKrbAuth->pUser);
|
|
for (i = 0; i < (int) strlen(userid); i++) sout[8+i] = userid[i];
|
|
len = 8 + strlen(userid);
|
|
|
|
// append 0 based octets so is multiple of 8
|
|
do
|
|
{
|
|
sout[len] = 0;
|
|
len++;
|
|
} while (len%8);
|
|
sout[len] = 0;
|
|
|
|
// encrypt the response
|
|
des_key_sched(credentials.session, keysched);
|
|
des_pcbc_encrypt((des_cblock *)sout, (des_cblock *)sout, len - 1, keysched, (des_cblock *)credentials.session, DES_ENCRYPT);
|
|
|
|
// Send this to IMAP. The caller will get the final response from IMAP to determine if the user was authenticated.
|
|
if ((clientOut = fs_get(len))!=0)
|
|
{
|
|
BMD(sout,(char *)clientOut,len);
|
|
|
|
// This gets sent finally to the server:
|
|
result = (*(cKrbAuth->responder)) ((void *)(cKrbAuth->mailstream), (char *)clientOut, len) != 0;
|
|
|
|
// Can now free this:
|
|
//
|
|
fs_give ((void **)&clientOut);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* KrbGetTicket - get our ticket for the IMAP server
|
|
**********************************************************************/
|
|
OSErr KerbGetTicketForIMAP(PStr serviceName,PStr inHost,CStr realm,unsigned long rndNum,UHandle *ticket,CKrbAuthPtr CKrbAuth)
|
|
{
|
|
Str63 fmt;
|
|
Str255 fullName;
|
|
Str255 host;
|
|
OSErr err;
|
|
UPtr spot;
|
|
struct hostInfo *hip, hi;
|
|
Str255 uRealm;
|
|
long ticketLength = 0;
|
|
|
|
/*
|
|
* the best thing in the world is four million DNS calls
|
|
*/
|
|
if (err=GetHostByName(inHost,&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,CKrbAuth->pInstance,&spot,".");
|
|
EscapeChars(host,GetRString(fmt,KERBEROS_ESCAPES));
|
|
EscapeChars(serviceName,GetRString(fmt,KERBEROS_ESCAPES));
|
|
GetRString(fmt,IMAP_KERBEROS_SERVICE_FMT); // note, the format is determined by RFC 1731.
|
|
CtoPCpy(uRealm, realm);
|
|
utl_PlugParams(fmt,fullName,serviceName,host,uRealm,CKrbAuth->pInstance);
|
|
|
|
/*
|
|
* save the actual host name used in the request for stage 2 later
|
|
*/
|
|
|
|
spot = fullName+1; spot = PIndex(fullName, '.'); spot++;
|
|
PToken(spot,CKrbAuth->pInstance,&spot,"@"); // save the host name for later
|
|
|
|
/*
|
|
* call the driver
|
|
*/
|
|
|
|
fullName[*fullName+1] = 0;
|
|
|
|
ticketLength = GetRLong(KERBEROS_BSIZE);
|
|
if (!(*ticket = NuHandle(ticketLength))) return(MemError());
|
|
LDRef(*ticket);
|
|
err = KClientGetTicketForServiceWithChecksumCompat(&(CKrbAuth->session),rndNum,fullName+1,**ticket,&ticketLength);
|
|
UL(*ticket);
|
|
|
|
/*
|
|
* adjust the ticket size
|
|
*/
|
|
if (err) ZapHandle(*ticket);
|
|
else SetHandleSize(*ticket,ticketLength);
|
|
|
|
return(err);
|
|
}
|
|
|
|
/************************************************************************
|
|
* KerbGetCredentials - get the credentials from the IMAP server.
|
|
************************************************************************/
|
|
OSErr KerbGetCredentials(CKrbAuthPtr cKrbAuth, CREDENTIALS *credentials)
|
|
{
|
|
OSErr err = noErr;
|
|
char uName[ANAME_SZ];
|
|
char uInstance[INST_SZ];
|
|
char uRealm[REALM_SZ];
|
|
|
|
// fill in the credentials structure with server information
|
|
WriteZero(credentials, sizeof(CREDENTIALS));
|
|
PtoCcpy(credentials->service, cKrbAuth->pService);
|
|
PtoCcpy(credentials->instance, cKrbAuth->pInstance);
|
|
strcpy(credentials->realm, cKrbAuth->realm);
|
|
|
|
// we'll leave instance blank. Don't really know what it's for anyway.
|
|
Zero(uInstance);
|
|
|
|
// must pass the KClient routine c string user information
|
|
if ((err=KClientGetSessionUserNameCompat(&(cKrbAuth->session), uName, 0)) == noErr)
|
|
{
|
|
// must also pass the user's realm
|
|
if ((err=KClientGetLocalRealmDeprecated(uRealm))==noErr)
|
|
{
|
|
// get the credentials
|
|
err = KClientGetCredentialsDeprecated(uName, uInstance, uRealm, credentials);
|
|
}
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
/************************************************************************
|
|
* UsedKerberos - set the global flag when Kerberos gets used
|
|
************************************************************************/
|
|
void UsedKerberos(void)
|
|
{
|
|
gIMAPAuthedKerberos = true;
|
|
}
|
|
|
|
/************************************************************************
|
|
* KerberosWasUsed - return true if we should destroy the Kerberos
|
|
* ticket and/or user.
|
|
************************************************************************/
|
|
Boolean KerberosWasUsed(void)
|
|
{
|
|
Boolean result = false;
|
|
PersHandle oldPers = CurPers;
|
|
|
|
// destroy Kerberos if any personality is set to do Kerberos authenticaion for POP
|
|
for (CurPers = PersList; CurPers && !result; CurPers = (*CurPers)->next) result = PrefIsSet(PREF_KERBEROS);
|
|
|
|
// destroy Kerberos if an IMAP personality authenticated with Kerberos
|
|
if (!result)
|
|
{
|
|
if (IMAPExists())
|
|
{
|
|
result = gIMAPAuthedKerberos;
|
|
}
|
|
}
|
|
CurPers = oldPers;
|
|
return(result);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* GSSAPI
|
|
*
|
|
* Kerberos V5 via GSSAPI via SASL. Requires MIT Kfm 4.0. Taken from
|
|
* Windows Eudora with major modifications.
|
|
**********************************************************************/
|
|
#pragma mark ~~~GSSAPI~~~
|
|
|
|
#define AUTH_GSSAPI_P_NONE 1
|
|
|
|
// CGSSAPIAuthData - struct to hold info for IMAP KV4 authentication
|
|
typedef struct CGSSAPIAuthData_ CGSSAPIAuthData, *CGSSAPIAuthDataPtr;
|
|
struct CGSSAPIAuthData_
|
|
{
|
|
KClientSessionInfo session; // the KClient session
|
|
authchallenge_t challenger; // routine to receive challeng from the IMAP server
|
|
authrespond_t responder; // routine to send response to the IMAP server
|
|
MAILSTREAM* mailstream; // mailstream to authenticate
|
|
|
|
char cService[255];
|
|
char cUser[255];
|
|
char cHost[255];
|
|
};
|
|
|
|
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 InitGSSAPIAuth(CGSSAPIAuthDataPtr cgssapiPtr, authchallenge_t challenger, authrespond_t responder, PStr fqdn, MAILSTREAM* s, PStr user);
|
|
long GSSAPIAuthenticate(CGSSAPIAuthDataPtr cgssapiPtr);
|
|
|
|
/***************************************************************************************
|
|
* KrbV4Authenticator - Implement the GSSAPI authenticator. This is called by imap_auth.
|
|
***************************************************************************************/
|
|
long GssapiAuthenticator(authchallenge_t challenger, authrespond_t responder, NETMBX *mb, void *s, unsigned long *trial, char *user)
|
|
{
|
|
MAILSTREAM *stream = (MAILSTREAM *) s;
|
|
long result = 0;
|
|
Str255 pUser, pHost;
|
|
OSErr err = noErr;
|
|
CGSSAPIAuthData authData;
|
|
|
|
if (!challenger || !responder || !mb || !s || !user) return (0);
|
|
|
|
// Get host and username
|
|
GetPOPInfo(pUser, pHost);
|
|
|
|
// Build a CGSSAPIAuthData to do all the work.
|
|
err = InitGSSAPIAuth(&authData, challenger, responder, pHost, stream, pUser);
|
|
if (err == noErr)
|
|
{
|
|
// return the user name
|
|
user = PtoCcpy(user, pUser);
|
|
|
|
// never retry
|
|
*trial = 0;
|
|
|
|
// do the work
|
|
result = GSSAPIAuthenticate(&authData);
|
|
}
|
|
|
|
// Display an error message if KClient failed at some point
|
|
if (err != noErr)
|
|
{
|
|
WarnUser(NO_KERBEROS,err);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* GSSAPIAuthenticate - authenticate
|
|
***************************************************************************************/
|
|
long GSSAPIAuthenticate(CGSSAPIAuthDataPtr cgssapiPtr)
|
|
{
|
|
long ret = NIL;
|
|
char tmp[MAILTMPLEN];
|
|
OM_uint32 maj,min,mmaj,mmin;
|
|
OM_uint32 mctx = 0;
|
|
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
|
|
gss_buffer_desc chal,resp,buf;
|
|
gss_name_t crname = NIL;
|
|
long i;
|
|
int conf;
|
|
gss_qop_t qop;
|
|
OM_uint32 result;
|
|
Str255 pErrorStr;
|
|
|
|
// Get initial (empty) challenge */
|
|
//
|
|
chal.value = (unsigned char *)(*cgssapiPtr->challenger)(cgssapiPtr->mailstream, (unsigned long *) &chal.length);
|
|
|
|
if (chal.value)
|
|
{
|
|
// get initial (empty) challenge:
|
|
//
|
|
sprintf (tmp,"%s@%s", cgssapiPtr->cService, cgssapiPtr->cHost);
|
|
|
|
buf.length = strlen ( (char *) (buf.value = tmp)) + 1;
|
|
|
|
/* get service name */
|
|
if (result = gss_import_name(&min,&buf,gss_nt_service_name,&crname)!=GSS_S_COMPLETE)
|
|
{
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
}
|
|
else
|
|
{
|
|
// Work around Kfm40 bug...
|
|
Kfm40Hack();
|
|
|
|
switch (maj = /* get context */
|
|
gss_init_sec_context (&min,GSS_C_NO_CREDENTIAL,&ctx,
|
|
crname,GSS_C_NO_OID,
|
|
GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,
|
|
0,GSS_C_NO_CHANNEL_BINDINGS,
|
|
GSS_C_NO_BUFFER,NIL,&resp,NIL,NIL))
|
|
{
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
do
|
|
{
|
|
/* negotiate authentication */
|
|
if (chal.value)
|
|
fs_give ((void **) &chal.value);
|
|
|
|
/* send response */
|
|
i = (*cgssapiPtr->responder) (cgssapiPtr->mailstream, (char *) resp.value, resp.length);
|
|
|
|
gss_release_buffer (&min,&resp);
|
|
}
|
|
while (i && /* get next challenge */
|
|
(chal.value=(*cgssapiPtr->challenger)(cgssapiPtr->mailstream, (unsigned long *)&chal.length))&&
|
|
(maj = gss_init_sec_context (&min,GSS_C_NO_CREDENTIAL,&ctx,
|
|
crname,GSS_C_NO_OID,
|
|
GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG,
|
|
0,GSS_C_NO_CHANNEL_BINDINGS,&chal,
|
|
NIL,&resp,NIL,NIL) ==
|
|
GSS_S_CONTINUE_NEEDED));
|
|
|
|
|
|
case GSS_S_COMPLETE:
|
|
if (chal.value)
|
|
{
|
|
fs_give ((void **) &chal.value);
|
|
if (maj != GSS_S_COMPLETE)
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
}
|
|
|
|
/* get prot mechanisms and max size */
|
|
if ((maj == GSS_S_COMPLETE) &&
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, resp.value ? (char *) resp.value : "", resp.length) &&
|
|
(chal.value = (*cgssapiPtr->challenger) (cgssapiPtr->mailstream, (unsigned long *)&chal.length))&&
|
|
(gss_unwrap (&min,ctx,&chal,&resp,&conf,&qop) == GSS_S_COMPLETE) &&
|
|
(resp.length >= 4) && (*((char *) resp.value) & AUTH_GSSAPI_P_NONE))
|
|
{
|
|
/* make copy of flags and length */
|
|
memcpy (tmp,resp.value,4);
|
|
gss_release_buffer (&min,&resp);
|
|
|
|
/* no session protection */
|
|
tmp[0] = AUTH_GSSAPI_P_NONE;
|
|
|
|
/* install user name */
|
|
strcpy (tmp+4, cgssapiPtr->cUser);
|
|
|
|
buf.value = tmp; buf.length = strlen (cgssapiPtr->cUser) + 4;
|
|
|
|
/* successful negotiation */
|
|
if (gss_wrap (&min,ctx,FALSE,qop,&buf,&conf,&resp) == GSS_S_COMPLETE)
|
|
{
|
|
if ((*cgssapiPtr->responder) (cgssapiPtr->mailstream, (char *) resp.value, resp.length)) ret = T;
|
|
gss_release_buffer (&min,&resp);
|
|
}
|
|
else (*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
}
|
|
|
|
/* flush final challenge */
|
|
if (chal.value) fs_give ((void **) &chal.value);
|
|
|
|
/* don't need context any more */
|
|
gss_delete_sec_context (&min,&ctx,NIL);
|
|
|
|
break;
|
|
|
|
case GSS_S_CREDENTIALS_EXPIRED:
|
|
|
|
if (chal.value) fs_give ((void **) &chal.value);
|
|
|
|
pmm_log (GetRString(pErrorStr,GSSAPIErrorsStrn+kGSSAPICredExpiredError),IMAP_ERROR);
|
|
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
|
|
break;
|
|
|
|
case GSS_S_FAILURE:
|
|
if (chal.value) fs_give ((void **) &chal.value);
|
|
|
|
if (min == (OM_uint32) KRB5_FCC_NOFILE)
|
|
{
|
|
pmm_log (GetRString(pErrorStr,GSSAPIErrorsStrn+kGSSAPINoCredError),IMAP_ERROR);
|
|
}
|
|
else
|
|
do switch (mmaj = gss_display_status (&mmin,min,GSS_C_MECH_CODE,
|
|
GSS_C_NULL_OID,&mctx,&resp))
|
|
{
|
|
case GSS_S_COMPLETE:
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
ComposeRString(pErrorStr,GSSAPIErrorsStrn+kGSSAPIFailure,resp.value);
|
|
pmm_log (pErrorStr,IMAP_ERROR);
|
|
|
|
gss_release_buffer (&mmin,&resp);
|
|
}
|
|
while (mmaj == GSS_S_CONTINUE_NEEDED);
|
|
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
|
|
break;
|
|
|
|
default: /* miscellaneous errors */
|
|
|
|
if (chal.value) fs_give ((void **) &chal.value);
|
|
|
|
do switch (mmaj = gss_display_status (&mmin,maj,GSS_C_GSS_CODE,
|
|
GSS_C_NULL_OID,&mctx,&resp))
|
|
{
|
|
case GSS_S_COMPLETE:
|
|
mctx = 0;
|
|
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
ComposeRString(pErrorStr,GSSAPIErrorsStrn+kGSSAPIUnknownFailure,resp.value);
|
|
pmm_log (pErrorStr,IMAP_ERROR);
|
|
gss_release_buffer (&mmin,&resp);
|
|
}
|
|
while (mmaj == GSS_S_CONTINUE_NEEDED);
|
|
|
|
do switch (mmaj = gss_display_status (&mmin,min,GSS_C_MECH_CODE,
|
|
GSS_C_NULL_OID,&mctx,&resp))
|
|
{
|
|
case GSS_S_COMPLETE:
|
|
case GSS_S_CONTINUE_NEEDED:
|
|
ComposeRString(pErrorStr,GSSAPIErrorsStrn+kGSSAPIStatus,resp.value);
|
|
pmm_log (pErrorStr,IMAP_ERROR);
|
|
gss_release_buffer (&mmin,&resp);
|
|
}
|
|
while (mmaj == GSS_S_CONTINUE_NEEDED);
|
|
|
|
(*cgssapiPtr->responder) (cgssapiPtr->mailstream, NIL, 0);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* finished with credentials name */
|
|
if (crname) gss_release_name (&min, &crname);
|
|
}
|
|
return ret; /* return status */
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* KfM 4.0 Hack
|
|
*
|
|
* There's a bug in the GSSAPI Library. The credentials are stored as a global, which
|
|
* is maintained even after the user manually destroys tickets. gss_init_sec_context()
|
|
* will fail with a nasty dialog under classic, fail quietly in OS X, and never allow
|
|
* the user to re-authenticate.
|
|
*
|
|
* The workaround is to update that global with the following two calls. Thanks to the
|
|
* support team at MIT.
|
|
*
|
|
* - jdboyd 041902
|
|
***************************************************************************************/
|
|
void Kfm40Hack(void)
|
|
{
|
|
OM_uint32 hackmin, hacklifetime;
|
|
gss_cred_id_t hackcred = GSS_C_NO_CREDENTIAL;
|
|
gss_name_t hackname = NIL;
|
|
gss_cred_usage_t hackusage;
|
|
gss_OID_set hackoids = GSS_C_NO_OID;
|
|
|
|
if (gss_inquire_cred(&hackmin,hackcred,&hackname,&hacklifetime,&hackusage,&hackoids) == GSS_S_COMPLETE)
|
|
gss_release_cred(&hackmin,&hackcred);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* NewKrbV4 - build a KrbV4 object to do the authentication work
|
|
***************************************************************************************/
|
|
OSErr InitGSSAPIAuth(CGSSAPIAuthDataPtr cgssapiPtr, authchallenge_t challenger, authrespond_t responder, PStr fqdn, MAILSTREAM* s, PStr user)
|
|
{
|
|
OSErr err = noErr;
|
|
Str255 pService;
|
|
|
|
WriteZero(cgssapiPtr, sizeof(CGSSAPIAuthDataPtr));
|
|
|
|
cgssapiPtr->challenger = challenger;
|
|
cgssapiPtr->responder = responder;
|
|
cgssapiPtr->mailstream = s;
|
|
|
|
PtoCcpy(cgssapiPtr->cUser,user);
|
|
GetRString(pService,KERBEROS_IMAP_SERVICE);
|
|
PtoCcpy(cgssapiPtr->cService,pService);
|
|
PtoCcpy(cgssapiPtr->cHost,fqdn);
|
|
|
|
return (err);
|
|
} |