1 line
30 KiB
C
Executable File
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);
|
|
}
|