eudora-mac/acap.c

1 line
30 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 "acap.h"
#define FILE_NUM 103
#pragma segment ACAP
/************************************************************************
* Simplified ACAP handling
* There are lots of things not up to snuff here. If we're really
* going to use ACAP as intended by ACAP Nazis, it will have to be made
* much less efficient.
************************************************************************/
typedef enum
{
adOK = 1,
adManual,
adQuit,
adCancel,
adServer,
adUser,
adPassword,
adCapsLockWarning,
adLimit
} ACAPDlogEnum;
typedef enum
{
acsTag, // expecting tag
acsResponse, // expecting response
acsText, // expecting open paren
acsFlush, // flush to newline
acsLimit
} ACAPCmdState, *ACAPCmdStatePtr, **ACAPCmdStateHandle;
typedef enum
{
alsFree, // not looking at anything yet
alsAtom, // collecting atom
alsQuote, // collecting quote
alsEscapedQuote, // collecting quote, just saw a backslash
alsDigit, // saw an open brace
alsCloseBrace, // saw a close brace
alsBraceCR, // saw the cr after the close brace
alsLiteral, // inside a literal
alsCR, // just saw a CR
alsLF, // just saw an LF
alsLimit
} ACAPLexState, *ACAPLexStatePtr, **ACAPLexStateHandle;
typedef enum
{
acaptString,
acaptOpenParen,
acaptCloseParen,
acaptNewline,
acaptLimit
} ACAPToken, *ACAPTokenPtr, **ACAPTokenHandle;
typedef struct AccuLL **AccuLLHandle;
typedef struct AccuLL
{
Accumulator strings;
AccuLLHandle next;
} AccuLL, *AccuLLPtr, **AccuLLHandle;
typedef struct
{
TransStream stream; // open stream
ACAPCmdState state; // current command state
long tag; // current tag
ACAPEnum response; // current response
Str255 responseValue; // text of the response
Str255 tokenValue; // text of the token
ACAPToken token; // token type
Accumulator text; // lists of strings of the result. Each list is separated by a nul.
UHandle buffer; // incoming data buffer
long spot; // spot in the buffer
long end; // end of the buffer
Boolean hasPlain; // has plaintext authentication
Boolean hasAPOP; // has apop authentication
Boolean hasCram; // has cram-md5 authentication
} ACAPState, *ACAPStatePtr, **ACAPStateHandle;
typedef struct
{
short pref;
short type;
long reserved;
Str255 name;
} ACAPResTemplate, *ACAPResTemplatePtr, **ACAPResTemplateHandle;
#define ACAPDone(r) (r==acapBad || r==acapOK || r==acapNo || r==acapBye)
OSErr GetACAPLogin(PStr server, PStr user, PStr password, Boolean giveQuit);
OSErr ACAPLogin(PStr server, PStr user, PStr password, ACAPStateHandle state);
OSErr ACAPLoadLo(ACAPStateHandle state);
OSErr ParseACAP(ACAPStateHandle state);
OSErr ACAPFill(ACAPStateHandle state);
ACAPStateHandle NewACAPState(void);
OSErr ACAPSetPrefRes(PStr name,PStr value,Handle acapRes);
void DisposeACAPState(ACAPStateHandle state);
OSErr ACAPBanner(ACAPStateHandle state,PStr challenge);
OSErr ACAPGetToken(ACAPStateHandle state);
OSErr ACAPLoginWAPOP(PStr user, PStr password, ACAPStateHandle state,PStr challenge);
OSErr ACAPLoginPlain(PStr user, PStr password, ACAPStateHandle state);
OSErr ACAPLoginWCram(PStr user, PStr password, ACAPStateHandle state);
OSErr ACAPLoginWFunnyCram(PStr user, PStr password, ACAPStateHandle state,PStr challenge);
PStr NextAccuString(PStr string,AccuPtr a,long *context);
OSErr ACAPReadAndSet(ACAPStateHandle state);
OSErr ACAPGetServer(PStr server);
OSErr ACAPTryServer(PStr server);
OSErr ACAPGetServerLo(PStr remaining,PStr server);
pascal Boolean ACAPLoginFilter(DialogPtr dgPtr,EventRecord *event,short *item);
/************************************************************************
* ACAPLoad - load initial settings with acap
************************************************************************/
OSErr ACAPLoad(Boolean giveQuit)
{
Str63 server;
Str63 user;
Str63 password;
Str255 s;
ACAPStateHandle state=NewACAPState();
OSErr err = fnfErr;
short retry;
Boolean hunt = True;
Boolean failedOnce = False;
if (state)
{
OTInitOpenTransport(); // just in case
*user = 0;
*server = 0;
do
{
if (!hunt || !(err=ACAPGetServer(server)))
{
if (!*user) GetPref(user,PREF_ACAP_USER);
if (!*user) GetPOPInfo(user,password);
GetPref(password,PREF_ACAP_PASS);
if (!*password) PCopy(password,(*CurPersSafe)->password);
if (!*password) GetPref(password,PREF_PASS_TEXT);
if (!failedOnce && *user && *password && *server || !(err=GetACAPLogin(server,user,password,giveQuit)))
{
failedOnce = True;
if (!*server) err = ACAPGetServer(server);
if (!err)
{
OpenProgress();
ProgressMessageR(kpTitle,ACAPPING);
if (!(err = ACAPLogin(server,user,password,state)))
{
err = ACAPLoadLo(state);
if (!*(*CurPersSafe)->password) PSCopy((*CurPersSafe)->password,password);
}
else *password = 0;
if ((*state)->stream) DisTrans((*state)->stream);
CloseProgress();
}
}
if (!err)
{
// we found a server. remember it
SetPref(PREF_ACAP_SERVER,server);
SetPref(PREF_ACAP_USER,user);
if (PrefIsSet(PREF_SAVE_PASSWORD)) SetPref(PREF_ACAP_PASS,password);
if (!*GetPOPPref(s)) WarnUser(ACAP_FAILED,err);
break;
}
}
hunt = False;
if (err)
{
if (err!=userCanceledErr)
{
retry = ReallyDoAnAlert(giveQuit ? ACAP_RETRY_ALRT:ACAP_RETRY_CNCL_ALRT,Normal);
if (retry==adOK) err = 0;
else if (retry == adQuit || !giveQuit && retry==adManual)
{
if (giveQuit) {Cleanup(); ExitToShell();}
}
}
}
}
while (!err);
}
DisposeACAPState(state);
return(err);
}
/************************************************************************
* ACAPLoginFilter - a ModalDialog filter for the acap login
************************************************************************/
pascal Boolean ACAPLoginFilter(DialogPtr dgPtr,EventRecord *event,short *item)
{
extern UHandle PwChars;
MyWindowPtr win = GetDialogMyWindowPtr (dgPtr);
if (MiniMainLoop(event))
{
*item = adManual;
return(True);
}
if (win && win->dialogRefcon=='acap')
{
if (event->what==keyDown || event->what==autoKey)
{
char key = event->message & charCodeMask;
switch (key)
{
case enterChar:
case returnChar:
*item = 1;
return(True);
break;
case backSpace:
if (*item==adPassword && **PwChars) --**PwChars;
return(False);
break;
case tabChar:
return(False);
break;
case '.':
if (event->modifiers & cmdKey)
{
*item=2;
return(True);
break;
}
/* fall through */
default:
if (GetDialogKeyboardFocusItem(dgPtr) == adPassword)
{
if (**PwChars < 255)
{
PCatC(*PwChars,key);
event->message = ((event->message >> 8)<<8) | bulletChar;
}
else
{
SysBeep(20);
event->what = nullEvent;
}
}
break;
}
}
else if (event->what==updateEvt)
{
if (GetDialogFromWindow((WindowPtr) event->message) == dgPtr)
HiliteButtonOne(dgPtr);
else
UpdateMyWindow((WindowPtr)event->message);
}
else
{
if (TickCount()%120<100 && CurrentModifiers()&alphaLock)
ShowDialogItem(dgPtr,adCapsLockWarning);
else
HideDialogItem(dgPtr,adCapsLockWarning);
EnableDItemIf(dgPtr,adOK,**PwChars);
}
}
return(False);
}
/************************************************************************
* GetACAPLogin - get login info from the user
************************************************************************/
OSErr GetACAPLogin(PStr server, PStr user, PStr password,Boolean giveQuit)
{
extern OSErr ResetPassword(void);
extern void CopyPassword(PStr);
MyWindowPtr dgPtrWin;
DialogPtr dgPtr;
short item;
DECLARE_UPP(ACAPLoginFilter,ModalFilter);
INIT_UPP(ACAPLoginFilter,ModalFilter);
if (!MommyMommy(ATTENTION,nil)) return(userCanceledErr);
if ((dgPtrWin = GetNewMyDialog(ACAP_LOGIN_DLOG,nil,nil,InFront))==nil)
{
WarnUser(GENERAL,MemError());
return(userCanceledErr);
}
dgPtr = GetMyWindowDialogPtr (dgPtrWin);
if (!(CurrentModifiers()&alphaLock)) HideDialogItem(dgPtr,adCapsLockWarning);
SetDIText(dgPtr,adServer,server);
SetDIText(dgPtr,adUser,user);
SetDIText(dgPtr,adPassword,"");
HideDialogItem(dgPtr,giveQuit ? adCancel : adQuit);
if (!*server) item = adServer;
else if (!*user) item = adUser;
else item = adPassword;
SelectDialogItemText(dgPtr,item,0,REAL_BIG);
StartMovableModal(dgPtr);
ShowWindow(dgPtr);
HiliteButtonOne(dgPtr);
PushCursor(arrowCursor);
dgPtrWin->dialogRefcon = 'acap';
do
{
if (ResetPassword()) item = adQuit;
else
{
MovableModalDialog(dgPtr,ACAPLoginFilterUPP,&item);
CopyPassword(password);
}
}
while (item==adOK && !*password);
PopCursor();
if (item==adOK)
{
GetDIText(dgPtr,adServer,server);
GetDIText(dgPtr,adUser,user);
}
else if (item==adQuit)
{
Cleanup();
ExitToShell();
}
EndMovableModal(dgPtr);
DisposDialog_(dgPtr);
InBG = False;
return(item==adOK ? noErr : userCanceledErr);
}
/************************************************************************
* ACAPGetServer - figure out what ACAP server to use
************************************************************************/
OSErr ACAPGetServer(PStr server)
{
uLong addr, mask;
Str127 remaining;
OSErr err;
if (*GetPref(server,PREF_ACAP_SERVER)) return(noErr);
OpenProgress();
ProgressMessageR(kpTitle,ACAP_SERVER_SEARCH);
GetMyHostid((void*)&addr,(void*)&mask);
ComposeString(remaining,"\p%I",addr);
if ((err=ACAPGetServerLo(remaining,server)) && !CommandPeriod && gUseOT)
{
DNSHostid(&addr);
ComposeString(remaining,"\p%I",addr);
err = ACAPGetServerLo(remaining,server);
}
CloseProgress();
return(err);
}
/************************************************************************
* ACAPGetServerLo - look for the acap server, given a name to start from
************************************************************************/
OSErr ACAPGetServerLo(PStr remaining,PStr server)
{
UPtr dot;
Str127 hostname;
while ((dot = PIndex(remaining,'.')) && !CommandPeriod)
{
if (*remaining - (dot-remaining) < 3) break; // don't do queries like acap.co.nz
GetRString(hostname,ACAPStrn+acapACAP);
PCatC(hostname,'.');
PCat(hostname,remaining);
if (!ACAPTryServer(hostname))
{
PCopy(server,hostname);
return(noErr);
}
*remaining -= dot-remaining;
BMD(dot+1,remaining+1,*remaining);
}
return(fnfErr);
}
/************************************************************************
* ACAPTryServer - see if there is an acap server here
************************************************************************/
OSErr ACAPTryServer(PStr server)
{
TransStream stream=nil;
OSErr err = NewTransStream(&stream);
if (!err)
{
err = ConnectTrans(stream,server,GetRLong(ACAP_PORT),True,GetRLong(SHORT_OPEN_TIMEOUT));
if (!err) DestroyTrans(stream);
ZapTransStream(&stream);
}
return(err);
}
/************************************************************************
* ACAPLogin - perform login on the ACAP server
************************************************************************/
OSErr ACAPLogin(PStr server, PStr user, PStr password, ACAPStateHandle state)
{
TransStream stream=nil;
OSErr err = NewTransStream(&stream);
Str255 challenge;
(*state)->stream = stream;
if (!err) err = ConnectTrans(stream,server,GetRLong(ACAP_PORT),False,GetRLong(OPEN_TIMEOUT));
if (!err)
{
err = ParseACAP(state); // read banner
if (!err)
{
ACAPBanner(state,challenge);
if ((*state)->hasCram)
if (*challenge)
err = ACAPLoginWFunnyCram(user,password,state,challenge);
else
err = ACAPLoginWCram(user,password,state);
else if ((*state)->hasAPOP && *challenge)
err = ACAPLoginWAPOP(user,password,state,challenge);
else if ((*state)->hasPlain)
err = ACAPLoginPlain(user,password,state);
else
err = 1;
}
}
if (err)
{
(*state)->stream = nil;
if (stream)
{
DestroyTrans(stream);
ZapTransStream(&stream);
}
}
return(err);
}
/************************************************************************
* ACAPLoginWFunnyCram - login with cram-md5, Randy's way
************************************************************************/
OSErr ACAPLoginWFunnyCram(PStr user, PStr password, ACAPStateHandle state,PStr challenge)
{
Str255 string, scratch;
long context = 0;
OSErr err = fnfErr;
short myTag = ++StupidTagForACAPandI4;
GenKeyedDigest(challenge,password,string);
ComposeRString(scratch,ACAPStrn+acapLoginParms,ACAPStrn+acapCram,user,string);
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCmd,
myTag,
ACAPStrn+acapAuth,
scratch,
NewLine);
if (!err)
while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)));
if (!err && (*state)->response!=acapOK) err = 1;
return(err);
}
/************************************************************************
* ACAPLoginWCram - login with cram-md5
************************************************************************/
OSErr ACAPLoginWCram(PStr user, PStr password, ACAPStateHandle state)
{
Str255 string, challenge;
long context = 0;
OSErr err = fnfErr;
short myTag = ++StupidTagForACAPandI4;
/*
* tell it we want cram-md5
*/
*string = 0;
PCatC(string,'"');
PCatR(string,ACAPStrn+acapCram);
PCatC(string,'"');
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCmd,
myTag,
ACAPStrn+acapAuth,
string,
NewLine);
/*
* wait for the challenge
*/
if (!err) while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)))
{
if ((*state)->tag==-1)
{
// got the challenge!
PCopy(challenge,(*state)->responseValue);
if (*challenge>5 && challenge[1]=='<')
{
// got a banner
GenKeyedDigest(challenge,password,string);
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCramMore,user,string,NewLine);
if (!err) while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)));
}
else err = fnfErr;
break;
}
}
if (!err && (*state)->response!=acapOK) err = 1;
return(err);
}
/************************************************************************
* ACAPLoginWAPOP - login with apop
************************************************************************/
OSErr ACAPLoginWAPOP(PStr user, PStr password, ACAPStateHandle state,PStr challenge)
{
Str255 string, scratch;
long context = 0;
Accumulator a = (*state)->text;
OSErr err = fnfErr;
short myTag = ++StupidTagForACAPandI4;
GenDigest(challenge,password,string);
ComposeRString(scratch,ACAPStrn+acapLoginParms,ACAPStrn+acapAPOP,user,string);
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCmd,
myTag,
ACAPStrn+acapAuth,
scratch,
NewLine);
if (!err)
while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)));
if (!err && (*state)->response!=acapOK) err = 1;
return(err);
}
/************************************************************************
* ACAPLoginPlain - plain login
************************************************************************/
OSErr ACAPLoginPlain(PStr user, PStr password, ACAPStateHandle state)
{
Str255 scratch;
OSErr err;
short myTag = ++StupidTagForACAPandI4;
ComposeRString(scratch,ACAPStrn+acapLoginParms,ACAPStrn+acapPlain,user,password);
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCmd,
myTag,
ACAPStrn+acapAuth,
scratch,
NewLine);
if (!err)
while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)));
if (!err && (*state)->response!=acapOK) err = 1;
return(err);
}
/************************************************************************
* ACAPLoadLo - load settings from a stream
************************************************************************/
OSErr ACAPLoadLo(ACAPStateHandle state)
{
OSErr err;
Str255 scratch;
short myTag = ++StupidTagForACAPandI4;
err = ComposeRTrans((*state)->stream,ACAPStrn+acapCmd,
myTag,
ACAPStrn+acapSearch,
GetRString(scratch,ACAPStrn+acapSearchMumboJumbo),
NewLine);
while (!(err=ParseACAP(state)) && ((*state)->tag!=myTag || !ACAPDone((*state)->response)))
{
if ((*state)->tag==myTag && (*state)->response==acapEntry)
ACAPReadAndSet(state);
}
return(err);
}
/************************************************************************
* ACAPReadAndSet - read acap response and set prefs from it
************************************************************************/
OSErr ACAPReadAndSet(ACAPStateHandle state)
{
Str255 name;
Str255 value;
PStr which=name;
Accumulator a = (*state)->text;
long context = 0;
Handle acapRes = GetResource('EuAC',128);
OSErr err = noErr;
short paren = 0;
if (!acapRes) return(fnfErr);
else HNoPurge(acapRes);
NextAccuString(which,&a,&context); // clear the entry name
while (NextAccuString(which,&a,&context))
{
if (EqualStrRes(which,ACAPStrn+acapOpen))
{
which = name;
paren++;
}
else if (EqualStrRes(which,ACAPStrn+acapClose))
{
if (paren) paren--;
which = name;
}
else if (paren==1 || paren==2)
{
if (which==name) which = value;
else if (which==value) // got one!
{
ACAPSetPrefRes(name,value,acapRes);
which = name;
}
}
}
return(err);
}
/************************************************************************
* ACAPSetPrefRes - set a pref from the acap stream
************************************************************************/
OSErr ACAPSetPrefRes(PStr name,PStr value,Handle acapRes)
{
ACAPResTemplate template;
UPtr spot,end;
long bytes;
spot = LDRef(acapRes);
end = spot + GetHandleSize(acapRes);
for (;spot<end;spot += 9+spot[8])
{
if (StringSame(name,spot+8))
{
BMD(spot,&template,9+spot[8]);
if (value[value[0]] == ' ') // remove spurious trailing space
value[0]--;
if (template.type == ACAPBoolean)
{
if (*value && value[1]=='1')
SetPref(template.pref,YesStr);
else
SetPref(template.pref,NoStr);
}
else if (template.type == ACAPRevBoolean)
{
if (*value && value[1] == '1')
SetPref(template.pref,NoStr);
else
SetPref(template.pref,YesStr);
break;
}
else if (template.type == ACAPWeKeepK)
{
StringToNum(value,&bytes);
SetPrefLong(template.pref,bytes/(1 K));
}
else
{
switch(template.pref)
{
case PREF_INTERVAL:
if (value[1] == '0')
SetPref(PREF_AUTO_CHECK,NoStr);
else
SetPref(PREF_AUTO_CHECK,YesStr);
SetPref(PREF_INTERVAL,value);
break;
case PREF_POP_MODE:
if (StringSame(value,"\pNormal"))
SetPrefLong(PREF_POP_MODE,1);
else if (StringSame(value,"\pSTATUS"))
SetPrefLong(PREF_POP_MODE,2);
else if (StringSame(value,"\pPOP Last"))
SetPrefLong(PREF_POP_MODE,3);
break;
case DUMMY_POP_AUTHENTICATE:
SetPref(PREF_DONT_PASS,YesStr);
SetPref(PREF_KERBEROS,NoStr);
SetPref(PREF_APOP,NoStr);
switch (FindSTRNIndex(ACAPExtraStrn,value))
{
case acapsKerberos: SetPref(PREF_KERBEROS,YesStr); break;
case acapsAPOP: SetPref(PREF_APOP,YesStr); break;
default:
case acapsPassword: SetPref(PREF_DONT_PASS,NoStr); break;
}
break;
case PREF_FINGER:
if (FindSTRNIndex(ACAPExtraStrn,value)==acapsFinger)
SetPref(PREF_FINGER,YesStr);
else
SetPref(PREF_FINGER,NoStr);
break;
case DUMMY_SEND_FORMAT:
SetPref(PREF_SINGLE,NoStr);
SetPref(PREF_DOUBLE,NoStr);
SetPref(PREF_BINHEX,NoStr);
SetPref(PREF_UUENCODE,NoStr);
switch (FindSTRNIndex(ACAPExtraStrn,value))
{
case acapsSingle: SetPref(PREF_SINGLE,YesStr); break;
case acapsBinHex: SetPref(PREF_BINHEX,YesStr); break;
case acapsUuencode: SetPref(PREF_UUENCODE,YesStr); break;
default:
case acapsDouble: SetPref(PREF_DOUBLE,YesStr); break;
}
break;
case BIG_MESSAGE:
if (value[value[0]] == 'K')
value[0]--;
if (value[1] == '0')
SetPref(PREF_NO_BIGGIES,NoStr);
else
SetPref(PREF_NO_BIGGIES,YesStr);
SetPref(BIG_MESSAGE,value);
break;
default:
SetPref(template.pref,value);
break;
}
}
}
}
return noErr; /* really should check the values of all those SetPrefs, but .... */
}
/************************************************************************
* ParseACAP - interpret the acap data stream. Currently does not know what
* commands have been issued; unacceptable in the long run, but good
* enough for now. Eventually, we will probably want to register handlers
* for each tag, and this routine will call the handlers.
* Returns after every line of data
************************************************************************/
OSErr ParseACAP(ACAPStateHandle state)
{
OSErr err;
Str255 scratch;
ACAPEnum response;
long val;
UPtr spot;
Accumulator text = (*state)->text;
// housecleaning
(*state)->response = (*state)->tag = -1;
text.offset = 0;
/*
* read tokens so long as there is no error
*/
while (!(err=ACAPGetToken(state)))
{
switch((*state)->state)
{
/*
* tag - read its value
*/
case acsTag:
if ((*state)->token==acaptString)
{
if ((*state)->tokenValue[0]==1 && (*state)->tokenValue[1]=='+')
(*state)->tag = -1;
else
{
PSCopy(scratch,(*state)->tokenValue);
*scratch = MIN(*scratch,sizeof(scratch)-2);
scratch[*scratch+1] = 0;
for (spot=scratch+1;*spot && !isdigit(*spot);spot++);
val = atoi(spot);
(*state)->tag = val;
}
(*state)->state = acsResponse;
}
else (*state)->state = acsFlush;
break;
/*
* response; look it up in our table and give it a numeric value
*/
case acsResponse:
if ((*state)->token==acaptString)
{
PSCopy(scratch,(*state)->tokenValue);
PSCopy((*state)->responseValue,(*state)->tokenValue);
response = FindSTRNIndex(ACAPStrn,scratch);
(*state)->response = response;
(*state)->state = acsText;
}
else (*state)->state = acsFlush;
break;
/*
* read text value
*/
case acsText:
switch((*state)->token)
{
case acaptOpenParen:
GetRString(scratch,ACAPStrn+acapOpen);
break;
case acaptCloseParen:
GetRString(scratch,ACAPStrn+acapClose);
break;
case acaptString:
PCopy(scratch,(*state)->tokenValue);
break;
default:
(*state)->state = acsFlush;
break;
}
if ((*state)->state != acsFlush)
AccuAddPtr(&text,scratch,*scratch+1);
break;
/*
* we don't like what we've seen; dump the rest of the response
*/
case acsFlush:
break; // keep flushing
}
/*
* bail out after every line
*/
if ((*state)->token==acaptNewline)
{
(*state)->state = acsTag;
break;
}
}
(*state)->text = text; // copy it back
return(err);
}
/************************************************************************
* ACAPGetToken - tokenize the acap stream
************************************************************************/
OSErr ACAPGetToken(ACAPStateHandle state)
{
ACAPLexState ls = alsFree;
OSErr err=noErr;
Byte c;
Str255 token;
Boolean white;
Boolean done = False;
long count = 0;
*token = 0;
while (!done)
{
// fill buffer if need be
if ((*state)->spot==(*state)->end && (err = ACAPFill(state))) break;
c = (*(*state)->buffer)[(*state)->spot++];
white = IsWhite(c);
switch (ls)
{
case alsFree:
switch (c)
{
case '"': ls = alsQuote; break;
case '\015': ls = alsCR; break;
case '{': ls = alsDigit; break;
case '(':
case ')': PCatC(token,c); done = True; break;
default:
if (!white)
{
ls = alsAtom;
PCatC(token,c);
}
break;
}
break;
case alsAtom:
if (white || c=='"' || c=='\015' || c=='{' || c=='(' || c==')')
{
done = True;
(*state)->spot--;
}
else
PCatC(token,c);
break;
case alsQuote:
if (c=='\\') ls = alsEscapedQuote;
else if (c=='"') done = True;
else if (c=='\015')
{
done = True;
(*state)->spot--;
}
else PCatC(token,c);
break;
case alsEscapedQuote:
if (c=='\015')
{
done = True;
(*state)->spot--;
}
else
PCatC(token,c);
ls = alsQuote;
break;
case alsDigit:
if (isdigit(c)) count = 10*count + (c-'0');
else ls = alsCloseBrace;
break;
case alsCloseBrace:
ls = alsBraceCR;
break;
case alsBraceCR:
ls = alsLiteral;
break;
case alsLiteral:
if (!count)
{
(*state)->spot--;
done = True;
}
else
{
count--;
PCatC(token,c);
}
break;
case alsCR:
if (c=='\012')
{
ls = alsLF;
done = True;
}
break;
}
}
if (!err)
{
// WARNING -- this stuff is UTF-8, and we're not dealing with that
PCopy((*state)->tokenValue,token);
switch (ls)
{
case alsLiteral:
case alsEscapedQuote:
case alsQuote:
case alsAtom: (*state)->token = acaptString; break;
case alsLF: (*state)->token = acaptNewline; break;
case alsFree: (*state)->token = c=='(' ? acaptOpenParen : acaptCloseParen; break;
default: (*state)->token = acaptLimit; break;
}
}
return(err);
}
/************************************************************************
* ACAPFill - fill the buffer
************************************************************************/
OSErr ACAPFill(ACAPStateHandle state)
{
long len = GetHandleSize((*state)->buffer);
OSErr err;
if (err=RecvTrans((*state)->stream,LDRef((*state)->buffer),&len)) /* get some chars */
len=0;
if (len)
{
UTF8To88591(*(*state)->buffer,len,*(*state)->buffer,&len);
TransLitRes(*(*state)->buffer,len,ktISOMac);
}
(*state)->spot = 0;
(*state)->end = len;
UL((*state)->buffer);
return(err);
}
/************************************************************************
* ACAPBanner - parse the banner
************************************************************************/
OSErr ACAPBanner(ACAPStateHandle state,PStr challenge)
{
Accumulator a = (*state)->text;
Str255 s;
long context = 0;
short bannerState = 0;
*challenge = 0;
if ((*state)->response==acapBye) return(fnfErr);
while (NextAccuString(s,&a,&context))
{
switch(FindSTRNIndex(ACAPStrn,s))
{
case acapPlain: if (bannerState == acapSASL) (*state)->hasPlain = True; break;
case acapAPOP: if (bannerState == acapSASL) (*state)->hasAPOP = True; break;
case acapCram: if (bannerState == acapSASL) (*state)->hasCram = True; break;
case acapXCram: if (bannerState == 0) bannerState = acapCram; break;
case acapOpen: bannerState = 0; break;
case acapClose: bannerState = -1; break;
case acapSASL: if (bannerState == 0) bannerState = acapSASL; break;
default:
if (bannerState == acapCram) PCopy(challenge,s);
else if (bannerState != acapSASL) bannerState = -1;
break;
}
}
return(noErr);
}
/************************************************************************
* NewACAPState - a new acap state
************************************************************************/
ACAPStateHandle NewACAPState(void)
{
ACAPStateHandle state = NewZH(ACAPState);
UHandle buffer;
Accumulator accu;
OSErr err;
if (state)
{
buffer = NewIOBHandle(255,OPTIMAL_BUFFER);
(*state)->buffer = buffer;
if (!(err = AccuInit(&accu))) (*state)->text = accu;
if (err || !buffer)
{
DisposeACAPState(state);
state = nil;
}
}
return(state);
}
/************************************************************************
* DisposeACAPState - destroy one
************************************************************************/
void DisposeACAPState(ACAPStateHandle state)
{
TransStream stream;
if (state)
{
ZapHandle((*state)->buffer);
AccuZap((*state)->text);
if (stream = (*state)->stream)
{
DestroyTrans(stream);
ZapTransStream(&stream);
}
}
ZapHandle(state);
}
/************************************************************************
* NextAccuString - read a string from an accumulator
************************************************************************/
PStr NextAccuString(PStr string,AccuPtr a,long *context)
{
*string = 0;
if (*context>=a->offset) return(nil);
PCopy(string,(*a->data)+*context);
*context += *string+1;
return(string);
}