1 line
95 KiB
C
Executable File
1 line
95 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 "pop.h"
|
||
#include "myssl.h"
|
||
#define FILE_NUM 30
|
||
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
|
||
/*
|
||
5/29/97 cwong
|
||
NOTE:
|
||
|
||
You should use the following macros when accessing POPD resources in the settings file:
|
||
|
||
GetResourceMainThread_
|
||
ZapSettingsResourceMainThread
|
||
AddMyResourceMainThread_
|
||
|
||
They access the main thread's Settings file (not the background thread's copy.)
|
||
*/
|
||
|
||
/************************************************************************
|
||
* functions for dealing with a pop 2 server
|
||
************************************************************************/
|
||
|
||
#ifdef KERBEROS
|
||
#include <krb.h>
|
||
#endif
|
||
#pragma segment POP
|
||
|
||
#define CMD_BUFFER 514
|
||
#define PSIZE (UseCTB ? 256 : 4096)
|
||
#define errNotFound -2
|
||
|
||
#define MatchPOPD(old,oldSpot,hash) \
|
||
((hash) && (*(old))[oldSpot].uidHash==(hash))
|
||
|
||
#define POP_TERM(buffer,size) ((size)==2 && (buffer)[0]=='.' && (buffer)[1]=='\015')
|
||
/************************************************************************
|
||
* private routines
|
||
************************************************************************/
|
||
void POPDelDup(POPDHandle popDH);
|
||
OSErr POPPreFetch(TransStream stream,POPDHandle popDH,short message,Boolean *capabilities);
|
||
int POPGetReplyLo(TransStream stream,short cmd, UPtr buffer, long *size,AccuPtr resAcc);
|
||
#define POPGetReply(stream,cmd,buffer,size) POPGetReplyLo(stream,cmd,buffer,size,nil)
|
||
void RelatedNote(FSSpecPtr spec,HeaderDHandle hdh,PStr theMessage);
|
||
int POPByeBye(TransStream stream);
|
||
int POPCmdLo(TransStream stream, short cmd, UPtr args, AccuPtr argsAcc);
|
||
#define POPCmd(stream,cmd,args) POPCmdLo(stream,cmd,args,nil)
|
||
int POPGetMessage(TransStream,long messageNumber,short *gotSome,POPDHandle popDH,Boolean *capabilities);
|
||
int DupHeader(short refN,UPtr buff,long bSize,long offset,long headerSize);
|
||
int SaveAndSplit(TransStream stream,short refN,long estSize,HeaderDHandle *hdhp,Boolean isIMAP);
|
||
Boolean StackLowErr = false;
|
||
Boolean PopConnected;
|
||
int FirstUnread(TransStream stream,int count);
|
||
Boolean HasBeenRead(TransStream stream,short msgNum,short count);
|
||
void StampPartNumber(MSumPtr sum,short part,short count);
|
||
UPtr ExtractStamp(UPtr stamp,UPtr banner);
|
||
short POPLast(TransStream,short *lastRead);
|
||
POPLineType ReadPlainBody(TransStream stream,short refN,char *buf,long bSize,long estSize);
|
||
short SplitMessage(short refN, long hStart, long hEnd, long msgEnd);
|
||
void DisposePOPD(POPDHandle *popDH);
|
||
OSErr BuildPOPD(TransStream stream,POPDHandle *popDH,short count,XferFlags *flags,Boolean *capabilities);
|
||
void FillPOPD(POPDPtr pdp,HeaderDHandle hdh);
|
||
short CountFetch(POPDHandle popDH);
|
||
PStr HeaderMsgId(HeaderDHandle hdh,PStr msgId);
|
||
uLong FakeMIDHash(HeaderDHandle hdh);
|
||
void SetFetchDel(POPDHandle popDH,short from, short to,Boolean fetch, Boolean delete);
|
||
void SetFetched(POPDHandle popDH,short from, short to);
|
||
void SetBeforeAfter(POPDHandle popDH,uLong gmt,short *after,short *before);
|
||
OSErr POPMsgSize(short messageNumber,long *msgsize);
|
||
short FindExistSpot(POPDHandle popDH,uLong hash);
|
||
OSErr DeletePOPMessage(TransStream stream,short number,long uidHash);
|
||
OSErr FillWithUidl(TransStream stream,POPDHandle popDH);
|
||
OSErr FillWithTop(TransStream stream,POPDHandle new, POPDHandle old);
|
||
OSErr FillSizesWithList(TransStream stream,POPDHandle popDH);
|
||
short FindUndelete(POPDHandle popDH,uLong gmt,uLong hash);
|
||
OSErr FillPOPDFromServer(TransStream stream,POPDHandle popDH,short spot);
|
||
|
||
OSErr InitKerberos();
|
||
OSErr KerbGetTicket(PStr popName,PStr host,PStr realm,PStr version,UHandle *ticket);
|
||
OSErr SendPOPTicket(TransStream stream);
|
||
void LogPOPD(PStr intro,POPDHandle newDH);
|
||
void Log1POPD(PStr intro, PStr which, POPDHandle popDH);
|
||
Boolean NoClearPass(Boolean *capabilities,UPtr response, short len);
|
||
void PrunePOPD(OSType listType,short listId, POPDHandle onServer);
|
||
OSErr ReapCmds(TransStream stream, short cmd);
|
||
void PopCapabilities(TransStream stream, Boolean *capabilities,SASLEnum *mechPtr);
|
||
int POPSasl(TransStream stream, Boolean *capabilities, SASLEnum mech, UPtr buffer, long *size);
|
||
OSErr FixLongFilename(HeaderDHandle hdh,FSSpecPtr spec);
|
||
PStr Un2184Append(PStr dest,short sizeofDest,PStr orig,PStr charset,Boolean isEncoded);
|
||
PStr Un2184(PStr dest, PStr orig, PStr charset);
|
||
|
||
/* stack sniffer defines */
|
||
|
||
// 5k seems to work for ppc. may need to tweak it some more. s/b smaller for 68k?
|
||
#define kLowStackSize ((GetCurrentISA() == kPowerPCISA) ? (5 K) : (4 K))
|
||
#define kMoreStackSpace (10 K)
|
||
|
||
/* Globals */
|
||
|
||
Boolean gPOPKerbInited = false; // true when Kerberos has been initialized for POP
|
||
KClientSessionInfo gSession; // session info
|
||
KClientKey gPrivateKey; // private key
|
||
|
||
|
||
/************************************************************************
|
||
* GetMyMail - the biggie; transfers mail into In mailbox
|
||
************************************************************************/
|
||
short GetMyMail(TransStream stream,Boolean quietly,short *gotSome,struct XferFlags *flags)
|
||
{
|
||
#pragma unused(quietly)
|
||
int messageCount;
|
||
Str255 msgname;
|
||
Str63 hostName;
|
||
long port;
|
||
TOCHandle tocH;
|
||
short err;
|
||
short fetchCount, message, fetched;
|
||
POPDHandle popDH=nil;
|
||
Boolean built = True;
|
||
int beforeBytes,
|
||
actualBytes,
|
||
approxBytes;
|
||
#ifdef BATCH_DELIVERY_ON
|
||
Boolean inThread=InAThread();
|
||
#endif
|
||
Boolean capabilities[pcapaLimit+1];
|
||
|
||
WriteZero(capabilities,sizeof(capabilities)); // we don't know if we have them yet!!!
|
||
|
||
if (Prr=StackInit(sizeof(short),&POPCmds)) return(Prr);
|
||
|
||
*gotSome = 0;
|
||
#ifdef ESSL
|
||
stream->ESSLSetting = GetPrefLong(PREF_SSL_POP_SETTING);
|
||
if(stream->ESSLSetting & esslUseAltPort)
|
||
port = GetRLong(POP_SSL_PORT);
|
||
else
|
||
#endif
|
||
port = PrefIsSet(PREF_KERBEROS)?GetRLong(KERB_POP_PORT):GetRLong(POP_PORT);
|
||
if (Prr=GetPOPInfoLo(msgname,hostName,&port)) return(Prr);
|
||
if ((err=StartPOP(stream,hostName,port))==noErr)
|
||
{
|
||
messageCount = POPIntroductions(stream, msgname, capabilities);
|
||
#ifdef DEBUG
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif
|
||
if (capabilities[0] && !capabilities[pcapaUIDL])
|
||
{
|
||
(*CurPers)->noUIDL = true;
|
||
Log(LOG_LMOS,"\pCAPA says no UIDL");
|
||
}
|
||
if (!Prr)
|
||
{
|
||
if (messageCount==0)
|
||
{
|
||
FixServers = FixServers || nil!=GetResource_(CUR_POPD_TYPE,POPD_ID);
|
||
ZapSettingsResourceMainThread_(CUR_POPD_TYPE,POPD_ID);
|
||
#ifdef TWO
|
||
ZapSettingsResourceMainThread_(CUR_POPD_TYPE,DELETE_ID);
|
||
ZapSettingsResourceMainThread_(CUR_POPD_TYPE,FETCH_ID);
|
||
#endif
|
||
}
|
||
else if (!BuildPOPD(stream,&popDH,messageCount,flags,capabilities))
|
||
{
|
||
CanPipeline = (capabilities[0] ? capabilities[pcapaPipelining] : PrefIsSet(PREF_CAN_PIPELINE))
|
||
&& !(*CurPers)->noUIDL;
|
||
|
||
ComposeLogR(LOG_RETR,nil,START_POP_LOG,hostName,port,messageCount);
|
||
if (tocH=GetInTOC())
|
||
{
|
||
/*
|
||
* run through the pure deletes
|
||
*/
|
||
for (message=0;message<messageCount;message++)
|
||
if ((*popDH)[message].delete && !(*popDH)[message].retr && !(*popDH)[message].stub)
|
||
{
|
||
Prr = DeletePOPMessage(stream,message,(*popDH)[message].uidHash);
|
||
if (!Prr) (*popDH)[message].deleted = True;
|
||
else break;
|
||
}
|
||
|
||
/*
|
||
* and now do the fetches
|
||
*/
|
||
if (!Prr)
|
||
{
|
||
#ifdef BATCH_DELIVERY_ON
|
||
short batchNum = GetRLong(DELIVERY_BATCH);
|
||
#endif
|
||
fetchCount = CountFetch(popDH);
|
||
if (CanPipeline) POPPreFetch(stream,popDH,0,capabilities);
|
||
for (fetched=message=0;message<messageCount;message++)
|
||
if ((*popDH)[message].retr || (*popDH)[message].stub)
|
||
{
|
||
ProgressR(NoChange,fetchCount-fetched,0,LEFT_TO_TRANSFER,nil);
|
||
TOCSetDirty(tocH,true);
|
||
beforeBytes = GetProgressBytes();
|
||
#ifdef DEBUG
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif
|
||
if (CommandPeriod || POPGetMessage(stream,message,gotSome,popDH,capabilities)) break;
|
||
#ifdef DEBUG
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif
|
||
fetched++;
|
||
// adjust progress bar
|
||
actualBytes = GetProgressBytes() - beforeBytes;
|
||
approxBytes = (*popDH)[message].stub ? (3 K) : (*popDH)[message].msgSize;
|
||
if (actualBytes < approxBytes)
|
||
ByteProgress(nil,actualBytes - approxBytes,0);
|
||
else
|
||
ByteProgressExcess(approxBytes - actualBytes);
|
||
|
||
// delete this message if a translator requested it.
|
||
if (ETLDeleteRequest)
|
||
{
|
||
DeleteSum(tocH, (*tocH)->count-1);
|
||
fetched--;
|
||
ETLDeleteRequest = false;
|
||
}
|
||
|
||
#ifdef BATCH_DELIVERY_ON
|
||
if (inThread && (fetched % batchNum == 0))
|
||
{
|
||
tocH = RenameInTemp(tocH);
|
||
// This is bad. We don't know why this happens,
|
||
// and if this codebase had a future, we would need
|
||
// to find out. But for now, we're just going to stop
|
||
// the crashing and feel ashamed. SD 5/2005
|
||
if (!tocH) break;
|
||
#ifdef THIS_CODE_HAD_A_FUTURE
|
||
#error FIX ME!
|
||
#endif
|
||
}
|
||
#endif
|
||
}
|
||
#ifdef BATCH_DELIVERY_ON
|
||
if (inThread)
|
||
RenameInTemp(tocH);
|
||
#endif
|
||
}
|
||
|
||
if (CommandPeriod) Prr = userCancelled;
|
||
}
|
||
else
|
||
Prr = 1;
|
||
if (!Prr)
|
||
{
|
||
ProgressMessageR(kpSubTitle,CLEANUP_CONNECTION);
|
||
}
|
||
|
||
PrunePOPD(CUR_POPD_TYPE,DELETE_ID,popDH);
|
||
PrunePOPD(CUR_POPD_TYPE,FETCH_ID,popDH);
|
||
DisposePOPD(&popDH);
|
||
}
|
||
else /* popd build failed */
|
||
ZapHandle(popDH);
|
||
}
|
||
}
|
||
if (!err) err = Prr;
|
||
if (!err && messageCount==0) ZapSettingsResourceMainThread_(CUR_POPD_TYPE,POPD_ID);
|
||
ProgressMessageR(kpSubTitle,CLEANUP_CONNECTION);
|
||
if (AttachedFiles) SetHandleBig_(AttachedFiles,0);
|
||
err = Prr;
|
||
EndPOP(stream);
|
||
|
||
ZapHandle(POPCmds);
|
||
return(err);
|
||
}
|
||
|
||
#ifdef BATCH_DELIVERY_ON
|
||
/**********************************************************************
|
||
* RenameInTemp
|
||
**********************************************************************/
|
||
TOCHandle RenameInTemp(TOCHandle tocH)
|
||
{
|
||
Str63 name;
|
||
FSSpec deliverSpec,
|
||
inSpec,
|
||
deliverFolder;
|
||
FSSpec deliverTOCSpec, tocSpec;
|
||
CInfoPBRec hfi;
|
||
long maxFileNum=0, fileNum;
|
||
OSErr err;
|
||
|
||
if (!tocH || !(*tocH)->count)
|
||
return tocH;
|
||
if (err=SubFolderSpec(DELIVERY_FOLDER,&deliverFolder))
|
||
{
|
||
Aprintf(OK_ALRT,Note,THREAD_SUBFOLDER_ERR,DELIVERY_FOLDER,err);
|
||
return tocH;
|
||
}
|
||
|
||
// make sure the toc is written
|
||
if (TOCIsDirty(tocH) || (*tocH)->reallyDirty)
|
||
if (err=WriteTOC(tocH)) return tocH;
|
||
|
||
/* find highest-numbered file in delivery folder */
|
||
Zero(hfi);
|
||
hfi.hFileInfo.ioNamePtr = name;
|
||
hfi.hFileInfo.ioFDirIndex=0;
|
||
while(!DirIterate(deliverFolder.vRefNum,deliverFolder.parID,&hfi))
|
||
{
|
||
if (hfi.hFileInfo.ioFlFndrInfo.fdType==MAILBOX_TYPE)
|
||
{
|
||
StringToNum(name, &fileNum);
|
||
if (fileNum > maxFileNum)
|
||
maxFileNum = fileNum;
|
||
}
|
||
}
|
||
|
||
// Make name for new mailbox
|
||
inSpec = GetMailboxSpec(tocH,-1);
|
||
NumToString(maxFileNum+1, name);
|
||
while (*name<6) PInsertC(name,sizeof(name),'0',name+1);
|
||
FSMakeFSSpec(deliverFolder.vRefNum,deliverFolder.parID,name,&deliverSpec);
|
||
|
||
|
||
// toc file?
|
||
tocSpec = inSpec;
|
||
PCatR(tocSpec.name,TOC_SUFFIX);
|
||
if (!FSpExists(&tocSpec))
|
||
{
|
||
FSMakeFSSpec(deliverFolder.vRefNum,deliverFolder.parID,name,&deliverTOCSpec);
|
||
PCatR(&deliverTOCSpec.name,TOC_SUFFIX);
|
||
}
|
||
else *tocSpec.name = 0;
|
||
|
||
// Move files
|
||
if ((*tocH)->win) CloseMyWindow(GetMyWindowWindowPtr((*tocH)->win));
|
||
if (err=SpecMoveAndRename(&inSpec,&deliverSpec))
|
||
{
|
||
Aprintf(OK_ALRT,Note,THREAD_DELIVER_CREATE_ERR,deliverSpec.name,err);
|
||
}
|
||
else
|
||
{
|
||
if (*tocSpec.name)
|
||
{
|
||
if (err=SpecMoveAndRename(&tocSpec,&deliverTOCSpec))
|
||
{
|
||
Aprintf(OK_ALRT,Note,THREAD_DELIVER_CREATE_ERR,deliverTOCSpec.name,err);
|
||
FSpDelete(&tocSpec); // hell with it. We can rebuild it
|
||
}
|
||
}
|
||
|
||
// Ok, we have moved the temp.in. Make a new one
|
||
if (err=MakeResFile(inSpec.name,inSpec.vRefNum,inSpec.parID,CREATOR,MAILBOX_TYPE))
|
||
{
|
||
Aprintf(OK_ALRT,Note,THREAD_DELIVER_CREATE_ERR,inSpec.name,err);
|
||
// this is bad
|
||
}
|
||
|
||
NeedToFilterIn++; // some filtering to be done
|
||
}
|
||
|
||
tocH = GetTempInTOC();
|
||
|
||
return tocH;
|
||
}
|
||
#endif
|
||
|
||
/**********************************************************************
|
||
* POPPreFetch
|
||
**********************************************************************/
|
||
OSErr POPPreFetch(TransStream stream, POPDHandle popDH,short message,Boolean *capabilities)
|
||
{
|
||
short messageCount = HandleCount(popDH);
|
||
Str63 args;
|
||
Str15 top;
|
||
OSErr err = noErr;
|
||
short cmd;
|
||
|
||
for (;message<messageCount;message++)
|
||
if ((*popDH)[message].retr || (*popDH)[message].stub)
|
||
{
|
||
NumToString(message+1,args);
|
||
if ((*popDH)[message].stub)
|
||
{
|
||
NumToString(GetRLong(BIG_MESSAGE_FRAGMENT),top);
|
||
PCatC(args,' ');
|
||
PCat(args,top);
|
||
if (capabilities[pcapaMangle] || capabilities[pcapaXMangle])
|
||
{
|
||
PCatC(args,' ');
|
||
PCatR(args,POPCapaStrn+(capabilities[pcapaMangle]?pcapaMangle:pcapaXMangle));
|
||
PCatR(args,MANGLE_ARGS);
|
||
}
|
||
cmd = kpcTop;
|
||
}
|
||
else cmd = kpcRetr;
|
||
|
||
return(POPCmd(stream, cmd,args));
|
||
}
|
||
return(noErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPrror - see if there was a POP error
|
||
************************************************************************/
|
||
int POPrror(void)
|
||
{
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* private routines
|
||
************************************************************************/
|
||
/************************************************************************
|
||
* StartPOP - get connected to the POP server
|
||
************************************************************************/
|
||
int StartPOP(TransStream stream, UPtr serverName, long port)
|
||
{
|
||
PopConnected=False;
|
||
Prr=ConnectTrans(stream,serverName,port,False,GetRLong(OPEN_TIMEOUT));
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* EndPOP - get rid of the POP server
|
||
************************************************************************/
|
||
int EndPOP(TransStream stream)
|
||
{
|
||
SilenceTrans(stream,True);
|
||
if (CommandPeriod && TransError(stream)==userCancelled) POPCmd(stream,kpcQuit,nil);
|
||
if (!Prr)
|
||
{
|
||
if (!CommandPeriod) (void) POPByeBye(stream);
|
||
Prr=DisTrans(stream);
|
||
}
|
||
Prr = DestroyTrans(stream) || Prr;
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPIntroductions - sniff the POP server's bottom, and vice-versa
|
||
************************************************************************/
|
||
int POPIntroductions(TransStream stream, PStr user,Boolean *capabilities)
|
||
{
|
||
Str255 buffer;
|
||
Str255 args;
|
||
long size;
|
||
int result = -1;
|
||
Boolean useAPOP = PrefIsSet(PREF_APOP);
|
||
Boolean kerb4 = PrefIsSet(PREF_KERBEROS) && !PrefIsSet(PREF_K5_POP);
|
||
Str255 digest;
|
||
SASLEnum mech=0;
|
||
|
||
#ifdef TWO
|
||
if (kerb4)
|
||
if (Prr=SendPOPTicket(stream))
|
||
{
|
||
(*CurPers)->popSecure = False;
|
||
goto done;
|
||
}
|
||
#endif
|
||
|
||
do
|
||
{
|
||
size = sizeof(buffer)-1;
|
||
Prr = RecvLine(stream,buffer+1,&size);
|
||
if (Prr) goto done;
|
||
buffer[0] = MIN(size,127);
|
||
ProgressMessage(kpMessage,buffer);
|
||
buffer[0] = MIN(size,255);
|
||
}
|
||
while (buffer[1]!='+' && buffer[1]!='-');
|
||
|
||
PopConnected = size && (buffer[1]=='+' || buffer[1]=='-');
|
||
if (buffer[1] != '+')
|
||
{
|
||
Prr = buffer[1];
|
||
POPCmdError(-1,nil,buffer);
|
||
if (kerb4 && !NoClearPass(capabilities,buffer,size)) KerbDestroy();
|
||
goto done;
|
||
}
|
||
|
||
if (capabilities) PopCapabilities(stream,capabilities,&mech);
|
||
|
||
#ifdef ESSL
|
||
if( ShouldUseSSL(stream) && !(stream->ESSLSetting & esslSSLInUse))
|
||
{
|
||
if (!capabilities || !capabilities[pcapaSTLS])
|
||
{
|
||
if(!(stream->ESSLSetting & esslOptional))
|
||
{
|
||
Prr = unimpErr;
|
||
ComposeStdAlert ( Note, ALRTStringsStrn+NO_SERVER_SSL );
|
||
goto done;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
StringPtr errStr;
|
||
|
||
errStr = buffer;
|
||
size = sizeof(buffer)-1;
|
||
Prr = POPCmdGetReply(stream,kpcStls,nil,buffer,&size);
|
||
if(!Prr)
|
||
{
|
||
Prr = ESSLStartSSL(stream);
|
||
if(Prr)
|
||
{
|
||
DoSSLErrString :
|
||
GetRString(buffer,SSL_ERR_STRING);
|
||
errStr = buffer + 1;
|
||
goto DoSSLErr;
|
||
}
|
||
else if(stream->ESSLSetting & esslSSLInUse)
|
||
PopCapabilities(stream,capabilities,&mech);
|
||
else
|
||
{
|
||
// Cyrus sucks.
|
||
// After a failed TLS negotiation, Cyrus will issue a bogus
|
||
// -ERR response to the NEXT command. They shouldn't be issuing
|
||
// any protocol-level response at all. bxxxxxxs
|
||
OTFlushInput(stream,GetRLong(FLUSH_TIMEOUT));
|
||
}
|
||
}
|
||
else
|
||
DoSSLErr :
|
||
{
|
||
if(stream->ESSLSetting & esslOptional)
|
||
Prr = noErr;
|
||
else
|
||
{
|
||
POPCmdError(kpcStls,nil,errStr);
|
||
goto done;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
ProgressMessageR(kpSubTitle,LOGGING_IN);
|
||
|
||
if (mech && !kerb4)
|
||
{
|
||
// SASL ahoy!
|
||
size = sizeof(buffer)-1;
|
||
Prr = POPSasl(stream,capabilities,mech,buffer,&size);
|
||
}
|
||
else
|
||
{
|
||
if (useAPOP) {PCopy(args,(*CurPers)->password);useAPOP = GenDigest(buffer,args,digest);}
|
||
|
||
if (useAPOP)
|
||
{
|
||
size = sizeof(buffer)-1;
|
||
if (PrefIsSet(PREF_POP_SENDHOST))
|
||
GetPOPPref(args);
|
||
else
|
||
PCopy(args,user);
|
||
PCatC(args,' ');
|
||
PCat(args,digest);
|
||
Prr = POPCmdGetReply(stream,kpcApop,args,buffer,&size);
|
||
}
|
||
else
|
||
{
|
||
if (PrefIsSet(PREF_POP_SENDHOST))
|
||
GetPOPPref(args);
|
||
else
|
||
PCopy(args,user);
|
||
size = sizeof(buffer)-1;
|
||
Prr = POPCmdGetReply(stream,kpcUser,args,buffer,&size);
|
||
if (Prr || *buffer != '+')
|
||
{
|
||
if (!Prr) POPCmdError(kpcUser,args,buffer);
|
||
Prr = '-';
|
||
goto done;
|
||
}
|
||
|
||
#ifdef TWO
|
||
if (kerb4)
|
||
GetRString(args,KERBEROS_FAKE_PASS);
|
||
else
|
||
#endif
|
||
PCopy(args,(*CurPers)->password);
|
||
|
||
size = sizeof(buffer)-1;
|
||
Prr = POPCmdGetReply(stream,kpcPass,args,buffer,&size);
|
||
}
|
||
}
|
||
if (Prr || *buffer != '+')
|
||
{
|
||
if (!Prr)
|
||
{
|
||
(*CurPers)->popSecure = False;
|
||
POPCmdError(kpcPass,nil,buffer);
|
||
if (!NoClearPass(capabilities,buffer,size))
|
||
InvalidatePasswords(False,True,False);
|
||
}
|
||
Prr = '-';
|
||
goto done;
|
||
}
|
||
(*CurPers)->popSecure = True;
|
||
SetPrefLong(PREF_POP_LAST_AUTH,GMTDateTime());
|
||
|
||
ProgressMessageR(kpSubTitle,LOOK_MAIL);
|
||
|
||
size = sizeof(buffer)-1;
|
||
Prr = POPCmdGetReply(stream,kpcStat,nil,buffer,&size);
|
||
if (Prr || *buffer != '+')
|
||
{
|
||
if (!Prr) POPCmdError(kpcStat,nil,buffer);
|
||
Prr = '-';
|
||
goto done;
|
||
}
|
||
|
||
result = Atoi(buffer+3);
|
||
done:
|
||
return(result);
|
||
}
|
||
|
||
/************************************************************************
|
||
* PopCapabilities - what can our pop server do?
|
||
************************************************************************/
|
||
void PopCapabilities(TransStream stream, Boolean *capabilities, SASLEnum *mechPtr)
|
||
{
|
||
short i;
|
||
Str255 buffer;
|
||
long size;
|
||
UPtr spot;
|
||
Str31 token;
|
||
Str31 service;
|
||
|
||
for (i=0;i<=pcapaLimit;i++) capabilities[i]=0;
|
||
*mechPtr = 0;
|
||
|
||
if (*GetRString(buffer,POPCmdsStrn+kpcCapa)<=1) return; // hack, but hey
|
||
GetRString(service,K5_POP_SERVICE);
|
||
|
||
size = sizeof(buffer);
|
||
if (!POPCmdGetReply(stream,kpcCapa,nil,buffer,&size) && *buffer=='+')
|
||
{
|
||
capabilities[0] = 1; // we have them!
|
||
for(;;)
|
||
{
|
||
size = sizeof(buffer)-1;
|
||
if (RecvLine(stream,buffer+1,&size)) break;
|
||
buffer[0] = MIN(size,255);
|
||
if (buffer[*buffer]=='\015') --*buffer;
|
||
if (buffer[0]==1 && buffer[1]=='.') break;
|
||
spot = buffer+1;
|
||
if (PToken(buffer,token,&spot," "))
|
||
{
|
||
i = FindSTRNIndex(POPCapaStrn,token);
|
||
if (i && i<pcapaLimit) capabilities[i] = 1;
|
||
|
||
if (i==pcapaSASL)
|
||
{
|
||
while (PToken(buffer,token,&spot," "))
|
||
*mechPtr = SASLFind(service,token,*mechPtr);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* POPSasl - do SASL for POP
|
||
**********************************************************************/
|
||
int POPSasl(TransStream stream, Boolean *capabilities, SASLEnum mech, UPtr buffer, long *size)
|
||
{
|
||
Accumulator chalAcc, respAcc;
|
||
short rounds = 0;
|
||
long bSize = *size;
|
||
short smtpEquivCode = 501;
|
||
Str63 service;
|
||
long state = 0;
|
||
Str255 scratch;
|
||
|
||
Zero(chalAcc);
|
||
Zero(respAcc);
|
||
|
||
// put auth command in inital response
|
||
AccuAddRes(&respAcc,EsmtpStrn+esmtpAuth);
|
||
AccuAddChar(&respAcc,' ');
|
||
|
||
// Grab stuff only Kerberos wants
|
||
GetRString(service,K5_POP_SERVICE);
|
||
|
||
// run the mechanism
|
||
do
|
||
{
|
||
// Build the response
|
||
if (SASLDo(service,mech,rounds++,&state,&chalAcc,&respAcc)) Prr = '-';
|
||
else
|
||
{
|
||
// Send the response
|
||
if (POPCmdLo(stream,kpcAuth,nil,&respAcc)) Prr = '-';
|
||
else
|
||
{
|
||
// get the reply
|
||
bSize = *size;
|
||
Prr = POPGetReplyLo(stream,kpcAuth,buffer,&bSize,&respAcc);
|
||
chalAcc.offset = 0;
|
||
if (!Prr && respAcc.offset && **respAcc.data=='+')
|
||
{
|
||
if (respAcc.offset>1 && (*respAcc.data)[1]!=' ')
|
||
{
|
||
// We win! We win!
|
||
Prr = 0;
|
||
}
|
||
else
|
||
{
|
||
Prr = ' ';
|
||
AccuAddFromHandle(&chalAcc,respAcc.data,1,respAcc.offset-1);
|
||
// clean it out
|
||
respAcc.offset = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
while (Prr==' ');
|
||
|
||
if (Prr || **respAcc.data!='+')
|
||
{
|
||
(*CurPers)->popSecure = False;
|
||
AccuToStr(&respAcc,scratch);
|
||
POPCmdError(kpcAuth,nil,scratch);
|
||
if (!NoClearPass(capabilities,scratch,*size))
|
||
smtpEquivCode = 535;
|
||
}
|
||
else
|
||
smtpEquivCode = 237; // auth succeeded!
|
||
|
||
// Let the sasl mechanism know how it all came out
|
||
SASLDone(service,mech,rounds,&state,smtpEquivCode);
|
||
|
||
AccuZap(chalAcc);
|
||
AccuZap(respAcc);
|
||
|
||
return Prr;
|
||
}
|
||
|
||
/**********************************************************************
|
||
* NoClearPass - is a pass error one that should not reset the password?
|
||
**********************************************************************/
|
||
Boolean NoClearPass(Boolean *capabilities,UPtr response, short len)
|
||
{
|
||
Str255 string;
|
||
short i;
|
||
|
||
// If the pop server has the AUTH_RESP_CODE caapability,
|
||
// then errors do NOT clear the password unless
|
||
// [auth] appears in them
|
||
if (capabilities && capabilities[pcapaAuthRespCode])
|
||
{
|
||
GetRString(string,POP3_AUTH_RESP_CODE);
|
||
return !PFindSub(string,response);
|
||
}
|
||
|
||
// without the capability, we have to refer to
|
||
// our list of non-clearing errors
|
||
for (i=1;*GetRString(string,NoClearPassStrn+i);i++)
|
||
if (PPtrFindSub(string,response,len)) return(True);
|
||
|
||
// Have we ever auth'ed using this password? If so,
|
||
// let's assume this is a server problem and not an authentication
|
||
// problem.
|
||
if (GetPrefLong(PREF_POP_LAST_AUTH)) return true;
|
||
|
||
// all else as failed. Sigh.
|
||
return(False);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPByeBye - tell the POP server we're leaving
|
||
************************************************************************/
|
||
int POPByeBye(TransStream stream)
|
||
{
|
||
char buffer[CMD_BUFFER];
|
||
long size=sizeof(buffer);
|
||
if (!PopConnected) return(noErr);
|
||
if (PrefIsSet(PREF_SLOW_QUIT)) Prr = POPCmdGetReply(stream,kpcQuit,nil,buffer,&size);
|
||
else {Prr = POPCmd(stream,kpcQuit,nil); *buffer = '+'; /* fast TCP disconnect */}
|
||
return (Prr || *buffer != '+');
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPCmd - Send a command to the POP server
|
||
************************************************************************/
|
||
int POPCmdLo(TransStream stream, short cmd, UPtr args, AccuPtr argsAcc)
|
||
{
|
||
char buffer[CMD_BUFFER];
|
||
short err;
|
||
|
||
/*
|
||
* reap outstanding commands
|
||
*/
|
||
if (!CanPipeline || POPCmds && (*POPCmds)->elCount>=15)
|
||
{
|
||
err=ReapCmds(stream, -1);
|
||
if (err==fnfErr) err = noErr;
|
||
if (err) return(Prr=err);
|
||
}
|
||
|
||
if (CanPipeline && cmd==kpcQuit) ReapCmds(stream, 0);
|
||
|
||
if (cmd) GetRString(buffer,POP_STRN+cmd);
|
||
if (cmd==kpcPass || cmd==kpcAuth) ProgressMessage(kpMessage,buffer);
|
||
if (cmd==kpcAuth) *buffer = 0;
|
||
if (args && *args)
|
||
PCat(buffer,args);
|
||
if (cmd!=kpcAuth && cmd!=kpcPass && cmd!=kpcRetr && cmd!=kpcTop) ProgressMessage(kpMessage,buffer);
|
||
if (cmd==kpcRetr || cmd==kpcDele) Log(LOG_LMOS,buffer);
|
||
|
||
if (!argsAcc) PCat(buffer,NewLine);
|
||
else if (cmd && cmd!=kpcAuth) PCatC(buffer,' ');
|
||
|
||
err=SendTrans(stream,buffer+1,*buffer,nil);
|
||
if (!err && argsAcc)
|
||
{
|
||
// add a newline to the accumulator
|
||
AccuAddStr(argsAcc,NewLine);
|
||
|
||
// send the data
|
||
err = SendTrans(stream,LDRef(argsAcc->data),argsAcc->offset,nil);
|
||
|
||
// erase what we did to the accumulator
|
||
UL(argsAcc->data);
|
||
argsAcc->offset -= *NewLine;
|
||
}
|
||
|
||
if (!err && POPCmds) err = StackQueue(&cmd,POPCmds);
|
||
|
||
return(err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* ReapCmds - reap commands until the named command is at the top
|
||
* of the stack, ready to be handled
|
||
**********************************************************************/
|
||
OSErr ReapCmds(TransStream stream, short cmd)
|
||
{
|
||
Str255 buffer;
|
||
long size;
|
||
OSErr err=noErr;
|
||
short thisCmd=0;
|
||
|
||
if (!POPCmds) return(noErr);
|
||
|
||
while ((*POPCmds)->elCount)
|
||
{
|
||
if (cmd!=-1)
|
||
{
|
||
StackTop(&thisCmd,POPCmds);
|
||
if (cmd==thisCmd) return(noErr);
|
||
}
|
||
StackPop(&thisCmd,POPCmds);
|
||
do
|
||
{
|
||
size = sizeof(buffer);
|
||
err = RecvLine(stream,buffer,&size);
|
||
}
|
||
while (!err && *buffer!='+' && *buffer!='-');
|
||
if (thisCmd==cmd) return(noErr);
|
||
if (thisCmd==kpcTop || thisCmd==kpcRetr)
|
||
{
|
||
do
|
||
{
|
||
size = sizeof(buffer);
|
||
err = RecvLine(stream,buffer,&size);
|
||
}
|
||
while (!err && !POP_TERM(buffer,size));
|
||
}
|
||
if (cmd==-1) return(noErr);
|
||
}
|
||
|
||
if (err) Prr=err;
|
||
|
||
return(err ? err : fnfErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPCmdGetReply - send a POP command and get a reply
|
||
************************************************************************/
|
||
int POPCmdGetReply(TransStream stream, short cmd, UPtr args, UPtr buffer, long *size)
|
||
{
|
||
if (cmd>=0 && (Prr=POPCmd(stream,cmd,args))) return(Prr); /* error in transmission */
|
||
|
||
return(POPGetReply(stream,cmd,buffer,size));
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPGetReply - get a reply to a POP command
|
||
************************************************************************/
|
||
int POPGetReplyLo(TransStream stream, short cmd, UPtr buffer, long *size, AccuPtr resAcc)
|
||
{
|
||
long rSize;
|
||
|
||
// So what's errChar? Well, two things:
|
||
// 1. Way Back When, when we did serial lines, some POP servers echoed;
|
||
// errChar is used to detect and ignore echoes.
|
||
// 2. Some POP servers add extraneous blank lines (not that I'm naming
|
||
// names, but if someone were to nuke a company named "ipswitch", we
|
||
// might not have this problem anymore), and we can skip those, too
|
||
// So when we see an actual valid POP error indicator (either + or -), we
|
||
// know the true response has begun. Hence, errChar.
|
||
Byte errChar = 0;
|
||
|
||
if (Prr=ReapCmds(stream, cmd)) return(Prr);
|
||
if (resAcc) resAcc->offset = 0;
|
||
do
|
||
{
|
||
rSize = *size;
|
||
Prr = RecvLine(stream,buffer,&rSize);
|
||
if (!rSize)
|
||
{
|
||
errChar = '-';
|
||
break;
|
||
}
|
||
if (!errChar && (*buffer=='+' || *buffer=='-')) errChar = *buffer;
|
||
if (!Prr && errChar && POPCmds && buffer[rSize-1]=='\r') StackPop(nil,POPCmds);
|
||
if (errChar && resAcc) AccuAddPtr(resAcc,buffer,rSize);
|
||
}
|
||
while (!Prr && !CommandPeriod && !(errChar && buffer[rSize-1]=='\r'));
|
||
*size = rSize;
|
||
buffer[0] = errChar;
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPGetMessage - get a message from the POP server
|
||
************************************************************************/
|
||
int POPGetMessage(TransStream stream, long messageNumber,short *gotSome,POPDHandle popDH,Boolean *capabilities)
|
||
{
|
||
char buffer[CMD_BUFFER];
|
||
long size = sizeof(buffer);
|
||
short count;
|
||
TOCHandle tocH = GetInTOC(); /* shd already be in memory, so NBD to grab it here */
|
||
POPDesc pd;
|
||
long msgsize;
|
||
Boolean notFetched = False;
|
||
|
||
/*
|
||
* if there's no room at all, we won't even try
|
||
*/
|
||
if (RoomForMessage(0)) return(WarnUser(NOT_ENOUGH_ROOM,Prr=dskFulErr));
|
||
|
||
pd = (*popDH)[messageNumber];
|
||
msgsize = pd.msgSize;
|
||
|
||
POPPreFetch(stream, popDH,CanPipeline?messageNumber+1:messageNumber,capabilities);
|
||
|
||
/*
|
||
* stub or retr
|
||
*/
|
||
if (pd.stub)
|
||
{
|
||
msgsize *= -1; /* let everyone down the line know what's going down */
|
||
size = sizeof(buffer);
|
||
Prr = POPGetReply(stream,kpcTop,buffer,&size);
|
||
NoAttachments = True; /* don't do BinHex */
|
||
RemIdFromPOPD(CUR_POPD_TYPE,DELETE_ID,(*popDH)[messageNumber].uidHash); /* and clear the force del flag if set */
|
||
}
|
||
else
|
||
{
|
||
NoAttachments = pd.error ? True : False;
|
||
refetch:
|
||
size = sizeof(buffer);
|
||
Prr=POPGetReply(stream,kpcRetr,buffer,&size);
|
||
}
|
||
|
||
if (Prr) return(Prr);
|
||
if (*buffer!='+')
|
||
{
|
||
POPCmdError(kpcRetr,nil,buffer);
|
||
return(Prr=1);
|
||
}
|
||
|
||
/*
|
||
* command issued and accepted - now read the message
|
||
*/
|
||
BadBinHex = False;
|
||
BadEncoding = 0;
|
||
#ifdef DEBUG
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif
|
||
count=FetchMessageText(stream,msgsize,&pd,messageNumber,nil);
|
||
#ifdef DEBUG
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif
|
||
|
||
|
||
if (CommandPeriod && StackLowErr)
|
||
{
|
||
StackLowErr = false;
|
||
#ifdef THREADING_ON
|
||
// increase thread stack size for next try
|
||
if (InAThread())
|
||
{
|
||
WarnUser(THREAD_LOW_STACK,0);
|
||
}
|
||
else
|
||
#endif
|
||
// try lowering appllimit for non-threaded operation????
|
||
// ...Will look into if users frequently experience
|
||
WarnUser(LOW_STACK,0);
|
||
}
|
||
|
||
/*
|
||
* did it work?
|
||
*/
|
||
if (!Prr && !CommandPeriod)
|
||
{
|
||
/*
|
||
* ask user what to do about encoding errors
|
||
*/
|
||
#ifdef BAD_ENCODING_HANDLING
|
||
if (BadBinHex || BadEncoding)
|
||
{
|
||
pd.delete = False;
|
||
pd.stubbed = True;
|
||
pd.error = True;
|
||
notFetched = True;
|
||
}
|
||
#endif
|
||
if (pd.delete)
|
||
{
|
||
Prr = DeletePOPMessage(stream,messageNumber,pd.uidHash);
|
||
if (!Prr) pd.deleted = True;
|
||
}
|
||
if (pd.stub) pd.stubbed = True;
|
||
else if (!notFetched && pd.retr)
|
||
{
|
||
pd.retred = True;
|
||
RemIdFromPOPD(CUR_POPD_TYPE,FETCH_ID,pd.uidHash);
|
||
}
|
||
}
|
||
|
||
(*popDH)[messageNumber] = pd;
|
||
|
||
if (!Prr) (*gotSome)+=count;
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* DeletePOPMessage - delete a message from the POP server
|
||
************************************************************************/
|
||
OSErr DeletePOPMessage(TransStream stream, short number,long uidHash)
|
||
{
|
||
Str255 buffer;
|
||
Str63 args;
|
||
long size;
|
||
|
||
NumToString(number+1,args);
|
||
size = sizeof(buffer);
|
||
Prr=POPCmd(stream,kpcDele,args);
|
||
if (!Prr) RemIdFromPOPD(CUR_POPD_TYPE,DELETE_ID,uidHash);
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FillSizesWithList - fill message sizes with the LIST command
|
||
************************************************************************/
|
||
OSErr FillSizesWithList(TransStream stream,POPDHandle popDH)
|
||
{
|
||
Str127 buffer;
|
||
long size = sizeof(buffer);
|
||
short msgNum;
|
||
UPtr spot;
|
||
short n = HandleCount(popDH);
|
||
|
||
if (Prr=POPCmdGetReply(stream,kpcList,nil,buffer,&size)) return(Prr);
|
||
|
||
if (*buffer != '+')
|
||
{
|
||
Prr = *buffer;
|
||
POPCmdError(kpcList,nil,buffer);
|
||
return(Prr);
|
||
}
|
||
|
||
// if (n>100) ByteProgress(nil,0,n);
|
||
|
||
for (size=sizeof(buffer);
|
||
!(Prr=RecvLine(stream,buffer,&size)) && !POP_TERM(buffer,size);
|
||
size=sizeof(buffer))
|
||
{
|
||
CycleBalls(); if (CommandPeriod) break;
|
||
|
||
spot = strtok(buffer," \t");
|
||
if (!spot) continue;
|
||
msgNum = Atoi(spot);
|
||
|
||
spot = strtok(nil," \t");
|
||
if (!spot) continue;
|
||
size = Atoi(spot);
|
||
|
||
if (msgNum<1 || msgNum>n) continue;
|
||
|
||
// if (n>100) ByteProgress(nil,-1,0);
|
||
|
||
(*popDH)[msgNum-1].msgSize = size;
|
||
}
|
||
if (CommandPeriod && !Prr) Prr = userCancelled;
|
||
|
||
// if (n > 100 && !Prr) ByteProgress(nil,1,1);
|
||
Progress(NoBar,0,nil,nil,nil);
|
||
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* POPCmdError - report an error for an POP command
|
||
************************************************************************/
|
||
int POPCmdError(short cmd, UPtr args, UPtr message)
|
||
{
|
||
Str255 theCmd;
|
||
Str255 theError;
|
||
int err;
|
||
|
||
*theCmd = 0;
|
||
GetRString(theCmd,POP_STRN+cmd);
|
||
if (args && *args)
|
||
PCat(theCmd,args);
|
||
strcpy(theError+1,message);
|
||
*theError = strlen(theError+1);
|
||
if (theError[*theError]=='\012') (*theError)--;
|
||
if (theError[*theError]=='\015') (*theError)--;
|
||
MyParamText(theCmd,theError,"\pPOP","");
|
||
err = ReallyDoAnAlert(PROTO_ERR_ALRT,Note);
|
||
return(err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FetchMessageText - read in the body of a message
|
||
************************************************************************/
|
||
int FetchMessageText(TransStream stream,long estSize,POPDPtr pdp,short messageNumber,TOCHandle useTocH)
|
||
{
|
||
return (FetchMessageTextLo(stream, estSize, pdp, messageNumber, useTocH, false, false));
|
||
}
|
||
|
||
/************************************************************************
|
||
* FetchMessageText - read in the body of a message
|
||
************************************************************************/
|
||
int FetchMessageTextLo(TransStream stream,long estSize,POPDPtr pdp,short messageNumber,TOCHandle useTocH,Boolean imap,Boolean import)
|
||
{
|
||
UPtr text=nil;
|
||
TOCHandle tocH;
|
||
MSumType sum;
|
||
long eof,chopHere;
|
||
Str255 name;
|
||
short count=0,part;
|
||
HeaderDHandle hdh = nil;
|
||
LineIOD lid;
|
||
OSErr err;
|
||
FSSpec spec;
|
||
extern OSErr ImportErr;
|
||
Str63 savedSub;
|
||
|
||
/*
|
||
* make the message summary
|
||
*/
|
||
WriteZero(&sum,sizeof(MSumType));
|
||
*savedSub = 0;
|
||
|
||
/*
|
||
* haven't seen any rich text yet
|
||
*/
|
||
AnyRich = AnyHTML = AnyFlow = AnyCharset = AnyDelSP = False;
|
||
ZapHandle(LastAttSpec); /* or attachments */
|
||
ETLDeleteRequest = False; /* and no translators have been run on this message yet */
|
||
|
||
/*
|
||
* grab the destination mailbox (usually "In")
|
||
*/
|
||
tocH = useTocH ? useTocH : GetInTOC();
|
||
if (!tocH) {Prr=-108;return(0);}
|
||
spec = GetMailboxSpec(tocH,-1);
|
||
PCopy(name,spec.name);
|
||
|
||
// if we're adding IMAP messages or importing mail, we've taken care of opening the mailbox already
|
||
if (!imap && !import)
|
||
{
|
||
Prr = BoxFOpen(tocH);
|
||
if (Prr) {FileSystemError(OPEN_MBOX,name,Prr); goto done;}
|
||
}
|
||
|
||
eof = FindTOCSpot(tocH,estSize);
|
||
|
||
Prr = SetFPos((*tocH)->refN, fsFromStart, eof);
|
||
if (Prr) {FileSystemError(WRITE_MBOX,name,Prr); goto done;}
|
||
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
count = SaveAndSplit(stream,(*tocH)->refN,estSize,&hdh,(*tocH)->imapTOC);
|
||
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
done:
|
||
if (!Prr && !GetFPos((*tocH)->refN,&chopHere))
|
||
SetEOF((*tocH)->refN,chopHere);
|
||
|
||
// if we're adding IMAP messages, or importing mail, we'll close and flush later.
|
||
if (Prr || (!imap && !import))
|
||
{
|
||
Prr = BoxFClose(tocH,true) || Prr;
|
||
if (Prr || !count) return(0);
|
||
}
|
||
|
||
/*
|
||
* now, read it back from the file
|
||
*/
|
||
if (Prr=OpenLine(spec.vRefNum,spec.parID,name,(imap||import)?fsRdPerm:fsRdWrPerm,&lid))
|
||
{FileSystemError(READ_MBOX,name,Prr);return(0);}
|
||
if (Prr=SeekLine(eof,&lid)) {FileSystemError(READ_MBOX,name,Prr);return(0);}
|
||
|
||
ReadSum(nil,False,&lid,True);
|
||
for (part=1;!(err=ReadSum(&sum,False,&lid,True));part++)
|
||
{
|
||
if (!*savedSub) PSCopy(savedSub,sum.subj);
|
||
if (part==1 && pdp)
|
||
{
|
||
FillPOPD(pdp,hdh);
|
||
DBNoteUIDHash(sum.uidHash,pdp->uidHash);
|
||
sum.uidHash = pdp->uidHash;
|
||
}
|
||
else
|
||
{
|
||
DBNoteUIDHash(sum.uidHash,kNoMessageId);
|
||
sum.uidHash = kNoMessageId;
|
||
}
|
||
if (!(*hdh)->isMIME)
|
||
{
|
||
TransLitString(sum.from);
|
||
TransLitString(sum.subj);
|
||
}
|
||
else sum.tableId = ViewTable(hdh);
|
||
if (part==1) sum.msgIdHash = (*hdh)->msgIdHash;
|
||
if (!ValidHash(sum.uidHash)) sum.uidHash = sum.msgIdHash;
|
||
#ifdef BAD_ENCODING_HANDLING
|
||
if ((estSize<0) || BadBinHex || BadEncoding) sum.flags |= FLAG_SKIPPED;
|
||
#else
|
||
if ((estSize<0)) sum.flags |= FLAG_SKIPPED;
|
||
#endif
|
||
|
||
// set or clear html/enriched flags. Clear is necessary because toc build might give
|
||
// false positive
|
||
if (count==1 && AnyRich) sum.flags |= FLAG_RICH; else sum.flags &= ~FLAG_RICH;
|
||
if (count==1 && AnyHTML) sum.opts |= OPT_HTML; else sum.opts &= ~OPT_HTML;
|
||
if (count==1 && AnyFlow) sum.opts |= OPT_FLOW; else sum.opts &= ~OPT_FLOW;
|
||
if (count==1 && AnyDelSP) sum.opts |= OPT_DELSP; else sum.opts &= ~OPT_DELSP;
|
||
if (count==1 && AnyCharset) sum.opts |= OPT_CHARSET; else sum.opts &= ~OPT_CHARSET;
|
||
if ((*hdh)->hasMDN) sum.opts |= OPT_RECEIPT;
|
||
if (LastAttSpec) sum.flags |= FLAG_HAS_ATT;
|
||
if (!sum.seconds) sum.seconds = GMTDateTime();
|
||
if (count>1) StampPartNumber(&sum,part,count);
|
||
sum.spamScore = 0;
|
||
sum.arrivalSeconds = GMTDateTime();
|
||
if (Prr) break;
|
||
if (useTocH && !import) // create a new summary if we're importing
|
||
{
|
||
MSumType newSum;
|
||
|
||
newSum = (*tocH)->sums[messageNumber];
|
||
newSum.offset = sum.offset;
|
||
newSum.length = sum.length;
|
||
newSum.bodyOffset = sum.bodyOffset;
|
||
newSum.flags |= sum.flags;
|
||
newSum.opts |= sum.opts;
|
||
newSum.msgIdHash = sum.msgIdHash;
|
||
if (!newSum.priority) newSum.priority = sum.priority;
|
||
PSCopy(newSum.subj,sum.subj);
|
||
if (!(newSum.opts & OPT_IMAP_SENT))
|
||
PSCopy(newSum.from,sum.from);
|
||
RemoveUTF8FromSum(&newSum);
|
||
(*tocH)->sums[messageNumber] = newSum;
|
||
}
|
||
else
|
||
if (Prr=!SaveMessageSum(&sum,&tocH))
|
||
{
|
||
if (import) ImportErr = memFullErr; // stop if we're importing.
|
||
break;
|
||
}
|
||
}
|
||
ReadSum(nil,False,&lid,True);
|
||
if (err!=fnfErr) Prr = err;
|
||
Prr = Prr || part<=count;
|
||
ZapHeaderDesc(hdh);
|
||
|
||
CloseLine(&lid);
|
||
|
||
#ifdef DEBUG
|
||
if (ETLDeleteRequest) ComposeLogS(LOG_PLUG,nil,"\pA plugin has requested the deletion of '%p' in '%p'",savedSub,spec.name);
|
||
#endif
|
||
|
||
if (BadBinHex || BadEncoding)
|
||
{
|
||
Str255 hex,
|
||
enc,
|
||
errorStr;
|
||
|
||
if (BadBinHex) GetRString(hex,BAD_HEX_MSG);
|
||
else *hex = 0;
|
||
if (BadEncoding) ComposeRString(enc,BAD_ENC_MSG,BadEncoding,BadEncoding);
|
||
else *enc = 0;
|
||
|
||
ComposeRString(errorStr,(imap?IMAP_BAD_HEXBIN_ERR_TEXT:BAD_HEXBIN_ERR_TEXT),hex,enc);
|
||
|
||
if (imap) // add the message error to the IMAP message we just downloaded
|
||
AddMesgError (tocH,messageNumber,errorStr,-1);
|
||
else // add it to the last message added to the mailbox. OK for POP.
|
||
AddMesgError (tocH,(*tocH)->count-1,errorStr,-1);
|
||
}
|
||
|
||
if (Prr)
|
||
{
|
||
WarnUser(READ_MBOX,Prr);
|
||
return(0);
|
||
}
|
||
|
||
count = part-1;
|
||
|
||
InvalSum(tocH,useTocH?messageNumber:(*tocH)->count-1);
|
||
if (!PrefIsSet(PREF_CORVAIR) && !((*tocH)->count%5))
|
||
{
|
||
Prr = WriteTOC(tocH);
|
||
FlushVol(nil,spec.vRefNum);
|
||
}
|
||
MakeMessTitle(name,tocH,useTocH?messageNumber:(*tocH)->count-count,False);
|
||
ComposeLogR(LOG_RETR,nil,MSG_GOT,name,count);
|
||
if (!imap) UpdateNumStatWithTime(kStatReceivedMail,1,(*tocH)->sums[useTocH?messageNumber:(*tocH)->count-1].seconds+ZoneSecs());
|
||
return(Prr ? 0:count);
|
||
}
|
||
|
||
/************************************************************************
|
||
* SaveAndSplit - read a message, (possibly) splitting it into parts and
|
||
* saving it.
|
||
************************************************************************/
|
||
int SaveAndSplit(TransStream stream,short refN,long estSize,HeaderDHandle *hdhp,Boolean isIMAP)
|
||
{
|
||
Str255 buf;
|
||
short count=0;
|
||
HeaderDHandle hdh=NewHeaderDesc(nil);
|
||
long fromSize;
|
||
long end;
|
||
long oldStart;
|
||
short lastHeaderTokenType;
|
||
long ticks = TickCount();
|
||
|
||
if (!hdh) {Prr=MemError(); return(0);}
|
||
|
||
// if (estSize>0) ByteProgress(nil,0,estSize);
|
||
|
||
if (Prr=PutOutFromLine(refN,&fromSize)) return(0);
|
||
|
||
reRead:
|
||
if (!hdh) {Prr=MemError(); return(0);}
|
||
|
||
lastHeaderTokenType = ReadHeader(stream,hdh,estSize,refN,False);
|
||
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",lastHeaderTokenType);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
if (lastHeaderTokenType!=EndOfHeader && lastHeaderTokenType!=EndOfMessage)
|
||
{
|
||
Prr = 1;
|
||
goto done;
|
||
}
|
||
|
||
if (fromSize)
|
||
{
|
||
(*hdh)->diskStart -= fromSize; /* count the envelope as part of the header */
|
||
fromSize = 0; /* in case we pass this way again. */
|
||
}
|
||
else
|
||
(*hdh)->diskStart = oldStart;
|
||
|
||
|
||
if (!Prr)
|
||
{
|
||
/*
|
||
* I've wanted to do this for years. say who it's from!
|
||
*/
|
||
PCopy(buf,(*hdh)->who);
|
||
*buf = MIN(*buf,31); // not too long here...
|
||
PCatC(buf,',');
|
||
PCatC(buf,' ');
|
||
PSCat(buf,(*hdh)->subj);
|
||
if (!(*hdh)->isMIME) TransLitString(buf);
|
||
ProgressMessage(kpMessage,buf);
|
||
|
||
// regenerate full info for comment
|
||
PCopy(buf,(*hdh)->who);
|
||
PCatC(buf,',');
|
||
PCatC(buf,' ');
|
||
PSCat(buf,(*hdh)->subj);
|
||
PSCopy((*hdh)->summaryInfo,buf);
|
||
|
||
|
||
/*
|
||
* now, go save the body
|
||
*/
|
||
if (lastHeaderTokenType!=EndOfMessage) ReadEitherBody(stream,refN,hdh,buf,sizeof(buf),estSize,EMSF_ON_ARRIVAL);
|
||
else FSWriteP(refN,"\p\015\015");
|
||
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
/*
|
||
* darn encapsulated stuf
|
||
*/
|
||
if (Prr == '82')
|
||
{
|
||
oldStart = (*hdh)->diskStart;
|
||
ZapHeaderDesc(hdh);
|
||
hdh = NewHeaderDesc(nil);
|
||
PSCopy((*hdh)->summaryInfo,buf);
|
||
Prr = noErr;
|
||
goto reRead;
|
||
}
|
||
|
||
/*
|
||
* ok, got real body now
|
||
*/
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
EnsureNewline(refN);
|
||
ticks = TickCount()-ticks + 1;
|
||
{
|
||
long rate = (estSize*600)/(ticks*1024);
|
||
ComposeLogS(LOG_TPUT,nil,"\p%dK in %d.%d sec; %d.%d KBps",estSize/1024,ticks/60,(ticks/6)%10,rate/10,rate%10);
|
||
}
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d %d;file %x;sc;g",Prr,GetFPos(refN,&end),refN);
|
||
#endif //DEBUG //////////////////////////
|
||
if (!Prr && !(Prr=GetFPos(refN,&end)))
|
||
{
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
TruncOpenFile(refN,end);
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d e %d ds %d st %d;sc;g",Prr,end,(*hdh)->diskStart,GetRLong(SPLIT_THRESH));
|
||
#endif //DEBUG //////////////////////////
|
||
if (!isIMAP && (end-(*hdh)->diskStart>GetRLong(SPLIT_THRESH)))
|
||
count = SplitMessage(refN,(*hdh)->diskStart,(*hdh)->diskEnd,end);
|
||
else
|
||
count = 1;
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
}
|
||
}
|
||
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
|
||
done:
|
||
*hdhp = hdh;
|
||
if (Prr) return(0);
|
||
return(estSize<0 && GetPrefLong(PREF_POP_MODE)==popRStatus && *(*hdh)->status ? 0 : count);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ReadEitherBody - read the body of a message from a pop-3 server
|
||
************************************************************************/
|
||
short ReadEitherBody(TransStream stream,short refN,HeaderDHandle hdh,char *buf,long bSize,long estSize,long context)
|
||
{
|
||
MIMESHandle mimeSList=nil;
|
||
BoundaryType endType;
|
||
|
||
/*
|
||
* is our MIME converter interested in this thing?
|
||
*/
|
||
if (!NoAttachments && estSize>=0)
|
||
{
|
||
mimeSList = NewMIMES(stream,hdh,False,context);
|
||
if (!mimeSList) return(Prr = MemError());
|
||
if (mimeSList == kMIMEBoring) mimeSList = nil;
|
||
else if ((*mimeSList)->readBody==READ_MESSAGE)
|
||
{
|
||
ZapMIMES(mimeSList);
|
||
return(Prr = '82');
|
||
}
|
||
}
|
||
|
||
/*
|
||
* call the proper body reading function
|
||
*/
|
||
endType = mimeSList
|
||
? (*(*mimeSList)->readBody)(stream,refN,mimeSList,buf,bSize,ReadPOPLine)
|
||
: ReadPlainBody(stream,refN,buf,bSize,estSize);
|
||
|
||
if (endType == btError) Prr = 1;
|
||
#ifdef DEBUG ////////////////////////////
|
||
if (BUG15) Dprintf("\p%d;sc;g",Prr);
|
||
#endif //DEBUG //////////////////////////
|
||
ZapMIMES(mimeSList);
|
||
return(Prr);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* RoomForMessage - make sure there's room for a message on both the
|
||
* attachments folder volume and the in box volume
|
||
**********************************************************************/
|
||
OSErr RoomForMessage(long msgsize)
|
||
{
|
||
FSSpec attSpec;
|
||
OSErr err=noErr;
|
||
|
||
GetAttFolderSpec(&attSpec);
|
||
err = VolumeMargin(MailRoot.vRef,msgsize);
|
||
if (!err && MailRoot.vRef==attSpec.vRefNum) return(noErr);
|
||
if (!err) err = VolumeMargin(attSpec.vRefNum,msgsize);
|
||
|
||
return(err);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ReadPlainBody - handle the body of a non-MIME message.
|
||
************************************************************************/
|
||
POPLineType ReadPlainBody(TransStream stream,short refN,char *buf,long bSize,long estSize)
|
||
{
|
||
long size;
|
||
Boolean hexing, singling, pgping;
|
||
POPLineType lineType;
|
||
#ifdef OLDPGP
|
||
PGPContext pgp;
|
||
#endif
|
||
|
||
/*
|
||
* prepare converters
|
||
*/
|
||
if (!NoAttachments)
|
||
{
|
||
BeginHexBin(nil);
|
||
BeginAbomination("",nil);
|
||
#ifdef OLDPGP
|
||
BeginPGP(&pgp);
|
||
#endif
|
||
hexing = singling = pgping = False;
|
||
}
|
||
ReadPOPLine(stream,nil,0,nil);
|
||
|
||
/*
|
||
* main processing loop
|
||
*/
|
||
for (lineType=ReadPOPLine(stream,buf,bSize,&size);
|
||
lineType!=plError && lineType!=plEndOfMessage;
|
||
lineType=ReadPOPLine(stream,buf,bSize,&size))
|
||
{
|
||
/*
|
||
* give each converter a crack at the line
|
||
*/
|
||
if (!NoAttachments)
|
||
{
|
||
if (!(singling || pgping)) hexing = ConvertHexBin(refN,buf,&size,lineType,estSize);
|
||
if (!(hexing || pgping)) singling = ConvertUUSingle(refN,buf,&size,lineType,estSize,nil,nil);
|
||
#ifdef OLDPGP
|
||
if (!(singling || hexing)) pgping = ConvertPGP(refN,buf,&size,lineType,estSize,&pgp);
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
* write the line
|
||
*/
|
||
if (size && (Prr=AWrite(refN,&size,buf)))
|
||
break;
|
||
}
|
||
|
||
/*
|
||
* record skipped message
|
||
*/
|
||
if (!Prr && lineType == plEndOfMessage && estSize < 0)
|
||
{
|
||
Str255 msg;
|
||
long count;
|
||
#ifdef TWO
|
||
if (Headering || PrefIsSet(PREF_NO_BIGGIES))
|
||
ComposeRString(msg,BIG_MESSAGE_MSG2,-estSize);
|
||
else
|
||
ComposeRString(msg,NOSPACE_SKIP,-estSize);
|
||
#else
|
||
ComposeRString(msg,BIG_MESSAGE_MSG,-estSize);
|
||
#endif
|
||
count = *msg;
|
||
Prr=AWrite(refN,&count,msg+1);
|
||
}
|
||
|
||
/*
|
||
* close converters
|
||
*/
|
||
if (!NoAttachments)
|
||
{
|
||
EndHexBin();
|
||
SaveAbomination(nil,0);
|
||
#ifdef OLDPGP
|
||
EndPGP(&pgp);
|
||
#endif
|
||
|
||
/*
|
||
* write attachment notes, if any
|
||
*/
|
||
WriteAttachNote(refN);
|
||
}
|
||
|
||
/*
|
||
* report error (if any)
|
||
*/
|
||
if (Prr) FileSystemError(WRITE_MBOX,"",Prr);
|
||
// else if (!UUPCIn) Progress(100,NoChange,nil,nil,nil);
|
||
|
||
return(Prr?btError : btEndOfMessage);
|
||
}
|
||
|
||
/************************************************************************
|
||
* PutOutFromLine - write an envelope
|
||
************************************************************************/
|
||
int PutOutFromLine(short refN,long *fromLen)
|
||
{
|
||
Str255 fromLine;
|
||
long len;
|
||
|
||
*fromLen = len = SumToFrom(nil,fromLine);
|
||
if (Prr=AWrite(refN,&len,fromLine))
|
||
return(FileSystemError(WRITE_MBOX,"",Prr));
|
||
return(noErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* DupHeader - copy the header of a split message
|
||
************************************************************************/
|
||
int DupHeader(short refN,UPtr buff,long bSize,long offset,long headerSize)
|
||
{
|
||
long currentOffset;
|
||
long readBytes,writeBytes;
|
||
long copied;
|
||
|
||
if (Prr=GetFPos(refN,¤tOffset))
|
||
return(FileSystemError(READ_MBOX,"",Prr));
|
||
for (copied=0; copied<headerSize; copied += readBytes)
|
||
{
|
||
if (Prr=SetFPos(refN,fsFromStart,offset+copied))
|
||
return(FileSystemError(READ_MBOX,"",Prr));
|
||
readBytes = bSize < headerSize-copied ? bSize : headerSize-copied;
|
||
if (Prr=ARead(refN,&readBytes,buff))
|
||
return(FileSystemError(READ_MBOX,"",Prr));
|
||
if (Prr=SetFPos(refN,fsFromStart,currentOffset))
|
||
return(FileSystemError(WRITE_MBOX,"",Prr));
|
||
writeBytes = readBytes;
|
||
if (Prr=FSZWrite(refN,&writeBytes,buff))
|
||
return(FileSystemError(WRITE_MBOX,"",Prr));
|
||
currentOffset += writeBytes;
|
||
}
|
||
return(noErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FirstUnread - find the first unread message
|
||
* We do try to be clever about it.
|
||
************************************************************************/
|
||
int FirstUnread(TransStream stream,int count)
|
||
{
|
||
short first, theLast, on;
|
||
static short lastCount=0;
|
||
Boolean hasBeen;
|
||
|
||
/*
|
||
* give LAST a whirl
|
||
*/
|
||
if (POPLast(stream,&theLast)) return(count+1);
|
||
theLast = MAX(theLast,0);
|
||
theLast = MIN(theLast,count);
|
||
|
||
/*
|
||
* if LAST returns nonzero, believe it
|
||
* Also believe it if the user tells us to believe it
|
||
*/
|
||
if (theLast || GetPrefLong(PREF_POP_MODE)==popRLast)
|
||
return(theLast+1);
|
||
|
||
/*
|
||
* LAST was a dead end. Do it the hard way
|
||
*/
|
||
on = lastCount;
|
||
lastCount = count;
|
||
|
||
#define SETHASBEEN(o,c)\
|
||
do {hasBeen=HasBeenRead(stream,o,c);if (CommandPeriod) return(c+1);} while (0)
|
||
if (PrefIsSet(PREF_NO_BIGGIES))
|
||
{
|
||
/* Heuristics */
|
||
if (on && on<=count)
|
||
{
|
||
SETHASBEEN(on,count);
|
||
if (hasBeen)
|
||
{
|
||
SETHASBEEN(on+1,count);
|
||
if (!hasBeen) return(on+1);
|
||
}
|
||
}
|
||
SETHASBEEN(count,count);
|
||
if (hasBeen) return(count+1);
|
||
if (count==1) return(1);
|
||
|
||
/* search... */
|
||
for (on=count-1;on;on--)
|
||
{
|
||
SETHASBEEN(on,count);
|
||
if (hasBeen) break;
|
||
}
|
||
return(on+1);
|
||
}
|
||
else
|
||
{
|
||
first = 1;
|
||
theLast = count;
|
||
/*
|
||
* try to cut the search short via heuristics
|
||
*/
|
||
if (on && on<=count)
|
||
{
|
||
SETHASBEEN(on,count);
|
||
if (hasBeen)
|
||
{
|
||
if (on<count)
|
||
{
|
||
SETHASBEEN(++on,count);
|
||
if (!hasBeen) return(on);
|
||
}
|
||
first = on+1;
|
||
}
|
||
else
|
||
theLast = on-1;
|
||
}
|
||
else
|
||
{
|
||
SETHASBEEN(count,count);
|
||
if (hasBeen) return(count+1);
|
||
SETHASBEEN(1,count);
|
||
if (count==1 || !hasBeen) return(1);
|
||
theLast=count-1;
|
||
first = 2;
|
||
on = count;
|
||
hasBeen = False;
|
||
}
|
||
|
||
/*
|
||
* hi ho, hi ho, it's off to search we go
|
||
*/
|
||
while (first<=theLast)
|
||
{
|
||
on = (first+theLast)/2;
|
||
SETHASBEEN(on,count);
|
||
if (hasBeen) first=on+1;
|
||
else theLast=on-1;
|
||
}
|
||
if (!hasBeen)
|
||
return(on);
|
||
else
|
||
return(on+1);
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* HasBeenRead - has a particular message been read?
|
||
* look for a "Status:" header; if it's "Status: R<something>", message
|
||
* has been read
|
||
************************************************************************/
|
||
Boolean HasBeenRead(TransStream stream,short msgNum,short count)
|
||
{
|
||
Str127 scratch;
|
||
Boolean unread=False, statFound=False;
|
||
Str31 terminate;
|
||
Str31 status;
|
||
UPtr cp;
|
||
long size;
|
||
|
||
if (msgNum>count) return(0);
|
||
Progress((msgNum*100)/count,NoBar,nil,GetRString(scratch,FIRST_UNREAD),nil);
|
||
GetRString(terminate,ALREADY_READ);
|
||
GetRString(status,STATUS);
|
||
NumToString(msgNum,scratch);
|
||
PLCat(scratch,1);
|
||
POPCmd(stream,kpcTop,scratch);
|
||
for (size=sizeof(scratch);
|
||
!(Prr=RecvLine(stream,scratch,&size)) &&
|
||
!POP_TERM(scratch,size);
|
||
size=sizeof(scratch))
|
||
if (!unread && !statFound && !striscmp(scratch,status+1))
|
||
{
|
||
statFound = True;
|
||
for (cp=scratch;cp<scratch+size;cp++)
|
||
{
|
||
if (*cp==':')
|
||
{
|
||
for (cp++;cp<=scratch+size-*terminate;cp++)
|
||
if (!striscmp(cp,terminate+1)) break;
|
||
unread = cp> scratch+size-*terminate;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
ComposeLogS(LOG_LMOS,nil,"\pHasBeenRead: %d sf %d un %d %p",msgNum,statFound,!unread,statFound&&!unread?"\pREAD":"\pUNREAD");
|
||
return(statFound && !unread);
|
||
}
|
||
|
||
/************************************************************************
|
||
* StampPartNumber - put the part number on a mail message
|
||
************************************************************************/
|
||
void StampPartNumber(MSumPtr sum,short part,short count)
|
||
{
|
||
char *spot;
|
||
short i, digits, len;
|
||
|
||
if (part==1)
|
||
sum->flags |= FLAG_FIRST;
|
||
else
|
||
sum->flags |= FLAG_SUBSEQUENT;
|
||
|
||
for (i=count,digits=0;i;i/=10,digits++);
|
||
len = 2*digits+2;
|
||
spot=sum->subj+MIN(*sum->subj+len,sizeof(sum->subj)-1);
|
||
*sum->subj = spot-sum->subj;
|
||
for (i=digits;i;i--,count/=10) *spot-- = '0'+count%10;
|
||
*spot-- = '/';
|
||
for (i=digits;i;i--,part/=10) *spot-- = '0'+part%10;
|
||
*spot = ' ';
|
||
|
||
}
|
||
|
||
/************************************************************************
|
||
* RecordAttachment - note that we've attached a file
|
||
************************************************************************/
|
||
OSErr RecordAttachment(FSSpecPtr spec,HeaderDHandle hdh)
|
||
{
|
||
Str255 theMessage;
|
||
OSErr err;
|
||
Boolean deleted = false;
|
||
|
||
if(FileTypeOf(spec) == REG_FILE_TYPE)
|
||
{
|
||
if(!hdh || AAFetchResData((*hdh)->contentAttributes,AttributeStrn+aRegFile,theMessage))
|
||
{
|
||
FSpDelete(spec);
|
||
deleted = true;
|
||
GetRString(theMessage, STOLEN_REG_FILE);
|
||
}
|
||
else
|
||
{
|
||
if(!gRegFiles) gRegFiles = NuHandle(0);
|
||
if(gRegFiles) PtrPlusHand_(spec, gRegFiles, sizeof(FSSpec));
|
||
}
|
||
}
|
||
|
||
// Long filename?
|
||
if (hdh && !(*hdh)->relatedPart) FixLongFilename(hdh,spec);
|
||
|
||
/*
|
||
* update global fsspec record
|
||
*/
|
||
if (!deleted)
|
||
{
|
||
if (!LastAttSpec) LastAttSpec = NewH(FSSpec);
|
||
if (LastAttSpec) **(FSSpecHandle)LastAttSpec = *spec;
|
||
}
|
||
|
||
// only record top-level files
|
||
if (AttFolderStack && !SameSpec(&CurrentAttFolderSpec,&AttFolderSpec)) return noErr;
|
||
|
||
if(!deleted)
|
||
{
|
||
if (hdh && (*hdh)->relatedPart)
|
||
RelatedNote(spec,hdh,theMessage);
|
||
else AttachNoteLo(spec,theMessage);
|
||
}
|
||
|
||
/*
|
||
* tack on the note
|
||
*/
|
||
if (!AttachedFiles) AttachedFiles = NuHandle(0);
|
||
if (AttachedFiles) PtrPlusHand_(theMessage+1,AttachedFiles,*theMessage);
|
||
if (err=MemError())
|
||
{
|
||
WarnUser(BINHEX_MEM,err);
|
||
CommandPeriod = true;
|
||
return(err);
|
||
}
|
||
|
||
if(deleted)
|
||
return(noErr);
|
||
|
||
RecordTransAttachments(spec);
|
||
|
||
/*
|
||
* add a comment?
|
||
*/
|
||
if (hdh && !PrefIsSet(PREF_NO_ATT_COMMENT))
|
||
DTSetComment(spec,PCopy(theMessage,(*hdh)->summaryInfo));
|
||
|
||
/*
|
||
* is there a date?
|
||
*/
|
||
if (hdh && !AAFetchResData((*hdh)->contentAttributes,AttributeStrn+aModDate,theMessage))
|
||
{
|
||
uLong mod;
|
||
long zone;
|
||
|
||
if (mod=BeautifyDate(theMessage,&zone))
|
||
if (mod>(long)GetRLong(TOO_EARLY_FILE)) AFSpSetMod(spec,mod+ZoneSecs());
|
||
}
|
||
|
||
// record for attachment received statistics
|
||
UpdateNumStatWithTime(kStatReceivedAttach,1,hdh?(*hdh)->gmtSecs+ZoneSecs():LocalDateTime());
|
||
|
||
return(noErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FixLongFilename - attach a long filename to an FSSpec made from a shorter
|
||
* filename
|
||
************************************************************************/
|
||
OSErr FixLongFilename(HeaderDHandle hdh,FSSpecPtr spec)
|
||
{
|
||
Str127 longFilename;
|
||
Str31 filenameAtt;
|
||
Str255 part;
|
||
Str255 charset;
|
||
|
||
*longFilename = *charset = 0;
|
||
|
||
// is there a filename at all?
|
||
if (AAFetchData((*hdh)->contentAttributes,GetRString(filenameAtt,AttributeStrn+aFilename),longFilename))
|
||
{
|
||
// no "filename". Is there a "filename*"?
|
||
PCatC(filenameAtt,'*');
|
||
if (!AAFetchData((*hdh)->contentAttributes,filenameAtt,part))
|
||
Un2184Append(longFilename,sizeof(longFilename),part,charset,true);
|
||
else
|
||
{
|
||
// no "filename*'.
|
||
short i;
|
||
for (i=0;;i++)
|
||
{
|
||
// Is there a "filename*0"?
|
||
if (!AAFetchData((*hdh)->contentAttributes,ComposeRString(filenameAtt,AttributeStrn+aFilename,i),part))
|
||
{
|
||
if (i==0) GetRString(charset,UNSPECIFIED_CHARSET);
|
||
Un2184Append(longFilename,sizeof(longFilename),part,charset,false);
|
||
}
|
||
// Is there a "filename*0*"?
|
||
else if (!AAFetchData((*hdh)->contentAttributes,PCat(filenameAtt,"\p*"),part))
|
||
Un2184Append(longFilename,sizeof(longFilename),part,charset,true);
|
||
else
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// check for applesingle
|
||
if (EqualStrRes(LDRef(hdh)->contentSubType,MIME_APPLEFILE))
|
||
if (longFilename[1]=='%' && *longFilename>1)
|
||
BMD(longFilename+2,longFilename+1,--*longFilename);
|
||
UL(hdh);
|
||
|
||
// is it a short filename?
|
||
if (*longFilename <= 31 && !AnyHighBits(longFilename+1,*longFilename)) return noErr;
|
||
|
||
// Ok, it's a long filename. Set the name of the file to it
|
||
// Make sure that the file is unique
|
||
(void) MakeUniqueLongFileName ( spec->vRefNum, spec->parID, longFilename, kTextEncodingUnknown, sizeof ( longFilename ) - 1 );
|
||
return FSpSetLongName(spec,kTextEncodingUnknown,longFilename,spec);
|
||
}
|
||
|
||
/************************************************************************
|
||
* Un2184Append - undo some 2184, and append to an existing string
|
||
************************************************************************/
|
||
PStr Un2184Append(PStr dest,short sizeofDest,PStr orig,PStr charset,Boolean isEncoded)
|
||
{
|
||
Str255 decoded;
|
||
short spaceNeeded;
|
||
short leftAppend = GetRLong(MIN_LEFT_APPEND);
|
||
Str31 elide;
|
||
|
||
if (isEncoded) Un2184(decoded,orig,charset);
|
||
else PCopy(decoded,orig);
|
||
|
||
spaceNeeded = 1+*dest+*decoded - sizeofDest;
|
||
if (spaceNeeded > 0)
|
||
{
|
||
// add in space for the ellipsis
|
||
spaceNeeded += *GetRString(elide,ELIDE_2184_STRING);
|
||
// Make sure we leave at least 64 characters of the original string
|
||
if (*dest > leftAppend)
|
||
{
|
||
// we can get all we want from the excess of dest
|
||
if (spaceNeeded <= *dest - leftAppend)
|
||
*dest -= spaceNeeded;
|
||
else
|
||
{
|
||
// take some from dest...
|
||
spaceNeeded -= *dest - leftAppend;
|
||
|
||
// and take the rest from the RIGHT side of decoded
|
||
BMD(decoded+1,decoded+1+spaceNeeded,*decoded-spaceNeeded);
|
||
*decoded -= spaceNeeded;
|
||
}
|
||
}
|
||
|
||
// copy the elision string
|
||
PCat(dest,elide);
|
||
}
|
||
|
||
// copy the decoded string
|
||
PCat(dest,decoded);
|
||
|
||
return dest;
|
||
}
|
||
|
||
/************************************************************************
|
||
* Un2184 - undo 2184 encoding
|
||
************************************************************************/
|
||
PStr Un2184(PStr dest, PStr orig, PStr charset)
|
||
{
|
||
short tableID;
|
||
Boolean found;
|
||
|
||
// copy it in
|
||
PCopy(dest,orig);
|
||
|
||
// do we have a charset?
|
||
if (!*charset)
|
||
{
|
||
if (PIndex(orig,'\''))
|
||
{
|
||
UPtr spot = orig+1;
|
||
PToken(orig,charset,&spot,"'"); // grab charset
|
||
PToken(orig,dest,&spot,"'"); // skip language
|
||
PToken(orig,dest,&spot,"'"); // put the rest into dest
|
||
}
|
||
if (!*charset) GetRString(charset,UNSPECIFIED_CHARSET);
|
||
}
|
||
|
||
// undo the QP
|
||
FixURLString(dest);
|
||
|
||
// transliterate
|
||
tableID = FindMIMECharsetLo(charset,&found);
|
||
if (!found) tableID = FindMIMECharset(GetRString(charset,UNSPECIFIED_CHARSET));
|
||
TransLitRes(dest+1,*dest,tableID);
|
||
|
||
return dest;
|
||
}
|
||
|
||
/************************************************************************
|
||
* AttachNoteLo - format the attachment note
|
||
************************************************************************/
|
||
void AttachNoteLo(FSSpecPtr spec,PStr theMessage)
|
||
{
|
||
Str31 folderName;
|
||
Str15 typeString, creatorString;
|
||
FInfo info;
|
||
long fid;
|
||
Str31 fidStr;
|
||
|
||
if (FSpIsItAFolder(spec))
|
||
{
|
||
info.fdCreator = kSystemIconsCreator;
|
||
info.fdType = kGenericFolderIcon;
|
||
fid = SpecDirId(spec);
|
||
}
|
||
else
|
||
{
|
||
FSpGetFInfo(spec,&info);
|
||
FSMakeFID(spec,&fid);
|
||
}
|
||
|
||
BMD(&info.fdCreator,creatorString+1,4);
|
||
BMD(&info.fdType,typeString+1,4);
|
||
*creatorString = *typeString = 4;
|
||
SanitizeFN(creatorString,creatorString,ATTCONV_BAD_CHARS,ATTCONV_REP_CHARS,false);
|
||
SanitizeFN(typeString,typeString,ATTCONV_BAD_CHARS,ATTCONV_REP_CHARS,false);
|
||
GetMyVolName(spec->vRefNum,folderName);
|
||
ComposeRString(theMessage,FILE_FOLDER_FMT,
|
||
folderName,spec->name,typeString,creatorString,Long2Hex(fidStr,fid));
|
||
}
|
||
|
||
/************************************************************************
|
||
* RelatedNote - format the related note
|
||
************************************************************************/
|
||
void RelatedNote(FSSpecPtr spec,HeaderDHandle hdh,PStr theMessage)
|
||
{
|
||
Str31 folderName;
|
||
long fid;
|
||
Str255 quoteName;
|
||
|
||
FSMakeFID(spec,&fid);
|
||
PCopy(quoteName,spec->name);
|
||
|
||
GetMyVolName(spec->vRefNum,folderName);
|
||
ComposeRString(theMessage,RELATED_FMT,MIME_RELATED,
|
||
folderName,URLEscape(quoteName),fid,(*hdh)->cidHash,(*hdh)->relURLHash,(*hdh)->absURLHash);
|
||
}
|
||
|
||
/************************************************************************
|
||
* AddAttachInfo - attach a note about problems with the enclosure
|
||
************************************************************************/
|
||
void AddAttachInfo(short theIndex, long result)
|
||
{
|
||
Str255 theMessage;
|
||
|
||
ComposeString(theMessage,"\p%r%d\015", theIndex, result);
|
||
PtrPlusHand_(theMessage+1,AttachedFiles,*theMessage);
|
||
}
|
||
|
||
/************************************************************************
|
||
* WriteAttachNote - write the attachment note
|
||
************************************************************************/
|
||
OSErr WriteAttachNote(short refN)
|
||
{
|
||
long size;
|
||
short err = noErr;
|
||
|
||
if (AttachedFiles && *AttachedFiles && (size=GetHandleSize_(AttachedFiles)))
|
||
{
|
||
if (!(err=EnsureNewline(refN)))
|
||
{
|
||
err = AWrite(refN,&size,LDRef(AttachedFiles));
|
||
UL(AttachedFiles);
|
||
SetHandleBig_(AttachedFiles,0);
|
||
}
|
||
}
|
||
return(err);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* POPLast - give the LAST command to find the last unread message
|
||
************************************************************************/
|
||
short POPLast(TransStream stream,short *lastRead)
|
||
{
|
||
Str127 buffer;
|
||
UPtr spot;
|
||
long size = sizeof(buffer);
|
||
|
||
if (Prr=POPCmdGetReply(stream,kpcLast,nil,buffer,&size)) return(Prr);
|
||
ComposeLogS(LOG_LMOS,nil,"\pLast: %s",buffer);
|
||
if (*buffer != '+') Prr = *buffer;
|
||
else
|
||
{
|
||
strtok(buffer," "); /* skip ok */
|
||
if (spot=strtok(nil," \015"))
|
||
*lastRead = Atoi(spot); /* read message size */
|
||
else
|
||
return(1);
|
||
}
|
||
return(0);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ReadPOPLine - read a line from the POP server.
|
||
* Returns the kind of line it is:
|
||
* plComplete - a line that began with a newline
|
||
* plPartial - the remainder of a line
|
||
* plBlank - a blank line
|
||
* plEndOfMessage - the message is OVER.
|
||
* plError - there has been an error (recorded in global Prr)
|
||
* Also removes the "." that escapes lines beginning with ".",
|
||
* and adds a ">" to escape envelopes
|
||
************************************************************************/
|
||
POPLineType ReadPOPLine(TransStream stream,UPtr buf, long bSize, long *len)
|
||
{
|
||
static wasNl;
|
||
POPLineType returnType;
|
||
long freeStack;
|
||
|
||
// sniff the stack to see if we're low
|
||
#ifdef THREADING_ON
|
||
if (InAThread())
|
||
{
|
||
ThreadID threadID;
|
||
|
||
GetCurrentThread (&threadID);
|
||
ThreadCurrentStackSpace(threadID, &freeStack);
|
||
}
|
||
else
|
||
#endif
|
||
freeStack = StackSpace();
|
||
|
||
if (freeStack < kLowStackSize)
|
||
{
|
||
CommandPeriod = true;
|
||
StackLowErr = true;
|
||
}
|
||
|
||
/*
|
||
* nil buffer initializes
|
||
*/
|
||
if (!buf)
|
||
{
|
||
wasNl = True;
|
||
return(plEndOfMessage);
|
||
}
|
||
|
||
/*
|
||
* grab the line
|
||
*/
|
||
*len = bSize-1; /* allow extra char for escaped envelopes */
|
||
if (Prr = RecvLine(stream,buf,len)) return(plError);
|
||
|
||
/*
|
||
* update progress indicator
|
||
*/
|
||
ByteProgress(nil,-*len,0);
|
||
|
||
/*
|
||
* are we looking at the beginning of a line?
|
||
*/
|
||
if (wasNl)
|
||
{
|
||
returnType = plComplete;
|
||
|
||
if (buf[0] == '.') /* leading period */
|
||
{
|
||
if (buf[1]=='.') /* if dot doubled, was in message data */
|
||
{
|
||
BMD(buf+1,buf,*len);
|
||
--*len;
|
||
}
|
||
else if (buf[1] == '\015') /* if dot followed by \015, end of message */
|
||
returnType = plEndOfMessage;
|
||
}
|
||
else if (IsFromLine(buf)) /* is envelope? */
|
||
{
|
||
BMD(buf,buf+1,*len); /* escape with '>' */
|
||
++*len;
|
||
buf[0] = '>';
|
||
}
|
||
}
|
||
else
|
||
returnType = plPartial;
|
||
|
||
wasNl = *len ? buf[*len-1] == '\015' : True; /* set for next go-round */
|
||
|
||
return(returnType);
|
||
}
|
||
|
||
/************************************************************************
|
||
* SplitMessage - split a message into pieces
|
||
************************************************************************/
|
||
short SplitMessage(short refN, long hStart, long hEnd, long msgEnd)
|
||
{
|
||
long splitSize = GetRLong(FRAGMENT_SIZE);
|
||
long bodySplit;
|
||
short err;
|
||
short count;
|
||
short headerNl=0;
|
||
long *froms=nil;
|
||
long *tos=nil;
|
||
Boolean *reals=nil;
|
||
short i;
|
||
Boolean headerReal;
|
||
|
||
if (hEnd-hStart > splitSize)
|
||
{
|
||
/* uh-oh. the header is waaaaaaay big */
|
||
if (err = HuntNewline(refN,hStart+4096,&hEnd,&headerReal)) goto done;
|
||
headerNl = headerReal?1:2;
|
||
}
|
||
|
||
/*
|
||
* how many splits do we need?
|
||
*/
|
||
bodySplit = splitSize - (hEnd-hStart); /* how much of the body goes into each split */
|
||
count = (msgEnd-hEnd+bodySplit-1)/bodySplit; /* how many splits do we need? */
|
||
bodySplit = (msgEnd-hEnd)/count; /* divide them evenly */
|
||
|
||
/*
|
||
* Ok, now let's find all the split locations
|
||
*/
|
||
if (!(froms = NuPtr((count+1)*sizeof(long *))) ||
|
||
!(tos = NuPtr((count+1)*sizeof(long *))) ||
|
||
!(reals = NuPtr((count+1)*sizeof(Boolean))))
|
||
{
|
||
WarnUser(MEM_ERR,err=MemError());
|
||
goto done;
|
||
}
|
||
|
||
for (i=1,*froms=hEnd;i<count;i++)
|
||
{
|
||
if (err = HuntNewline(refN,hEnd+i*bodySplit,&froms[i],&reals[i]))
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* stuff the end of the message into the last split location,
|
||
* and the beginning into the first
|
||
*/
|
||
froms[count] = msgEnd;
|
||
reals[count] = True;
|
||
reals[0] = True;
|
||
tos[0] = hEnd + headerNl;
|
||
|
||
/*
|
||
* now, calculate where it all goes
|
||
*/
|
||
for (i=1;i<count;i++)
|
||
{
|
||
tos[i] = tos[i-1] + /* start of last body */
|
||
hEnd-hStart+headerNl + /* header size */
|
||
(reals[i]?0:1) + /* room for extra nl? */
|
||
froms[i]-froms[i-1]; /* size of last body segment */
|
||
}
|
||
tos[count] = tos[count-1]+froms[count]-froms[count-1];
|
||
|
||
/*
|
||
* copy the segments, one by one
|
||
*/
|
||
for (i=count-1;i>=0;i--)
|
||
{
|
||
if (tos[i] != froms[i])
|
||
{
|
||
/*
|
||
* copy body bytes
|
||
*/
|
||
if (err = CopyFBytes(refN,froms[i],froms[i+1]-froms[i],refN,tos[i]))
|
||
{WarnUser(WRITE_MBOX,err); goto done;}
|
||
|
||
if (i)
|
||
{
|
||
/*
|
||
* copy the header bytes
|
||
*/
|
||
if (err = CopyFBytes(refN,hStart,hEnd-hStart,refN,tos[i]-(hEnd-hStart+headerNl)))
|
||
{FileSystemError(WRITE_MBOX,"",err); goto done;}
|
||
}
|
||
|
||
/*
|
||
* do we need to add an ending newline to the header?
|
||
*/
|
||
if (headerNl)
|
||
{
|
||
if (err = SetFPos(refN,fsFromStart,tos[i]-headerNl))
|
||
{FileSystemError(WRITE_MBOX,"",err); goto done;}
|
||
if (err = FSWriteP(refN,headerReal?Cr:"\p\015\015"))
|
||
{FileSystemError(WRITE_MBOX,"",err); goto done;}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* do we need to add an ending newline to the body?
|
||
*/
|
||
if (!reals[i+1])
|
||
{
|
||
if (err = SetFPos(refN,fsFromStart,tos[i]+froms[i+1]-froms[i]))
|
||
{FileSystemError(WRITE_MBOX,"",err); goto done;}
|
||
if (err = FSWriteP(refN,Cr))
|
||
{FileSystemError(WRITE_MBOX,"",err); goto done;}
|
||
}
|
||
}
|
||
|
||
TruncOpenFile(refN,tos[count]);
|
||
|
||
done:
|
||
if (tos) ZapPtr(tos);
|
||
if (froms) ZapPtr(froms);
|
||
if (reals) ZapPtr(reals);
|
||
Prr = err;
|
||
return(err ? 0 : count);
|
||
}
|
||
|
||
#ifdef POPSECURE
|
||
/************************************************************************
|
||
* VetPOP - make sure the 's POP account is ok.
|
||
************************************************************************/
|
||
short VetPOP(void)
|
||
{
|
||
Str255 , host;
|
||
long port;
|
||
short err;
|
||
|
||
if (UUPCIn && !UUPCOut) {WarnUser(UUPC_SECURE,0);return(1);}
|
||
GetPOPInfo(,host);
|
||
port = GetRLong(POP_PORT);
|
||
if ((err=StartPOP(host,port))==noErr)
|
||
{
|
||
(void) POPIntroductions();
|
||
if (Prr) err=Prr;
|
||
}
|
||
EndPOP();
|
||
if (UseCTB && !err) err = CTBNavigateSTRN(NAVMID);
|
||
return(err);
|
||
}
|
||
#endif
|
||
|
||
#pragma segment MD5
|
||
static char hex[]="0123456789abcdef";
|
||
/************************************************************************
|
||
* GenDigest - generate a digest for APOP
|
||
************************************************************************/
|
||
Boolean GenDigest(UPtr banner,UPtr secret,UPtr digest)
|
||
{
|
||
Str255 stamp;
|
||
MD5_CTX md5;
|
||
short i;
|
||
|
||
if (!*ExtractStamp(stamp,banner)) return(False);
|
||
|
||
MD5Init(&md5);
|
||
MD5Update(&md5,stamp+1,*stamp);
|
||
MD5Update(&md5,secret+1,*secret);
|
||
MD5Final(&md5);
|
||
|
||
for (i=0;i<sizeof(md5.digest);i++)
|
||
{
|
||
digest[2*i+1] = hex[(md5.digest[i]>>4)&0xf];
|
||
digest[2*i+2] = hex[md5.digest[i]&0xf];
|
||
}
|
||
digest[0] = 2*sizeof(md5.digest);
|
||
return(True);
|
||
}
|
||
|
||
#define kmd5opad (0x5C)
|
||
#define kmd5ipad (0x36)
|
||
#define kmd5Len (64)
|
||
/************************************************************************
|
||
* GenKeyedDigest - generate a keyed digest for APOP
|
||
************************************************************************/
|
||
Boolean GenKeyedDigest(UPtr banner,UPtr secret,UPtr digest)
|
||
{
|
||
Str255 stamp;
|
||
MD5_CTX /*ipadMD5,*/ md5;
|
||
short i;
|
||
/* Str255 localS; */
|
||
|
||
if (!*ExtractStamp(stamp,banner)) return(False);
|
||
|
||
/*
|
||
Zero(localS);
|
||
BMD(secret+1,localS,*secret);
|
||
for (i=0;i<kmd5Len;i++) localS[i] ^= kmd5ipad;
|
||
|
||
MD5Init(&ipadMD5);
|
||
MD5Update(&ipadMD5,localS,kmd5Len);
|
||
MD5Update(&ipadMD5,stamp+1,*stamp);
|
||
MD5Final(&ipadMD5);
|
||
|
||
Zero(localS);
|
||
BMD(secret+1,localS,*secret);
|
||
for (i=0;i<kmd5Len;i++) localS[i] ^= kmd5opad;
|
||
|
||
MD5Init(&md5);
|
||
MD5Update(&md5,localS,kmd5Len);
|
||
MD5Update(&md5,ipadMD5.digest,sizeof(ipadMD5.digest));
|
||
MD5Final(&md5);
|
||
*/
|
||
hmac_md5(stamp+1,*stamp,secret+1,*secret,&md5.digest);
|
||
|
||
for (i=0;i<sizeof(md5.digest);i++)
|
||
{
|
||
digest[2*i+1] = hex[(md5.digest[i]>>4)&0xf];
|
||
digest[2*i+2] = hex[md5.digest[i]&0xf];
|
||
}
|
||
digest[0] = 2*sizeof(md5.digest);
|
||
return(True);
|
||
}
|
||
|
||
/************************************************************************
|
||
* ExtractStamp - grab the timestamp out of a POP banner
|
||
************************************************************************/
|
||
UPtr ExtractStamp(UPtr stamp,UPtr banner)
|
||
{
|
||
UPtr cp1,cp2;
|
||
|
||
*stamp = 0;
|
||
banner[*banner+1] = 0;
|
||
if (cp1=strchr(banner+1,'<'))
|
||
if (cp2=strchr(cp1+1,'>'))
|
||
{
|
||
*stamp = cp2-cp1+1;
|
||
strncpy(stamp+1,cp1,*stamp);
|
||
}
|
||
return(stamp);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* New LMOS strategy
|
||
************************************************************************/
|
||
|
||
/************************************************************************
|
||
* FillPOPD - fill the pop descriptor with important info from a header
|
||
************************************************************************/
|
||
void FillPOPD(POPDPtr pdp,HeaderDHandle hdh)
|
||
{
|
||
Str255 msgId;
|
||
uLong hash;
|
||
|
||
if (pdp->uidHash==0)
|
||
{
|
||
pdp->receivedGMT = GMTDateTime();
|
||
if (pdp->uidHash==kNeverHashed ||
|
||
pdp->uidHash==kNoMessageId)
|
||
{
|
||
if (*HeaderMsgId(hdh,msgId))
|
||
hash = MIDHash(msgId+1,*msgId);
|
||
else
|
||
hash = FakeMIDHash(hdh);
|
||
pdp->uidHash = hash;
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* BuildPOPD - build the descriptor of the work we have to do with the
|
||
* pop server
|
||
* -- HERE BE DRAGONS --
|
||
* be careful with this code. It does things in a specific order for a reason
|
||
************************************************************************/
|
||
OSErr BuildPOPD(TransStream stream,POPDHandle *popDH,short count,XferFlags *flags,Boolean *capabilities)
|
||
{
|
||
POPDesc new, old;
|
||
short i;
|
||
short spot;
|
||
Boolean room;
|
||
Boolean anyRoom = True; /* we wouldn't be here if there weren't at least a little room */
|
||
long skipSize = GetRLong(BIG_MESSAGE) K;
|
||
POPDHandle oldDH = nil;
|
||
Boolean sbm = PrefIsSet(PREF_NO_BIGGIES);
|
||
Boolean lmos = PrefIsSet(PREF_LMOS);
|
||
long spaceNeeded = 0;
|
||
short lastRead = 0; /* for the status or last methods */
|
||
uLong age = GetPrefLong(PREF_LMOS_XDAYS);
|
||
Boolean onFetch, onDelete;
|
||
Boolean aged;
|
||
Boolean plentyRoom;
|
||
short popMode = GetPrefLong(PREF_POP_MODE);
|
||
int total = 0;
|
||
#ifdef THREADING_ON
|
||
TOCHandle tempInTocH = nil;
|
||
|
||
if (InAThread())
|
||
tempInTocH = GetTempInTOC();
|
||
#endif
|
||
|
||
age = (age&&lmos) ? (GMTDateTime()-24*3600*age) : 0;
|
||
/*
|
||
* allocate it
|
||
*/
|
||
if (!(*popDH = ZeroHandle(NuHandle(count*sizeof(POPDesc)))))
|
||
return(WarnUser(MEM_ERR,Prr=MemError()));
|
||
|
||
if (Prr=FillSizesWithList(stream,*popDH)) return(Prr);
|
||
|
||
if (popMode!=popRUIDL) lastRead = FirstUnread(stream,count)-1;
|
||
|
||
if (!(*CurPers)->noUIDL && (Prr = FillWithUidl(stream,*popDH)))
|
||
return(Prr);
|
||
|
||
ASSERT(CurResFile()==SettingsRefN);
|
||
UseResFile(SettingsRefN); // make sure!
|
||
if (oldDH = (void*)Get1Resource(CUR_POPD_TYPE,POPD_ID)) HNoPurge((Handle)oldDH);
|
||
else
|
||
{
|
||
Prr = ResError();
|
||
if (Prr==resNotFound) Prr = noErr;
|
||
if (Prr) return(WarnUser(BUILD_POPD,Prr));
|
||
}
|
||
|
||
if (LogLevel & LOG_LMOS) Log1POPD("\pBuildPOPD","\pOld",oldDH);
|
||
|
||
if ((*CurPers)->noUIDL && (Prr = FillWithTop(stream,*popDH,oldDH))) return(Prr);
|
||
|
||
for (i=0;i<count;i++) spaceNeeded += (**popDH)[i].msgSize;
|
||
plentyRoom = !RoomForMessage(spaceNeeded);
|
||
spaceNeeded = 0;
|
||
|
||
/*
|
||
* examine each of the new messages
|
||
*/
|
||
for (i=0;i<count;i++)
|
||
{
|
||
MyThreadBeginCritical();
|
||
CycleBalls();
|
||
MyThreadEndCritical();
|
||
aged = False;
|
||
|
||
new = (**popDH)[i];
|
||
|
||
/*
|
||
* have we seen it before?
|
||
*/
|
||
if (!oldDH || errNotFound==(spot=FindExistSpot(oldDH,new.uidHash)))
|
||
{
|
||
Zero(old);
|
||
old.retred = i<lastRead;
|
||
old.stubbed = i<lastRead;
|
||
|
||
#ifdef THREADING_ON
|
||
/*
|
||
* if the message is already in in.temp and we don't have a popd entry, machine probably crashed
|
||
* during download before popd could get updated. mark these messages as fetched. make an exception
|
||
* for the last message, since it could be incomplete. remove this message and re-fetch it.
|
||
*/
|
||
if (InAThread())
|
||
{
|
||
short sum;
|
||
|
||
if (tempInTocH)
|
||
{
|
||
sum = FindSumByHash (tempInTocH,new.uidHash);
|
||
if (sum != -1)
|
||
{
|
||
if (sum == ((*tempInTocH)->count - 1))
|
||
{
|
||
DeleteSum (tempInTocH, sum);
|
||
old.retred = False;
|
||
old.stubbed = False;
|
||
}
|
||
else
|
||
{
|
||
old.retred = True;
|
||
new.retred = True;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
old = (*oldDH)[spot];
|
||
new = old;
|
||
new.retr = False;
|
||
new.stub = False;
|
||
new.delete = False;
|
||
if (popMode!=popRUIDL)
|
||
{
|
||
old.stubbed = old.stubbed || i<lastRead;
|
||
old.retred = old.retred || i<lastRead;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* hard nuke?
|
||
*/
|
||
if (flags->nukeHard) new.delete = True;
|
||
|
||
/*
|
||
* just stub?
|
||
*/
|
||
else if (flags->stub) {new.head = new.stub = True;}
|
||
|
||
/*
|
||
* is this message supposed to be toast?
|
||
*/
|
||
else if (old.deleted) new.delete = True; /* yes. Just kill it. */
|
||
else
|
||
{
|
||
/*
|
||
* ok, message is not just to be killed. Make a rough pass on whether
|
||
* or not it should be fetched or deleted. We'll refine our decision
|
||
* later
|
||
*/
|
||
|
||
/*
|
||
* check lists
|
||
*/
|
||
onFetch = IdIsOnPOPD(CUR_POPD_TYPE,FETCH_ID,new.uidHash);
|
||
onDelete = IdIsOnPOPD(CUR_POPD_TYPE,DELETE_ID,new.uidHash);
|
||
|
||
/*
|
||
* is there room?
|
||
*/
|
||
room = anyRoom && (plentyRoom || !RoomForMessage(spaceNeeded+new.msgSize));
|
||
if (room) new.big2 = False; /* clear old flag */
|
||
|
||
/*
|
||
* Should we fetch it? (ROUGH)
|
||
*/
|
||
if (!old.retred) new.retr = flags->check;
|
||
|
||
/*
|
||
* should we delete it? (ROUGH)
|
||
*/
|
||
aged = age && new.receivedGMT && age>new.receivedGMT;
|
||
new.delete = !lmos ||
|
||
flags->servDel && onDelete ||/* user told us to */
|
||
aged && new.retred;
|
||
|
||
/*
|
||
* ok, now we fine-tune the process. Check that there is room for
|
||
* the message, and that we don't want to skip it because of the
|
||
* skip big messages preference.
|
||
*/
|
||
|
||
/*
|
||
* if we're skipping big messages, see if we want to skip this one
|
||
*/
|
||
if (!sbm) new.skip = False; /* clear old flag */
|
||
else if (new.msgSize>skipSize && skipSize > 0) new.skip = True;
|
||
else new.skip = False;
|
||
|
||
if (new.retr && new.skip && !onFetch)
|
||
{
|
||
new.retr = False;
|
||
if (!old.stubbed)
|
||
{
|
||
new.stub = True;
|
||
new.delete = False; /* don't delete, because only fetching a stub */
|
||
}
|
||
}
|
||
|
||
/*
|
||
* under what circumstances do we not fetch when we already have a stub?
|
||
* We get stubs from:
|
||
* Fetching headers - such stubs are never expanded except manually
|
||
* Skip big messages - if the pref changes, the messages will be fetched
|
||
* Not enough room - if the user makes room, the messages will be fetched
|
||
*/
|
||
if (old.stubbed && new.retr)
|
||
{
|
||
if (new.head || new.error) new.retr = False; // if the user fetched headers, must request fetch
|
||
}
|
||
|
||
new.retr = new.retr || flags->servFetch && onFetch;
|
||
|
||
/*
|
||
* If we want the whole message, do we have room?
|
||
*/
|
||
if (new.retr && !room)
|
||
{
|
||
new.big2 = True;
|
||
new.delete = False; /* since we want to fetch it but can't, don't delete it */
|
||
new.retr = False;
|
||
if (!old.stubbed) new.stub = True; /* shd we fetch the stub? */
|
||
}
|
||
}
|
||
|
||
/*
|
||
* maybe we don't even have room for the stub?
|
||
*/
|
||
if (!anyRoom && new.stub)
|
||
{
|
||
new.stub = False; /* no room at the inn */
|
||
new.delete = False; /* again, since we want it but can't get it, don't delete it */
|
||
}
|
||
|
||
/*
|
||
* ok, we did it! Now, record how much disk this message will use.
|
||
*/
|
||
if (new.stub) spaceNeeded += 3 K; /* arbitrary conservative guess at stub size */
|
||
else if (new.retr) spaceNeeded += new.msgSize;
|
||
anyRoom = anyRoom && (plentyRoom || !RoomForMessage(spaceNeeded));
|
||
|
||
/*
|
||
* special processing
|
||
*/
|
||
if (flags->nuke) new.delete = True;
|
||
|
||
/*
|
||
* last sanity check
|
||
*/
|
||
if (!(new.retred || new.retr) && (!onDelete || onFetch) && !flags->nukeHard && !old.deleted) new.delete = False;
|
||
|
||
/*
|
||
* store it back
|
||
*/
|
||
(**popDH)[i] = new;
|
||
if (new.retr)
|
||
total+=new.msgSize;
|
||
else
|
||
if (new.stub)
|
||
total+=3 K;
|
||
}
|
||
ByteProgress(nil,0,total);
|
||
POPDelDup(*popDH);
|
||
|
||
if (LogLevel & LOG_LMOS) LogPOPD("\pBUILT",*popDH);
|
||
|
||
return(noErr);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* POPDelDup - delete duplicate messages before downloading
|
||
**********************************************************************/
|
||
void POPDelDup(POPDHandle popDH)
|
||
{
|
||
short i,j,n;
|
||
short killme;
|
||
|
||
n = HandleCount(popDH);
|
||
for (i=0;i<n;i++)
|
||
for (j=i+1;j<n;j++)
|
||
if ((*popDH)[i].uidHash && (*popDH)[i].uidHash==(*popDH)[j].uidHash)
|
||
{
|
||
if ((*popDH)[i].retred)
|
||
killme = j;
|
||
else if ((*popDH)[j].retred)
|
||
killme = i;
|
||
else if ((*popDH)[i].msgSize>(*popDH)[j].msgSize)
|
||
killme = j;
|
||
else
|
||
killme = i;
|
||
(*popDH)[killme].retr = (*popDH)[killme].stub = False;
|
||
(*popDH)[killme].delete = True;
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* FillWithTop - fill up the descriptor without uidl. Not fun.
|
||
************************************************************************/
|
||
OSErr FillWithTop(TransStream stream,POPDHandle new, POPDHandle old)
|
||
{
|
||
short oldCount;
|
||
short newCount = HandleCount(new);
|
||
short oldSpot, newSpot;
|
||
short oldUndelCount;
|
||
short oldUndelSpot;
|
||
|
||
if (old)
|
||
{
|
||
oldCount = HandleCount(old);
|
||
/*
|
||
* find last undeleted message in old descriptor, and count them
|
||
*/
|
||
oldUndelCount = 0;
|
||
for (oldSpot=0;oldSpot<oldCount;oldSpot++)
|
||
{
|
||
if (!(*old)[oldSpot].deleted && (*old)[oldSpot].uidHash)
|
||
{
|
||
oldUndelSpot = oldSpot;
|
||
oldUndelCount++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* I DON'T TRUST THIS
|
||
*/
|
||
if (!oldUndelCount) return(noErr); /* we will either delete or stub these, so we can stop now. maybe */
|
||
|
||
/*
|
||
* Now, check to see if it's in the same spot on the server
|
||
*/
|
||
if (oldUndelCount && newCount>=oldUndelCount)
|
||
{
|
||
Prr = FillPOPDFromServer(stream,new,oldUndelCount-1);
|
||
if (Prr) return(Prr);
|
||
|
||
if (MatchPOPD(old,oldUndelSpot,(*new)[oldUndelCount-1].uidHash))
|
||
{
|
||
/*
|
||
* ok, now we make the big leap of faith. Assume that since we found
|
||
* this one where we expected it, the others will be there, too
|
||
*/
|
||
ComposeLogS(LOG_LMOS,nil,"\pCopy old to %d",oldUndelCount);
|
||
for (newSpot=oldSpot=0;oldSpot<oldCount;oldSpot++)
|
||
{
|
||
if (!(*old)[oldSpot].deleted)
|
||
{
|
||
(*new)[newSpot].uidHash = (*old)[oldSpot].uidHash;
|
||
(*new)[newSpot++].receivedGMT = (*old)[oldSpot].receivedGMT;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* NO TRUST HERE
|
||
*/
|
||
return(noErr); /* we can stop now, we'll fetch or stub the rest. maybe */
|
||
}
|
||
}
|
||
}
|
||
else
|
||
/*
|
||
* DONT TRUST
|
||
*/
|
||
return(noErr); /* no old one, so don't need to top. maybe */
|
||
|
||
/*
|
||
* ok, so much for clever. now, we act with force
|
||
*/
|
||
for (newSpot=0;!Prr && newSpot<newCount;newSpot++)
|
||
Prr = FillPOPDFromServer(stream,new,newSpot);
|
||
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FillPOPDFromServer - fille the POP Descriptor block by asking the server
|
||
************************************************************************/
|
||
OSErr FillPOPDFromServer(TransStream stream,POPDHandle popDH,short spot)
|
||
{
|
||
long msgSize;
|
||
Str255 scratch;
|
||
HeaderDHandle hdh=nil;
|
||
short refN = 0;
|
||
Token822Enum tokenType;
|
||
POPDesc pd;
|
||
long size;
|
||
|
||
if ((*popDH)[spot].uidHash) {return(noErr);} /* already done */
|
||
pd = (*popDH)[spot];
|
||
|
||
ComposeRString(scratch,FIRST_UNREAD,spot+1);
|
||
ProgressMessage(kpSubTitle,scratch);
|
||
|
||
if (!(hdh=NewHeaderDesc(nil)))
|
||
return(WarnUser(MEM_ERR,Prr=MemError()));
|
||
|
||
/*
|
||
* get rest of message
|
||
*/
|
||
NumToString(spot+1,scratch);
|
||
PLCat(scratch,1);
|
||
size = sizeof(scratch);
|
||
if (Prr=POPCmdGetReply(stream,kpcTop,scratch,scratch,&size)) goto done;
|
||
|
||
/*
|
||
* read in the header
|
||
*/
|
||
tokenType = ReadHeader(stream,hdh,pd.msgSize,0,False);
|
||
if (CommandPeriod || tokenType==ErrorToken) {Prr=ErrorToken; goto done;}
|
||
|
||
if (tokenType!=EndOfMessage)
|
||
{
|
||
for (msgSize=sizeof(scratch);
|
||
!(Prr=RecvLine(stream,scratch,&msgSize)) && !POP_TERM(scratch,msgSize);
|
||
msgSize=sizeof(scratch));
|
||
}
|
||
|
||
/*
|
||
* fill the descriptor
|
||
*/
|
||
FillPOPD(&pd,hdh);
|
||
ComposeLogS(LOG_LMOS,nil,"\pFill %d: hash %x gmt %x.",spot,pd.uidHash,pd.receivedGMT);
|
||
|
||
(*popDH)[spot] = pd;
|
||
|
||
done:
|
||
ZapHeaderDesc(hdh);
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FindExistSpot - find a message already in a descriptor
|
||
************************************************************************/
|
||
short FindExistSpot(POPDHandle popDH,uLong hash)
|
||
{
|
||
short spot=0;
|
||
short count = GetHandleSize_(popDH)/sizeof(POPDesc);
|
||
|
||
for (spot=0;spot<count;spot++)
|
||
if (MatchPOPD(popDH,spot,hash)) return(spot);
|
||
return(errNotFound);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FindUndelete - find a message already in a descriptor,
|
||
* without the delete flag if possible
|
||
************************************************************************/
|
||
short FindUndelete(POPDHandle popDH,uLong gmt,uLong hash)
|
||
{
|
||
short spot;
|
||
short count = GetHandleSize_(popDH)/sizeof(POPDesc);
|
||
short found=errNotFound;
|
||
|
||
for (spot=0;spot<count;spot++)
|
||
if (MatchPOPD(popDH,spot,hash))
|
||
{
|
||
if (!(*popDH)[spot].delete) return(spot);
|
||
found = spot;
|
||
}
|
||
return(found);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* DisposePOPD - get rid of the POP descriptors
|
||
************************************************************************/
|
||
void DisposePOPD(POPDHandle *popDH)
|
||
{
|
||
short err;
|
||
Handle oldDH;
|
||
|
||
if ((*popDH))
|
||
{
|
||
if (LogLevel & LOG_LMOS) LogPOPD("\pAFTER",*popDH);
|
||
|
||
/*
|
||
* is the old one there?
|
||
*/
|
||
if (oldDH = GetResourceMainThread_(CUR_POPD_TYPE,POPD_ID))
|
||
{
|
||
HNoPurge(oldDH);
|
||
|
||
/*
|
||
* is it the same as the new one?
|
||
*/
|
||
if (GetHandleSize(oldDH)==GetHandleSize_(*popDH) &&
|
||
!memcmp(LDRef(oldDH),LDRef(*popDH),GetHandleSize(oldDH)))
|
||
{
|
||
/*
|
||
* yup. pitch the new one.
|
||
*/
|
||
ComposeLogS(LOG_LMOS,nil,"\pNo changes");
|
||
ZapHandle(*popDH);
|
||
PurgeIfClean(oldDH);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* there was no old resource, or the old one is different
|
||
* kill the old one
|
||
*/
|
||
ZapSettingsResourceMainThread_(CUR_POPD_TYPE,POPD_ID);
|
||
|
||
/*
|
||
* put in the new one
|
||
*/
|
||
err = AddMyResourceMainThread_(*popDH,CUR_POPD_TYPE,POPD_ID,"");
|
||
|
||
/*
|
||
* bad things?
|
||
*/
|
||
if (err)
|
||
{
|
||
WarnUser(SAVE_POPD,err);
|
||
ZapHandle(*popDH);
|
||
}
|
||
else
|
||
{
|
||
#ifdef THREADING_ON
|
||
MyUpdateResFile(GetMainGlobalSettingsRefN ());
|
||
#else
|
||
MyUpdateResFile(SettingsRefN);
|
||
#endif
|
||
FixServers = True;
|
||
|
||
/*
|
||
* done with the handle
|
||
*/
|
||
*popDH = nil;
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef TWO
|
||
/**********************************************************************
|
||
* FixMessServerAreas - fix the server displays of message windows
|
||
**********************************************************************/
|
||
void FixMessServerAreas(void)
|
||
{
|
||
WindowPtr winWP;
|
||
MyWindowPtr win;
|
||
|
||
for (winWP=FrontWindow_();winWP;winWP=GetNextWindow(winWP))
|
||
if (IsWindowVisible(winWP))
|
||
if (win = GetWindowMyWindowPtr (winWP))
|
||
Fix1MessServerArea (win);
|
||
}
|
||
#endif
|
||
|
||
/************************************************************************
|
||
* HeaderMsgId - nab the message-id
|
||
************************************************************************/
|
||
PStr HeaderMsgId(HeaderDHandle hdh,PStr msgId)
|
||
{
|
||
Str255 scratch;
|
||
|
||
if (!AAFetchResData((*hdh)->funFields,InterestHeadStrn+hMessageId,scratch))
|
||
PCopyTrim(msgId,scratch,128);
|
||
else
|
||
*msgId = 0;
|
||
return(msgId);
|
||
}
|
||
|
||
/************************************************************************
|
||
* FakeMIDHash - fake a message-id for something that doesn't have one
|
||
************************************************************************/
|
||
uLong FakeMIDHash(HeaderDHandle hdh)
|
||
{
|
||
Str255 scratch;
|
||
|
||
/*
|
||
* no message-id in this message. Look for other headers of interest,
|
||
* and add them to our string one at a time
|
||
*/
|
||
*scratch = 0;
|
||
AAFetchResData((*hdh)->funFields,InterestHeadStrn+hReceived,scratch);
|
||
if (!*scratch)
|
||
AAFetchResData((*hdh)->funFields,InterestHeadStrn+hDate,scratch);
|
||
|
||
LDRef(hdh);
|
||
PSCat(scratch,(*hdh)->who);
|
||
PSCat(scratch,(*hdh)->subj);
|
||
UL(hdh);
|
||
|
||
return(Hash(scratch));
|
||
}
|
||
|
||
/************************************************************************
|
||
* CountFetch - count the number of messages to be fetched
|
||
************************************************************************/
|
||
short CountFetch(POPDHandle popDH)
|
||
{
|
||
short n;
|
||
short fetch = 0;
|
||
|
||
if (*popDH)
|
||
{
|
||
n = HandleCount(popDH);
|
||
while (n--)
|
||
{
|
||
if ((*popDH)[n].retr || (*popDH)[n].stub) fetch++;
|
||
}
|
||
}
|
||
return(fetch);
|
||
}
|
||
|
||
/************************************************************************
|
||
* AddIdToPOPD - add a message to a POPD list
|
||
************************************************************************/
|
||
OSErr AddIdToPOPD(OSType theType,short listId, uLong uidHash,Boolean dupOk)
|
||
{
|
||
POPDHandle resH;
|
||
short n;
|
||
short i;
|
||
POPDesc popd;
|
||
OSErr err;
|
||
|
||
if (!ValidHash(uidHash)) return(errNotFound);
|
||
resH = GetResourceMainThread_(theType,listId);
|
||
if (!resH)
|
||
{
|
||
resH = NuHandle(0);
|
||
if (resH) AddMyResourceMainThread_(resH,theType,listId,"");
|
||
else return(resNotFound);
|
||
}
|
||
|
||
HNoPurge_(resH);
|
||
|
||
n = HandleCount(resH);
|
||
for (i=0;i<n;i++)
|
||
if ((*resH)[i].uidHash==uidHash)
|
||
return(noErr);
|
||
|
||
/*
|
||
* not there. add it.
|
||
*/
|
||
Zero(popd);
|
||
popd.uidHash = uidHash;
|
||
if (err=PtrPlusHand_(&popd,resH,sizeof(popd)))
|
||
WarnUser(MEM_ERR,err);
|
||
else
|
||
ChangedResource((Handle)resH);
|
||
PurgeIfClean(resH);
|
||
return(noErr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* RemIdFromPOPD - remove a message from a POPD list
|
||
************************************************************************/
|
||
OSErr RemIdFromPOPD(OSType theType,short listId, uLong uidHash)
|
||
{
|
||
POPDHandle resH = GetResourceMainThread_(theType,listId);
|
||
short n;
|
||
short i;
|
||
OSErr err=errNotFound;
|
||
|
||
if (!ValidHash(uidHash)) return(errNotFound);
|
||
if (!resH) return(resNotFound);
|
||
HNoPurge_(resH);
|
||
|
||
n = HandleCount(resH);
|
||
for (i=0;i<n;i++)
|
||
if ((*resH)[i].uidHash==uidHash)
|
||
{
|
||
BMD((*resH)+i+1,(*resH)+i,(n-1-i)*sizeof(POPDesc));
|
||
SetHandleBig_(resH,(n-1)*sizeof(POPDesc));
|
||
ChangedResource((Handle)resH);
|
||
err = noErr;
|
||
break;
|
||
}
|
||
PurgeIfClean(resH);
|
||
return(err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* PrunePOPD - prune old stuff from fetch & delete lists
|
||
**********************************************************************/
|
||
void PrunePOPD(OSType theType,short listId, POPDHandle onServer)
|
||
{
|
||
POPDHandle resH = GetResourceMainThread_(theType,listId);
|
||
short n;
|
||
uLong uidHash;
|
||
short sCount;
|
||
short i;
|
||
|
||
if (!resH) return;
|
||
HNoPurge_(resH);
|
||
n = HandleCount(resH);
|
||
sCount = HandleCount(onServer);
|
||
while (n--)
|
||
{
|
||
uidHash = (*resH)[n].uidHash;
|
||
for (i=0;i<sCount;i++)
|
||
{
|
||
if ((*onServer)[i].uidHash==uidHash) break;
|
||
}
|
||
if (i==sCount)
|
||
{
|
||
ComposeLogS(LOG_LMOS,nil,"\pPrune %p: %d %x",listId%4==DELETE_ID%4?"\pDELETE":"\pFETCH",n,uidHash);
|
||
RemIdFromPOPD(theType,listId,uidHash);
|
||
}
|
||
}
|
||
}
|
||
|
||
/************************************************************************
|
||
* IdIsOnPOPD - is a message on a POPD list?
|
||
************************************************************************/
|
||
Boolean IdIsOnPOPD(OSType listType,short listId, uLong uidHash)
|
||
{
|
||
POPDHandle resH = GetResourceMainThread_(listType,listId);
|
||
short n;
|
||
short i;
|
||
Boolean result=False;
|
||
|
||
if (!ValidHash(uidHash)) return(False);
|
||
if (!resH) return(False);
|
||
HNoPurge_(resH);
|
||
|
||
n = HandleCount(resH);
|
||
for (i=0;i<n;i++)
|
||
if ((*resH)[i].uidHash==uidHash)
|
||
{
|
||
result = !(*resH)[i].deleted;
|
||
break;
|
||
}
|
||
PurgeIfClean(resH);
|
||
return(result);
|
||
}
|
||
|
||
|
||
/************************************************************************
|
||
* FillWithUidl - fill the descriptor using the uidl command
|
||
************************************************************************/
|
||
OSErr FillWithUidl(TransStream stream,POPDHandle popDH)
|
||
{
|
||
Str255 buffer;
|
||
long size = sizeof(buffer);
|
||
UPtr spot,end;
|
||
short msgNum;
|
||
short n = HandleCount(popDH);
|
||
uLong uidHash;
|
||
POPDHandle oldDH = GetResourceMainThread_(CUR_POPD_TYPE,POPD_ID);
|
||
short i;
|
||
|
||
for (i=n;i--;)
|
||
(*popDH)[i].uidHash = 0;
|
||
i = 0;
|
||
|
||
if (Prr=POPCmdGetReply(stream,kpcUidl,nil,buffer,&size))
|
||
return(Prr);
|
||
|
||
if (*buffer=='-')
|
||
{
|
||
buffer[size] = 0;
|
||
ComposeLogS(LOG_LMOS,nil,"\pUIDL err: %s",buffer);
|
||
(*CurPers)->noUIDL = True;
|
||
return(noErr);
|
||
}
|
||
|
||
// if (n>100) ByteProgress(nil,0,n);
|
||
|
||
for (size=sizeof(buffer);
|
||
!(Prr=RecvLine(stream,buffer,&size)) && !POP_TERM(buffer,size);
|
||
size=sizeof(buffer))
|
||
{
|
||
MiniEvents(); if (CommandPeriod) break; CycleBalls();
|
||
if (*buffer!=' ' && *buffer!='\t')
|
||
{
|
||
msgNum = Atoi(buffer);
|
||
if (msgNum<1 || msgNum>n) continue;
|
||
|
||
// if (n>100) ByteProgress(nil,-1,0);
|
||
|
||
#ifdef DEBUG
|
||
if (RunType!=Production && ++i!=msgNum) AlertStr(OK_ALRT,Stop,"\pBad UIDL!");
|
||
#endif
|
||
|
||
end = buffer+size;
|
||
while (end[-1]<=' ') end--;
|
||
for (spot=buffer;spot<end && *spot==' ';spot++);
|
||
while(spot<end && *spot!=' ') spot++;
|
||
while(spot<end && *spot==' ') spot++;
|
||
if (spot<end)
|
||
{
|
||
spot[-1] = end-spot;
|
||
uidHash = Hash(spot-1);
|
||
(*popDH)[msgNum-1].uidHash = uidHash;
|
||
(*popDH)[msgNum-1].receivedGMT = GMTDateTime();
|
||
ComposeLogS(LOG_LMOS,nil,"\pUIDL %d <20>%p<> %x",msgNum,spot-1,uidHash);
|
||
}
|
||
}
|
||
}
|
||
// if (n > 100 && !Prr) ByteProgress(nil,1,1);
|
||
|
||
for (i=0;i<n;i++)
|
||
if ((*popDH)[i].uidHash==0)
|
||
FillPOPDFromServer(stream,popDH,i);
|
||
|
||
Progress(NoBar,0,nil,nil,nil);
|
||
|
||
if (CommandPeriod && !Prr) Prr = userCancelled;
|
||
return(Prr);
|
||
}
|
||
|
||
/************************************************************************
|
||
* LogPOPD - write the POPD's to the log file
|
||
************************************************************************/
|
||
void LogPOPD(PStr intro,POPDHandle newDH)
|
||
{
|
||
Log1POPD(intro,"\pFetch",(void*)GetResource(CUR_POPD_TYPE,FETCH_ID));
|
||
Log1POPD(intro,"\pDelete",(void*)GetResource(CUR_POPD_TYPE,DELETE_ID));
|
||
Log1POPD(intro,"\pOld",(void*)GetResource(CUR_POPD_TYPE,POPD_ID));
|
||
Log(LOG_LMOS,"\p---");
|
||
Log1POPD(intro,"\pNew",newDH);
|
||
}
|
||
|
||
/************************************************************************
|
||
* Log1POPD - write a POPD to the log file
|
||
************************************************************************/
|
||
void Log1POPD(PStr intro, PStr which, POPDHandle popDH)
|
||
{
|
||
short i;
|
||
short count;
|
||
|
||
if (popDH==nil || GetHandleSize_(popDH)==0)
|
||
ComposeLogS(LOG_LMOS,nil,"\p%p: %p: <empty>",intro,which);
|
||
else
|
||
{
|
||
HNoPurge_(popDH);
|
||
count = GetHandleSize_(popDH)/sizeof(POPDesc);
|
||
for (i=0;i<count;i++)
|
||
{
|
||
CycleBalls();
|
||
ComposeLogS(LOG_LMOS,nil,"\p%p: %p: %d hash %x gmt %x %c%c %c%c %c%c %c%c",
|
||
intro,which,i+1,(*popDH)[i].uidHash,(*popDH)[i].receivedGMT,
|
||
(*popDH)[i].retr ? 'F' : 'f',(*popDH)[i].retred ? 'F' : 'f',
|
||
(*popDH)[i].stub ? 'S' : 's',(*popDH)[i].stubbed ? 'S' : 's',
|
||
(*popDH)[i].big2 ? 'T' : 't',(*popDH)[i].skip ? 'K' : 'k',
|
||
(*popDH)[i].delete ? 'D' : 'd',(*popDH)[i].deleted ? 'D' : 'd'
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**********************************************************************
|
||
* KerbDestroy - destroy the user's current ticket
|
||
**********************************************************************/
|
||
OSErr KerbDestroy(void)
|
||
{
|
||
if (gPOPKerbInited)
|
||
{
|
||
KClientDisposeSessionCompat(&gSession);
|
||
gPOPKerbInited = false;
|
||
}
|
||
return(KClientLogoutCompat());
|
||
}
|
||
|
||
/**********************************************************************
|
||
* KerbDestroyUser - destroy the username
|
||
**********************************************************************/
|
||
OSErr KerbDestroyUser(void)
|
||
{
|
||
OSErr err = noErr;
|
||
|
||
/* KerbDestroy does everything that it needs to */
|
||
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
*
|
||
**********************************************************************/
|
||
OSErr KerbUsername(PStr name)
|
||
{
|
||
OSErr err;
|
||
UPtr atSign;
|
||
|
||
*name = 0;
|
||
err = KClientGetUserNameDeprecated(name);
|
||
|
||
if (err==cKrbNotLoggedIn)
|
||
{
|
||
/*
|
||
* log kerberos in
|
||
*/
|
||
|
||
if (err = KClientNewSessionCompat(&gSession, 1/*nLocalAddress*/, GetRLong(POP_PORT) /*inLocalPort*/, 2 /*inRemoteAddress*/, GetRLong(POP_PORT) /*inRemotePort*/)) return (err);
|
||
if (err = KClientLoginCompat(&gSession, &gPrivateKey)) return(err);
|
||
|
||
/*
|
||
* get username again
|
||
*/
|
||
if (noErr!=(err=KClientGetUserNameDeprecated(name))) return(err);
|
||
}
|
||
|
||
/*
|
||
* convert to pascal, trim realm
|
||
*/
|
||
if (!err)
|
||
{
|
||
c2pstr(name);
|
||
if (atSign=PIndex(name,'@'))
|
||
name[0] = atSign-name-1;
|
||
}
|
||
|
||
return(err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* KerbGetTicket - get our ticket
|
||
**********************************************************************/
|
||
OSErr KerbGetTicket(PStr serviceName,PStr inHost,PStr realm,PStr version,UHandle *ticket)
|
||
{
|
||
Str63 fmt;
|
||
Str255 fullName;
|
||
Str255 scratch;
|
||
Str255 host;
|
||
Str255 shortHost;
|
||
OSErr err;
|
||
UPtr spot;
|
||
struct hostInfo *hip, hi;
|
||
unsigned long bufLen;
|
||
|
||
// create a new session if we must
|
||
err = InitKerberos();
|
||
if (err != noErr) return (err);
|
||
|
||
// get the user name from KCLient
|
||
err = KerbUsername(scratch);
|
||
if (err != noErr) return (err);
|
||
|
||
/*
|
||
* 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,shortHost,&spot,".");
|
||
EscapeChars(host,GetRString(fmt,KERBEROS_ESCAPES));
|
||
EscapeChars(serviceName,GetRString(fmt,KERBEROS_ESCAPES));
|
||
GetRString(fmt,KERBEROS_SERVICE_FMT);
|
||
utl_PlugParams(fmt,fullName,serviceName,host,realm,shortHost);
|
||
ProgressMessage(kpMessage,ComposeRString(scratch,KERBEROS_TICK_FMT,fullName));
|
||
|
||
// Null terminate these, KCLient expects C-Strings
|
||
version[*version+1] = 0;
|
||
fullName[*fullName+1] = 0;
|
||
|
||
bufLen = GetRLong(KERBEROS_BSIZE);
|
||
if (!(*ticket = NuHandle(bufLen))) return(MemError());
|
||
|
||
/*
|
||
* call the driver
|
||
*/
|
||
|
||
LDRef(*ticket);
|
||
err = KClientMakeSendAuthCompat(&gSession, fullName+1, **ticket, &bufLen, GetRLong(KERBEROS_CHECKSUM), version+1);
|
||
UL(*ticket);
|
||
|
||
|
||
/*
|
||
* adjust the ticket size
|
||
*/
|
||
if (err) ZapHandle(*ticket);
|
||
else SetHandleSize(*ticket,bufLen);
|
||
|
||
/*
|
||
* Cleanup
|
||
*/
|
||
|
||
return(err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* InitKerberos - get ready to start making Kerberos calls
|
||
**********************************************************************/
|
||
OSErr InitKerberos()
|
||
{
|
||
OSErr err = noErr;
|
||
|
||
if (!gPOPKerbInited)
|
||
{
|
||
// make sure Kerberos is present before we start calling it.
|
||
if ((Ptr) (KClientNewSessionCompat) == (Ptr) (kUnresolvedCFragSymbolAddress))
|
||
{
|
||
// Kerberos is not installed. Warn and crap out.
|
||
WarnUser(NO_KERBEROS,err);
|
||
err = fnfErr;
|
||
}
|
||
else
|
||
{
|
||
// Kerberos is here. Try to start a new session.
|
||
err = KClientNewSessionCompat(&gSession, 1, GetRLong(POP_PORT), 2, GetRLong(POP_PORT));
|
||
if (err != noErr) return (err);
|
||
else gPOPKerbInited = true;
|
||
}
|
||
}
|
||
return (err);
|
||
}
|
||
|
||
/**********************************************************************
|
||
* SendPOPTicket - send a ticket to the Pop server
|
||
**********************************************************************/
|
||
OSErr SendPOPTicket(TransStream stream)
|
||
{
|
||
Str63 popName,host,realm,version;
|
||
Handle ticket=nil;
|
||
OSErr err;
|
||
|
||
if (err=GetPOPInfo(popName,host)) return(err);
|
||
GetRString(popName,KERBEROS_POP_SERVICE);
|
||
GetPref(realm,PREF_REALM);
|
||
GetRString(version,KERBEROS_VERSION);
|
||
|
||
if (err=KerbGetTicket(popName,host,realm,version,&ticket)) return(WarnUser(NO_KERBEROS,err));
|
||
|
||
err=SendTrans(stream,LDRef(ticket),GetHandleSize(ticket),nil);
|
||
ZapHandle(ticket);
|
||
return(err);
|
||
}
|