/* 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 "tcp.h"
#define FILE_NUM 37
/* Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */
/************************************************************************
 * functions for i/o over a pseudo-telnet MacTcp stream
 * these functions are oriented toward CONVENIENCE, not performance
 ************************************************************************/

#pragma segment TcpTrans

/************************************************************************
 * private functions
 ************************************************************************/
#define TcpTrouble(stream,which,err) TT(stream,which,err,FNAME_STRN+FILE_NUM,__LINE__)
int TT(TransStream stream,int which, int theErr, int file, int line);
uLong RandomAddr(uLong *addrs);
void NoteAddrGoodness(struct hostInfo *hip,uLong addr,short err);
pascal void BindDone(struct hostInfo *hostInfoPtr, char *userData);
pascal void TcpASR(StreamPtr tcpStream, unsigned short eventCode,UPtr userDataPtr, unsigned short terminReason, struct ICMPReport *icmpMsg);

/**********************************************************************
 * ConnectTransLo - connect to a host
 **********************************************************************/
OSErr ConnectTrans(TransStream stream, UPtr serverName, long port, Boolean silently,uLong timeout)
{
	Str255 realHost;
	
	PCopy(realHost,serverName);
	
	SplitPort(realHost,&port);

	return ConnectTransLo(stream,realHost,port,silently,timeout);
}

/**********************************************************************
 * SplitPort - Split host:port into components
 *  returns true if port found
 **********************************************************************/
Boolean SplitPort(PStr host,long *port)
{
	UPtr colon;
	long localPort;
	
	if (colon=PIndex(host,':'))
	{
		*colon = *host - (colon-host);
		*host = colon-host-1;
		StringToNum(colon,&localPort);
		ASSERT(localPort>0);
		if (localPort>0 && port) *port = localPort;
		return true;
	}
	
	return false;
}

/**********************************************************************
 * RandomAddr - pick a random address out of a set of addresses
 **********************************************************************/
uLong RandomAddr(uLong *addrs)
{
	short count;
	if (!PrefIsSet(PREF_DNS_BALANCE)) return(addrs[0]);
	
	for (count=NUM_ALT_ADDRS;count;count--) if (addrs[count-1]) break;
	return(addrs[count<2?0:TickCount()%count]);
}

/**********************************************************************
 * NoteAddrGoodness - remove an address or all except an address
 **********************************************************************/
void NoteAddrGoodness(struct hostInfo *hip,uLong addr,short err)
{
	short count,i;

	if (!PrefIsSet(PREF_DNS_BALANCE)) return;
	
	for (count=NUM_ALT_ADDRS;count;count--) if (hip->addr[count-1]==addr) break;

	if (err)
	{
		/* this addr didn't work.  remove it */
		for (i=count;i<NUM_ALT_ADDRS;i++) hip->addr[i-1] = hip->addr[i];
		hip->addr[NUM_ALT_ADDRS-1] = 0;
		if (count==1) *hip->cname = 0;	/* if used last addr, kill the name to force another lookup */
	}
	else
	{
		/* mark the OTHERS as dead. */
		WriteZero(hip->addr,sizeof(uLong)*NUM_ALT_ADDRS);
		hip->addr[0] = addr;
	}
}

#pragma segment Main
/************************************************************************
 * BindDone - report that the resolver has done its duty
 ************************************************************************/
pascal void BindDone(struct hostInfo *hostInfoPtr, char *userData)
{
	*(short *)userData = hostInfoPtr->rtnCode;
	return;
}
#pragma segment TcpTrans

/************************************************************************
 * TcpTrouble - report an error with TCP and break the connection
 ************************************************************************/
int TT(TransStream stream, int which, int theErr, int file, int line)
{	
	if (stream==0 || (!stream->BeSilent && (!CommandPeriod || stream->Opening&&!PrefIsSet(PREF_OFFLINE)&&!PrefIsSet(PREF_NO_OFF_OFFER))))
	{
		Str255 message;
		Str255 tcpMessage;
		Str63 debugStr;
		Str31 rawNumber;
		short realSettingsRef = SettingsRefN;

		NumToString(theErr,rawNumber);
		
		SettingsRefN = GetMainGlobalSettingsRefN();
		GetRString(message, which);
		if (-23000>=theErr && theErr >=-23048)
			GetRString(tcpMessage,MACTCP_ERR_STRN-22999-theErr);
		else if (2<=theErr && theErr<=9)
			GetRString(tcpMessage,MACTCP_ERR_STRN+theErr+(23048-23000));
		else
			*tcpMessage = 0;
		ComposeRString(debugStr,FILE_LINE_FMT,file,line);
		SettingsRefN = realSettingsRef;


		MyParamText(message,rawNumber,tcpMessage,debugStr);
		if (stream==0 || stream->Opening)
		{
			if (2==ReallyDoAnAlert(OPEN_ERR_ALRT,Caution))
				SetPref(PREF_OFFLINE,YesStr);
		}
		else ReallyDoAnAlert(BIG_OK_ALRT,Caution);
	}
	return(stream ? (stream->streamErr=theErr) : theErr);
}

/************************************************************************
 * TcpASR - asynchronous notification routine
 ************************************************************************/
pascal void TcpASR(StreamPtr tcpStream, unsigned short eventCode,
	UPtr userDataPtr, unsigned short terminReason, struct ICMPReport *icmpMsg)
{
//#pragma unused(userDataPtr)
	if (tcpStream==((TransStreamPtr)userDataPtr)->TcpStream)
	{
		if (eventCode==TCPDataArrival) ((TransStreamPtr)userDataPtr)->CharsAvail = 1;
		else if (eventCode==TCPICMPReceived)
		{
			ICMPAvail = 1;
			ICMPMessage = *icmpMsg;
		}
		else if (eventCode==TCPTerminate)
		{
			WhyTCPTerminated = terminReason;
		}
	}
}


#pragma segment TcpTrans
/*********************************************************************************
 * Eudora might should take advantage of native Open Transport TCP/IP when it's 
 * installed.  The following functions implement the same as the MacTCP ones above,
 * but employ OT.  There's also some PPP stuff here, too.
 *********************************************************************************/
 
OSErr pppErr = noErr;
MyOTPPPInfoStruct	MyOTPPPInfo;
unsigned long connectionSelection = kOtherSelected;
long oldDlogState = 0;
Boolean needPPPConnection = false;	//used to determine when PPP is down but should be up.
Boolean OTinitialized = false;
Boolean dialingThePhone = false;			//set to true after we've dialed the phone.
Boolean gUpdateTPWindow = false;	// set to true when the connection method changes to invalidate the TP window

//setup
Boolean HasOTInetLib(void);
Boolean OTSupported(void);
OSErr OpenOTInternetServices(MyOTInetSvcInfo *myOTInfo);
pascal void MyOTNotifyProc(MyOTInetSvcInfo *info, OTEventCode theEvent, OTResult theResult, void *theParam);
OSErr CreateOTStream(TransStream stream);
OSErr OTTCPOpen(TransStream stream, InetHost tryAddr, InetPort port,uLong timeout);
void EnqueueMyTStream(MyOTTCPStreamPtr myStream);
void DestroyMyTStream(MyOTTCPStreamPtr myStream);

//communication
OSErr OTGetHostByName(InetDomainName hostName, InetHostInfo *hostInfoPtr);
OSErr OTGetHostByAddr(InetHost addr, InetHostInfo *domainNamePtr);
InetHost OTRandomAddr(InetHostInfo *host);
OSErr OTGetDomainMX(InetDomainName hostName, InetMailExchange *MXPtr, short *numMX);
void GetPreferredMX(InetDomainName preferred, InetMailExchange *MXPtr, short numMX);
short OTWaitForChars(TransStream stream, long timeout, UPtr line, long *size, OTResult *otResult);
pascal void MyOTStreamNotifyProc (MyOTTCPStream *myStream, OTEventCode code, OTResult theResult, void *theParam);
short SpinOnWithConnectionCheck(short *rtnCodeAddr,long maxTicks,Boolean allowCancel,Boolean forever);

//error reporting
OSErr OTTE(TransStream stream, OSErr generalError, OSErr specficError, short file, short line);
#define OTTCPError(stream,generalError,specficError) OTTE(stream,generalError,specficError,FNAME_STRN+FILE_NUM,__LINE__)
#define IsMyOTError(x) (x >= errOTInitFailed && x <= errMyLastOTErr)
#define IsOTPPPError(x) (x <= kCCLErrorStart && x >= kCCLErrorEnd)

//PPP functions
OSErr OTVerifyOpen(TransStream stream);
OSErr OTPPPConnect(Boolean *attemptedConnection);
OSErr GetPPPConnectionState(EndpointRef endPoint, unsigned long *PPPState);
pascal void MyPPPEndpointNotifier(void *context, OTEventCode code, OTResult result, void *cookie);
OSErr TurnOnPPPConnectionDialog(EndpointRef endPoint, Boolean on);
OSErr ResetPPPConnectionDialog(EndpointRef endPoint);
OSErr WaitForOTPPPDisconnect(Boolean showStatus);
OTResult NewPPPControlEndpoint(void);
OSErr GetCurrentUInt32Option(EndpointRef endPoint, OTXTIName theOption, UInt32 *value);
OSErr SetCurrentUInt32Option(EndpointRef endPoint, OTXTIName theOption, UInt32 value);
OSErr OTPPPDialingInformation(Boolean *redial, unsigned long *numRedials, unsigned long *delay);
Boolean SearchDirectoryForFile(CInfoPBRec *info, long dirToSearch, OSType type, OSType creator);
void UpdateCachedConnectionMethodInfo(void);

/*********************************************************************************
 * OTInitOpenTransport - initialize open transport if present.
 *********************************************************************************/
void OTInitOpenTransport(void)
{	
	//if we have OT && TCP, then try to initialize it
	if (OTIs)	
	{
		if (!HasOTInetLib())
		{
		 	gUseOT = false;
		 	
		 	// Only warn the user if we know OT works on this machine
		 	if (OTSupported()) OTTCPError(NULL,errOTInitFailed,errOTMissingLib);
		}
		else 
		{
			gUseOT = true;
		
			//Initialize if we haven't already.
			if (!OTinitialized)
			{	
				OSStatus osStatus;	
					
				if ((osStatus = InitOpenTransportInContext(kInitOTForApplicationMask,nil)) != kOTNoError)
				{
					gUseOT = false;
					OTTCPError(NULL,errOTInitFailed,osStatus);
				}
				else	//Open Transport is here.  
				{	
					//See if RemoteAcess/OT/PPP is present while we're here.
					long result;
					OSErr err = Gestalt(gestaltOpenTptRemoteAccess, &result);
					
					if ((err == noErr) 
					 && (result & (1 << gestaltOpenTptRemoteAccessPresent)) 
					 && (result & (1 << gestaltOpenTptPPPPresent)))
					{
						gHasOTPPP = true;
					}
					else 
					{
						gHasOTPPP = false;
					}

#ifdef USE_NETWORK_SETUP	
					// check for the network setup library as well ...
					UseNetworkSetup();
#endif	//UseNetworkSetup

					
					OTinitialized = true;		//remember that we started OT.
				}
			}
		}
	}
}

/*********************************************************************************
 * HasOTInetLib - (CFM Only) returns true if the OpenTptInetLib functions are present.
 *********************************************************************************/
Boolean HasOTInetLib(void)
{
	Boolean HasOTInetLib = true;
	
#if TARGET_RT_MAC_CFM
  HasOTInetLib = (long)(&OTInetGetInterfaceInfo) != kUnresolvedCFragSymbolAddress
  	&& (long)(&OTInetStringToAddress) != kUnresolvedCFragSymbolAddress
  	&& (long)(&OTInetAddressToName) != kUnresolvedCFragSymbolAddress
  	&& (long)(&OTInetMailExchange) != kUnresolvedCFragSymbolAddress; 	
#endif	//TARGET_RT_MAC_CFM

	return(HasOTInetLib);
}

/*********************************************************************************
 * OTSupported - (CFM Only) returns true if OT can be used on this 68K machine
 *********************************************************************************/
Boolean OTSupported(void)
{
	Boolean OTSupported = true;
 	long result;

#if TARGET_RT_MAC_CFM
 	//	OT can't be used if this is a 68K machine AND an OT version less than 1.2 is installed.
 	OTSupported = false;
	if (Gestalt(gestaltOpenTptVersions, &result) == noErr)
	{
		if ((result & 0xFFFF0000) >= 0x01200000) OTSupported = true;
	}
#endif	//TARGET_RT_MAC_CFM

	return(OTSupported);
}

/*********************************************************************************
 * OTCloseOpenTransport - cleanup after Open Transport has been used
 *********************************************************************************/
void OTCleanUpAfterOpenTransport(void)
{
	if (OTinitialized)
	{			
		//shut down OT/PPP if appropriate ...
		if (gHasOTPPP)
			if (MyOTPPPInfo.weConnectedPPP == true) 
				OTPPPDisconnect(false, true);
			
		CloseOpenTransportInContext(nil);
	}
}

/*********************************************************************************
 * OTTCPConnectTrans - connect to the remote host.	This version uses OT TCP.
 *********************************************************************************/
OSErr OTTCPConnectTrans(TransStream stream, UPtr serverName, long port,Boolean silently,uLong timeout)
{
	InetHostInfo		hostInfo;
	Str255				scratch;
	InetMailExchange 	MXRecords[NUM_MX];
	short				numMX = NUM_MX;
	InetDomainName		hostName;
	Boolean				useMX = PrefIsSet(PREF_IGNORE_MX) && (port == GetRLong(SMTP_PORT)) && GetOSVersion()<0x1030;
	InetHost			tryAddr;
	long				receiveBufferSize = 0;
	OSErr				err = noErr;
							
#ifdef DEBUG
	if (BUG12) port += 10000;
#endif

	ASSERT(stream);
	ASSERT(stream->OTTCPStream == 0);
	
	stream->streamErr = noErr;
	stream->Opening = true;
	stream->BeSilent = silently;
	
	//allocate memory for a new connection
	stream->OTTCPStream = New(MyOTTCPStream);
	if (stream->OTTCPStream != 0)
	{	
		WriteZero(stream->OTTCPStream,sizeof(MyOTTCPStream));
	
		ProgressMessageR(kpSubTitle,WHO_AM_I);
		//Make sure TCP is up and going.  Connect with OTPPP or something at this point.
		if ((err=OTVerifyOpen(stream)) == noErr)
		{			
			gActiveConnections++;	// a TCP connection has been established
				
			PCat(GetRString(scratch, DNR_LOOKUP), serverName);
			ProgressMessage(kpSubTitle, scratch);
			
			strncpy(hostName,serverName+1,(*serverName > kMaxHostNameLen? kMaxHostNameLen : *serverName));
			hostName[*serverName] = 0;
			
			//MX allows us to pick the best address to send mail to.
			if (useMX)
			{
				if ((err = OTGetDomainMX(hostName, MXRecords, &numMX)) != noErr) useMX = false;
				else GetPreferredMX(hostName, MXRecords, numMX);
			}
		
			if (err != userCancelled)
			{
				//Lookup the host by its name
				if ((err = OTGetHostByName(hostName, &hostInfo)) == noErr)
				{
					tryAddr = OTRandomAddr(&hostInfo);
					
					//OTGetHostByName succeeded.  Create the myStream structure, and allocate a buffer
					ProgressMessageR(kpSubTitle,HOUSEKEEPING);
					if ((err = CreateOTStream(stream)) == noErr)
					{
						// allocate a buffer for tcp, and create the stream
						receiveBufferSize = GetRLong(RCV_BUFFER_SIZE);
						if ((stream->RcvBuffer=NuHTempOK(receiveBufferSize))!=nil)
						{
							//Create stream succeeded.  Now try opening the connection.
							ComposeRString(scratch,CNXN_OPENING,serverName,tryAddr);
							ProgressMessageR(kpSubTitle,PREPARING_CONNECTION);
							ProgressMessage(kpMessage,scratch);
							
							// log the connection we're about to make
							ComposeLogS(LOG_PROTO,nil,"\pConnecting to %i:%d",tryAddr,port);
								
							if ((err = OTTCPOpen(stream, tryAddr, port, timeout)) == noErr)
							{
								//open succeeded.
								stream->RcvSpot = -1;
								ComposeLogS(LOG_PROTO,nil,"\pConnected to %i:%d",tryAddr,port);
							}
							else if (err != userCancelled) 
							{
								OTTCPError(stream,errOpenStream, stream->streamErr);
								ComposeLogS(LOG_PROTO,nil,"\pConnection to %i:%d failed.  Error %d",tryAddr,port,err);
							}
						}
						else WarnUser(MEM_ERR, stream->streamErr);	  // Memory error while creating buffer
					}
					else if (err != userCancelled) OTTCPError(stream,errCreateStream, stream->streamErr=err);
				}
				else
				{
					if (err != userCancelled) OTTCPError(stream,errDNR,stream->streamErr=err);
//					else OTTCPError(stream,errUserCancelledConnection,stream->streamErr=err);

					// Log the DNS Error
					ComposeLogS(LOG_PROTO,nil,"\pConnection failed.  DNS error %d", err);
				}
			}
		}
		else 
		{
			if (stream->streamErr == -7109) stream->streamErr = userCancelled;
			if (stream->streamErr != userCancelled) OTTCPError(stream,errPPPConnect, stream->streamErr);	
		}		
	}
	else WarnUser(OT_CON_MEM_ERR, stream->streamErr);	
		
	stream->Opening = false;	
	return ((OSErr)stream->streamErr);
}

#define	SWING_THE_THING	60
/*********************************************************************************
 * OTTCPSendTrans - send some text to the remote host.	This version uses OT TCP.
 *********************************************************************************/
OSErr OTTCPSendTrans(TransStream stream, UPtr text,long size, ...)
{
	OTResult 	result = noErr;
	va_list 	extra_buffers;
	UPtr		curBuf;
	long		curSize = 0;
	long		last = TickCount();
	long		now = 0;
	Boolean slow = False;
	
	ASSERT(stream);
	ASSERT(stream->OTTCPStream);
	
	stream->streamErr = noErr;
	
	if (CommandPeriod) return(userCancelled);
			
	if (size==0) return(noErr); 	// allow vacuous sends
	
	va_start(extra_buffers,size);

	//We'll send the text buffer first.
	curBuf = text;
	curSize = size;
	do
	{	
		//send the current buffer.
		while (curSize > 0)
		{		
			now = TickCount();
			result = OTSnd(stream->OTTCPStream->ref, curBuf, curSize, 0);
			if (result >= 0) 
			{
				//log what we sent
				if (LogLevel&LOG_TRANS && !stream->streamErr && result) CarefulLog(LOG_TRANS,LOG_SENT,curBuf,result);
				CycleBalls();
				curSize -= result;
				curBuf += result;
				last = now;
				if (IsSendAudit(stream)) stream->bytesTransferred += result;
			} 
			else 		//no data was sent
			{
				if (result != kOTFlowErr) 		//something happened to the connection.  Stop the transfer
				{
					if (stream->OTTCPStream->otherSideClosed)
					{
						stream->streamErr = result = errLostConnection;
					}
					else stream->streamErr = errMiscSend;
						
					break;
				}
				else													//just some flow control issue or something.  Swing the thing, and check for a command-.
				{
					// check to see if we need ppp, but the connection is down
					if (needPPPConnection && MyOTPPPInfo.state != kPPPUp) return(stream->streamErr = result = errLostConnection);
				}
			}
		
			if (slow || (now - last) > SWING_THE_THING)
			{
				if (slow) YieldTicks = 0;
				MiniEvents();
				if (CommandPeriod) return(stream->streamErr = userCancelled);
				if ((now - last) > SWING_THE_THING)
				{
					slow = True;
					CyclePendulum();
					last = now - SWING_THE_THING/2;
				}
			}
		}

		//point to the next buffer to send
		if (!stream->streamErr)
		{
			curBuf = va_arg(extra_buffers,UPtr);
			curSize = va_arg(extra_buffers,long);
		}
		
	} while (!stream->streamErr && curBuf);
	
	va_end(extra_buffers);
	
	if (stream->streamErr)
	{
		if (stream->streamErr!=commandTimeout && stream->streamErr!=userCancelled)
			OTTCPError(stream,stream->streamErr,result);
	}
		
	return (stream->streamErr);
}

/*********************************************************************************
 * OTTCPRecvTrans - get some text from the remote host.  This version uses OT TCP.
 *********************************************************************************/
OSErr OTTCPRecvTrans(TransStream stream, UPtr line,long *size)
{
	OTResult result = noErr;
	Str31 scratch;
	long timeout = InAThread() ? GetRLong(THREAD_RECV_TIMEOUT) : GetRLong(RECV_TIMEOUT);
	
	ASSERT(stream);
	ASSERT(stream->OTTCPStream);
	
	stream->streamErr = noErr;
	
	// Spin until something comes in.
	do
	{
		// read characters from the network, or die trying.
		stream->streamErr = OTWaitForChars(stream, timeout, line, size, &result);
		if (stream->streamErr == noErr)
		{				
			if (result >= 0)								// got something
				*size = result;
			else if (stream->OTTCPStream->otherSideClosed)	// lost connection
				stream->streamErr = errLostConnection;
			else if (result == kOTNoDataErr)				// got nothing
				*size = 0; 
			else 											// got some other error
				stream->streamErr = errMiscRec;
		}
	}
	while (stream->streamErr == commandTimeout &&
				 AlertStr(TIMEOUT_ALRT,Caution,GetRString(scratch,InAThread() ? THREAD_RECV_TIMEOUT : RECV_TIMEOUT))==1);
	
	if (stream->streamErr)
	{
		if (stream->streamErr!=commandTimeout && stream->streamErr!=userCancelled)
			OTTCPError(stream,stream->streamErr,(result < 0 ? result : kOTNoDataErr));
			// Note: result could be 0 if OTWaitForChars fails.  We want a real error message displayed in that case, too.
	}
	else 
	{
		if (*size) ResetAlertStage();
		if (*size && LogLevel&LOG_TRANS) CarefulLog(LOG_TRANS,LOG_GOT,line,*size);	//log what we got ...
		if (*size && IsRecAudit(stream)) stream->bytesTransferred += *size;
	}
			
	return (stream->streamErr);
}

/*********************************************************************************
 * TCPDisTrans - disconnect from the remote host.  This version uses OT TCP.
 *********************************************************************************/
OSErr OTTCPDisTrans(TransStream stream)
{
	if (stream && stream->OTTCPStream)
	{	
		stream->streamErr = noErr;
		
		//We're going to do an orderly disconnect on this stream.
		stream->OTTCPStream->weAreClosing = true;
			
		if ((stream->OTTCPStream->ref) && (!stream->OTTCPStream->ourSideClosed))
		{
			//close our end if the other side has aborted.
			if (stream->OTTCPStream->otherSideClosed) 
			{
				OTSndDisconnect(stream->OTTCPStream->ref, nil);
				stream->OTTCPStream->ourSideClosed = true;
				stream->OTTCPStream->releaseMe = true;				//we can destroy this connection completely.
			}
			else
			{
				if (OTSndOrderlyDisconnect(stream->OTTCPStream->ref)!=noErr) stream->OTTCPStream->releaseMe = true;
				stream->OTTCPStream->ourSideClosed = true;
				stream->OTTCPStream->age = TickCount();
			}
		}
		
		return (stream->streamErr);
	}
	else return (noErr);
}

/*********************************************************************************
 * OTTCPDestroyTrans - destroy the connection.  This version uses OT TCP
 *********************************************************************************/
OSErr OTTCPDestroyTrans(TransStream stream)
{
	OSStatus status = noErr;
		
	if ((stream == 0) || (stream->OTTCPStream == 0)) return (noErr);
	
	//We are destroying the stream without disconnecting it.  An error occured, or the the user cancelled.
	//Do an abortive disconnect.
	if (!stream->OTTCPStream->weAreClosing)
	{	
		DestroyMyTStream(stream->OTTCPStream);
		ZapPtr(stream->OTTCPStream);
	}
	else	//otherwise, queue up the stream for an orderly disconnect.
	{
		if (stream->OTTCPStream)
		{	
 			//if both sides are closed, throw out this connection immediately
 			if (stream->OTTCPStream->ourSideClosed && stream->OTTCPStream->otherSideClosed) stream->OTTCPStream->releaseMe = true;
 			
 			if (!stream->OTTCPStream->releaseMe) 			
			{
				//Queue it up in our queue of MyTStreams waiting to hear an orderly disconnect
				EnqueueMyTStream(stream->OTTCPStream);
				stream->OTTCPStream = 0;
			}
			else
			{
				//an error occurred disconnecting our stream.  Just kill it.
				DestroyMyTStream(stream->OTTCPStream);
				ZapPtr(stream->OTTCPStream);
			}
		}
	}
	
	//dispose our receive buffer.
	if (stream->RcvBuffer) ZapHandle(stream->RcvBuffer);
	
	return (stream->streamErr);
}
		
		
/*********************************************************************************
 * OTTCPTransError - report our most recent error
 *********************************************************************************/
OSErr OTTCPTransError(TransStream stream)
{
	ASSERT(stream);
	ASSERT(stream->OTTCPStream);
	
	return (stream->streamErr);
}

/*********************************************************************************
 * OTTCPSilenceTrans - turn off error reports from ot tcp routines
 *********************************************************************************/
void  OTTCPSilenceTrans(TransStream stream, Boolean silence)
{
	ASSERT(stream);
	
	stream->BeSilent = silence;
}

/********************************************************************************
 * OTTCPWhoAmI - return the mac's tcp name.  This version uses OT TCP.
 ********************************************************************************/
UPtr  OTTCPWhoAmI(TransStream stream, Uptr who)
{
#pragma unused(stream)

	InetInterfaceInfo localInfo;
	MyOTInetSvcInfo	myOTInfo;
	short	len;
	OSErr whoAmIErr = noErr;
	
	if (!*MyHostname)
	{
		whoAmIErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
		if (whoAmIErr == kOTNotFoundErr)	//OT hasn't been loaded yet ...
		{
			if (whoAmIErr = OpenOTInternetServices(&myOTInfo)) 
				return (who);
			whoAmIErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
			OTCloseProvider(myOTInfo.ref);
			myOTInfo.ref = 0;
		}
		if (whoAmIErr != noErr) 
			return (who);
		ComposeRString(who,TCP_ME,localInfo.fAddress);
		SetPref(PREF_LASTHOST,who);
		PCopy(MyHostname,who);
	}
	PCopy(who,MyHostname);
	
	//remove the '.' from the end of who, if there is one.
	len = strlen(who);
	if (who[len-1]=='.') who[len-1]=0;	
	
	return(who);
}

/********************************************************************************
 * DNSHostid - return the mac's dns server hostid.  This version uses OT TCP.
 ********************************************************************************/
OSErr  DNSHostid(uLong *dnsAddr)
{
	InetInterfaceInfo localInfo;
	MyOTInetSvcInfo	myOTInfo;
	OSErr dnsHostidErr = noErr;

	dnsHostidErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
	if (dnsHostidErr == kOTNotFoundErr)	//OT hasn't been loaded yet ...
	{
		if (!(dnsHostidErr = OpenOTInternetServices(&myOTInfo)))
		{ 
			dnsHostidErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
			OTCloseProvider(myOTInfo.ref);
			myOTInfo.ref = 0;
		}
	}

	if (!dnsHostidErr) *dnsAddr = *(long*)&localInfo.fDNSAddr;

	return(dnsHostidErr);
}

/********************************************************************************
 * OTMyHostid - return the mac's hostid.  This version uses OT TCP.
 ********************************************************************************/
OSErr  OTMyHostid(uLong *myAddr,uLong *myMask)
{
	InetInterfaceInfo localInfo;
	MyOTInetSvcInfo	myOTInfo;
	OSErr myHostidErr=noErr;
	
	myHostidErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
	if (myHostidErr == kOTNotFoundErr)	//OT hasn't been loaded yet ...
	{
		if (!(myHostidErr = OpenOTInternetServices(&myOTInfo)))
		{ 
			myHostidErr = OTInetGetInterfaceInfo(&localInfo, kDefaultInetInterface);
			OTCloseProvider(myOTInfo.ref);
			myOTInfo.ref = 0;
		}
	}

	if (!myHostidErr)
	{
		*myAddr = *(long*)&localInfo.fAddress;
		*myMask = *(long*)&localInfo.fNetmask;
	}
	
	return(myHostidErr);
}

/********************************************************************************
 * OTMyHostid - return the mac's hostid.  This version uses OT TCP.
 ********************************************************************************/
OSErr GetMyHostid(uLong *addr,uLong *mask)
{
		return(OTMyHostid(addr,mask));
}

/************************************************************************
 * OTGetHostByName - get host information, given a hostname
 * this routine maintains a small, unflushable cache.
 ************************************************************************/
OSErr OTGetHostByName(InetDomainName hostName, InetHostInfo *hostInfoPtr)
{
	MyOTInetSvcInfo	myOTInfo;
	OSErr nameErr = noErr;
				
	if (nameErr = OpenOTInternetServices(&myOTInfo)) 
		return (nameErr);
	myOTInfo.status = inProgress;
	nameErr = OTInetStringToAddress(myOTInfo.ref,hostName,hostInfoPtr);
	if (nameErr == noErr) nameErr = SpinOnWithConnectionCheck(&(myOTInfo.status),0,True,False);
	if (nameErr == noErr) nameErr = myOTInfo.result; 
	OTCloseProvider(myOTInfo.ref);
	myOTInfo.ref = 0;	
	//return without changing the name of the server we wish to look up.
	if (nameErr == noErr) strcpy(hostInfoPtr->name,hostName);	
	
	// log the result of the name lookup. Be careful not to overrun buffers.
	if (LogLevel&LOG_PROTO)
	{
		short count;
		Str63 logHostName;
		
		MakePStr(logHostName,hostName,strlen(hostName));
		ComposeLogS(LOG_PROTO,nil,"\pDNS Lookup of \"%p\"",logHostName);
		if (nameErr == noErr)
		{
			for (count=0;count<kMaxHostAddrs;count++)
				if (hostInfoPtr->addrs[count])
					ComposeLogS(LOG_PROTO,nil,"\p    %i (%d)",hostInfoPtr->addrs[count],count+1);
		}
		else ComposeLogS(LOG_PROTO,nil,"\pLookup failed (error %d)",nameErr);
	}
			
	return (nameErr);
}

/*********************************************************************************
 * OTGetHostByAddr - get host information, given an address.  This version uses OT
 * this routine maintains a small, unflushable cache.
 *********************************************************************************/
OSErr OTGetHostByAddr(InetHost addr, InetHostInfo *domainNamePtr)
{
	MyOTInetSvcInfo	myOTInfo;
	short len = 0;
	OSErr addrErr = noErr;
				
	domainNamePtr->addrs[0] = addr;	
	if (addrErr = OpenOTInternetServices(&myOTInfo)) 
		return (addrErr);
	myOTInfo.status = inProgress;
	addrErr = OTInetAddressToName(myOTInfo.ref,domainNamePtr->addrs[0],domainNamePtr->name);
	if (addrErr == noErr) addrErr = SpinOnWithConnectionCheck(&(myOTInfo.status),0,True,False);
	if (addrErr == noErr) addrErr = myOTInfo.result; 
	OTCloseProvider(myOTInfo.ref);
	myOTInfo.ref = 0;
	
	//remove the '.' from the end of name, if there is one.
	if (addrErr == noErr)
	{	
		len = strlen(domainNamePtr->name);
		if (domainNamePtr->name[len-1]=='.') domainNamePtr->name[len-1]=0;	
	}		
	
	return (addrErr);
}

/**********************************************************************
 * OTRandomAddr - pick a random address out of a set of addresses we
 * got from our OT DNR lookip.
 **********************************************************************/
InetHost OTRandomAddr(InetHostInfo *host)
{
	short count;
	
	if (!PrefIsSet(PREF_DNS_BALANCE)) 
		return (host->addrs[0]);
	
	for (count=kMaxHostAddrs;count;count--) if (host->addrs[count-1]) break;
	return (host->addrs[count<2?0:TickCount()%count]);
}

/************************************************************************
 * OTGetDomainMX - Get the mail exchange info for a particular host.
 ************************************************************************/
OSErr OTGetDomainMX(InetDomainName hostName, InetMailExchange *MXPtr, short *numMX)
{
	OSErr err = noErr;
	MyOTInetSvcInfo	myOTInfo;
	short i;
	
	if (MXPtr == 0 		// must have a place to store the MX records
	 || numMX == 0 		// must have an idea of how many records will fit there
	 || *numMX <= 0) 	// and must have allocated room for at least one
		return (paramErr);
	
	//clear out the MXPtr
	for (i = 0; i < *numMX; i++) 
		(MXPtr[i]).exchange[0] = (MXPtr[i]).preference = 0;
	
	//do the MX lookup
	if ((err = OpenOTInternetServices(&myOTInfo)) == noErr)
	{
		myOTInfo.status = inProgress;
		err = OTInetMailExchange(myOTInfo.ref, hostName, numMX, MXPtr);
		if (err == noErr) err = SpinOnWithConnectionCheck(&(myOTInfo.status),0,True,False);
		OTCloseProvider(myOTInfo.ref);
		myOTInfo.ref = 0;
		
		if (err == noErr)
			if (MXPtr->exchange[0] == 0) return (errNoMXRecords);			
	}
	
	return (err);
}

/**********************************************************************
 * GetPreferredMX - return the preferred host to send mail to.
 **********************************************************************/
void GetPreferredMX(InetDomainName preferredName, InetMailExchange *MXPtr, short numMX)
{
	short lowestPref, preferredHost;
	short count = numMX - 1;
	short len;
	
	preferredHost = count;
	lowestPref = MXPtr[count].preference;
	for (count; count >= 0; count--)
	{
		if (MXPtr[count].preference < lowestPref) 
		{
			lowestPref = MXPtr[count].preference;
			preferredHost = count;
		}
	}
	strcpy(preferredName, MXPtr[preferredHost].exchange);
	
	//remove the '.' from the end of preferredName, if there is one.
	len = strlen(preferredName);
	if (preferredName[len-1]=='.') preferredName[len-1]=0;	
}

#pragma segment Main
/*********************************************************************************
 * MyOTNotifyProc - This callback handles the results of all asynch OT calls.
 *********************************************************************************/
pascal void MyOTNotifyProc(MyOTInetSvcInfo *info, OTEventCode theEvent, OTResult theResult, void *theParam)
{
	switch (theEvent)
	{
		case T_OPENCOMPLETE:							// The OTAsyncOpenInternetServices function has completed
		case T_DNRSTRINGTOADDRCOMPLETE:		// The OTInetStringToAddress function has finished	
		case T_DNRADDRTONAMECOMPLETE:			// The OTInetAddressToName function has finished
		case T_DNRMAILEXCHANGECOMPLETE:		// The OTInetMailExchange function has finished
			info->status = 0;
			info->result = theResult;
			info->cookie = theParam;
			break;
				
		default:													// All other network events can be ignored
			break;
	}
}
#pragma segment TcpTrans

/*********************************************************************************
 * OpenOTInternetServices - Open up the internet service provider OT gives us.
 *********************************************************************************/
OSErr OpenOTInternetServices(MyOTInetSvcInfo *myOTInfo)
{
	OSErr err = noErr;
	DECLARE_UPP(MyOTNotifyProc,OTNotify);
	
	INIT_UPP(MyOTNotifyProc,OTNotify);
	myOTInfo->status = inProgress;
	if ((err = OTAsyncOpenInternetServicesInContext(kDefaultInternetServicesPath, 0, MyOTNotifyProcUPP, myOTInfo,NULL)) == noErr) 
		if ((err = SpinOnWithConnectionCheck(&(myOTInfo->status),0,True,False)) == noErr) 
		{
			myOTInfo->ref = myOTInfo->cookie;
		}
	
	if (err != noErr && err != userCancelled) err = errOTInetSvcs;
			
	return (err);
}

#pragma segment Main
/*********************************************************************************
 *	MyOTStreamNotifyProc - my OT notifier proc for TCP streams.
 *********************************************************************************/
pascal void MyOTStreamNotifyProc (MyOTTCPStream *myStream, OTEventCode code, OTResult theResult, void *cookie)
{
	OSStatus err;
	
	switch (code) 
	{
		case T_DISCONNECT:								// Other side has aborted
			myStream->otherSideClosed = true;
			myStream->status = 0;
			break;
			
		case T_ORDREL:									// Other side has closed in an orderly fashion
			myStream->otherSideClosed = true;
			myStream->status = 0;
			err = OTRcvOrderlyDisconnect(myStream->ref);
			if (!myStream->ourSideClosed)
			{
				err = OTSndOrderlyDisconnect(myStream->ref);
				myStream->ourSideClosed = true;
			}
			myStream->releaseMe = true;					// we don't need this stream anymore.
			break;
			
		case T_OPENCOMPLETE:							// OTOpenAsyncEndpoint has finished
		case T_BINDCOMPLETE:							// OTBind has finished
		case T_UNBINDCOMPLETE:							// OTUnbind has finished
		case T_CONNECT:									// OTConnect has finished
		case T_PASSCON:									// state is now T_DATAXFER
			myStream->status = 0;
			myStream->code = code;
			myStream->result = theResult;
			myStream->cookie = cookie;
			break;
	}
}
#pragma segment TcpTrans

/*********************************************************************************
 *	CreateOTStream - create a MyOTTCPStreamPtr
 *********************************************************************************/
OSErr CreateOTStream(TransStream stream)
{
	OSStatus	OTErr = noErr;
	DECLARE_UPP(MyOTStreamNotifyProc,OTNotify);
	
	INIT_UPP(MyOTStreamNotifyProc,OTNotify);
	ASSERT(stream);
	
	stream->streamErr = noErr;
	
	//Open a TCP endpoint asynchronously
	stream->OTTCPStream->status = inProgress;
	stream->streamErr = OTAsyncOpenEndpointInContext(OTCreateConfiguration(kTCPName),0,0,MyOTStreamNotifyProcUPP,stream->OTTCPStream,nil);
	if (stream->streamErr == noErr) stream->streamErr = SpinOnWithConnectionCheck(&(stream->OTTCPStream->status),0,True,False);
	if ((stream->streamErr == noErr) || ((stream->streamErr = stream->OTTCPStream->result) == noErr)) 
	{
		if (stream->OTTCPStream->code != T_OPENCOMPLETE) 
			return (stream->streamErr = errOpenStream);
		stream->OTTCPStream->ref = stream->OTTCPStream->cookie;
		if (stream->OTTCPStream->ref == kOTInvalidEndpointRef)
			return (stream->streamErr = errOpenStream);

		//Initialize the MyOTTCPStreamPtr flags
		stream->OTTCPStream->weAreClosing = false;
		stream->OTTCPStream->otherSideClosed = false;
		stream->OTTCPStream->ourSideClosed = false;
		stream->OTTCPStream->releaseMe = false;
	}

	if (stream->streamErr != noErr)
	{
		if (stream->OTTCPStream->ref) OTCloseProvider(stream->OTTCPStream->ref);
		stream->OTTCPStream->ref = 0;
	}
	return (stream->streamErr);
} 

/*********************************************************************************
 *	OTTCPOpen - actually open a connection
 *	
 *	3/18/99 no longer using OT memory allocation routines.
 *********************************************************************************/
OSErr OTTCPOpen(TransStream stream, InetHost tryAddr, InetPort port,uLong timeout)
{
	InetAddress connectAddr;
	TCall sndCall;
	
	ASSERT(stream);
	ASSERT(stream->OTTCPStream);
	
	stream->streamErr = noErr;
	stream->OTTCPStream->status = inProgress;
	if (stream->streamErr = OTBind(stream->OTTCPStream->ref, 0, 0)) 	// OTBind doesn't need any parameters.  This is an outgoing connection.  jdboyd 04/11/02
		return (stream->streamErr);
	if (stream->streamErr = SpinOnWithConnectionCheck(&(stream->OTTCPStream->status),60*timeout,True,False)) 
		return (stream->streamErr);
	if (stream->OTTCPStream->code != T_BINDCOMPLETE) 
	{
		stream->streamErr = stream->OTTCPStream->result;
		return (errOpenStream);
	}
	
	// set up the TCall structure needed to connect to tryAddr		
	OTInitInetAddress(&connectAddr, port, tryAddr);
	WriteZero(&sndCall, sizeof(TCall));
	sndCall.addr.len = sizeof(InetAddress);				
	sndCall.addr.buf = (unsigned char*) &connectAddr;
	
	// now connect to the address asynchronously
	stream->OTTCPStream->status = inProgress;	
	stream->streamErr = OTConnect(stream->OTTCPStream->ref, &sndCall, 0);
	if (stream->streamErr != noErr && stream->streamErr != kOTNoDataErr) 
		return (stream->OTTCPStream->otherSideClosed ? errLostConnection : stream->streamErr);
	if (stream->streamErr = SpinOnWithConnectionCheck(&(stream->OTTCPStream->status),60*timeout,True,False))
		return (stream->OTTCPStream->otherSideClosed ? errLostConnection : stream->streamErr);
	
	// did we connect?
	if (stream->OTTCPStream->code != T_CONNECT) 		
	{
		stream->streamErr = errLostConnection;
		return (errOpenStream);
	}
	if (stream->streamErr = OTRcvConnect(stream->OTTCPStream->ref, 0)) 
		return (stream->streamErr);
	
	return (stream->streamErr);
}

/*********************************************************************************
 * OTWaitForChars - spin, giving everybody else time, until chars available
 *********************************************************************************/
short OTWaitForChars(TransStream stream, long timeout, UPtr line, long *size, OTResult *otResult)
{
	EventRecord event;
	long ticks=TickCount();
	static long waitTicks=0;
	long tookTicks;
	Boolean result = false;
	long timeoutTicks = ticks + 60*timeout;
	Boolean slow = False;
	OTFlags junkFlags = 0;
	
	ASSERT(stream);
	ASSERT(stream->OTTCPStream);
	
	if (!InBG) waitTicks = 0;
	
	// spin until we see data on the network ...
	do
	{
		// check to see if our connection is still up.  Only care if remote end has closed.
		if (stream->OTTCPStream->otherSideClosed) return(errLostConnection);			

		// check to see if we need ppp, but the connection is down
		if (needPPPConnection && MyOTPPPInfo.state != kPPPUp) return(errLostConnection);
		
		//check to see if cmd-. has been pressed.
		if (CommandPeriod) return (userCancelled);
									
		if (TickCount()-ticks  > 10)
		{
			slow = True;
			CyclePendulum();
			ticks=TickCount();
			if (ticks >timeoutTicks) return(commandTimeout);
		}

		// receive size bytes and put it in line.  We expect some data, so wait around until we see it.
		*otResult = OTRcv(stream->OTTCPStream->ref, line, *size, &junkFlags);
		
		// we'll spin until we see data or an error.
		if (*otResult == kOTNoDataErr) *otResult = 0;
		
		if (slow) YieldTicks = 0;
		
		//	To speed up threaded xfers when app is in bg, don't call WaitNextEvent as often
		// also speeded up typing by changing NEED_YIELD -- it checks Typing when in a thread. (though I'm not quite sure how thread gets time when Typing is true)
		if (NEED_YIELD || ((*otResult == 0) && !stream->DontWait))
		{	
			tookTicks = TickCount();

// 11-13-97 change to fix cmd period bug when in main thread
			result = WNE(MINI_MASK|keyDownMask,&event,waitTicks);
			if (CommandPeriod) return(userCancelled);
//was:
//		result = WNE(MINI_MASK,&event,waitTicks);
			
			tookTicks = TickCount()-tookTicks;
			if (InBG)
				if (tookTicks > waitTicks+1)
					waitTicks = MIN(120,tookTicks);
				else
					waitTicks = waitTicks>>1;
			else
				waitTicks = 1;
			if (result) (void) MiniMainLoop(&event);
		}
	}
	while ((*otResult == 0) && !stream->DontWait);
	stream->CharsAvail = 0;
	return(0);
}


/*********************************************************************************
 *	OTFlushInput - dump all chars that arrive on a stream, until there are no
 *   chars for a given period of time
 *********************************************************************************/
void OTFlushInput(TransStream stream,uLong timeout)
{
	Str255 junk;
	OTResult result;
	long got;

	do
	{
		got = sizeof(junk);
		if (OTWaitForChars(stream, timeout, junk, &got, &result)) break;
		if (LogLevel&LOG_TRANS && !stream->streamErr && result>0) CarefulLog(LOG_TRANS,LOG_FLUSHED,junk,result);
	}
	while (result>0);
}

static short errorMessages[errMyLastOTErr-errOTInitFailed] = 
	{ 
		OT_INIT_ERR,	//errOTInitFailed,
		OT_INIT_ERR,	//errOTInetSvcs, 
		BIND_ERR,	//errDNR,
		0,	//errNoMXRecords,
		TCP_TROUBLE,	//errCreateStream,
		NO_SMTP_SERVER,	//errOpenStream,
		TCP_TROUBLE,	//errLostConnection,
		TCP_TROUBLE,	//errMiscRec,
		TCP_TROUBLE,	//errMiscSend
		OT_DIALUP_CONNECT_ERR,//errPPPConnect
		0,	//errPPPPrefNotFound
		0,	//errPPPStateUnknown
		OT_MISSING_LIBRARY	//errOTMissingLib:
	};



static void OTTELo ( OSErr generalError, OSErr specificError, StringPtr message ) {
	if (IsMyOTError(generalError))
		GetRString(message, errorMessages[generalError - errOTInitFailed]);
	else
		GetRString(message, TCP_TROUBLE);
	}

/*********************************************************************************
 *	OTTE - give the user some helpful information if an error occurs.
 *		generalError contains a general description of the error, set by me.
 *		specificError contains the actual error returned by the failed call
 *********************************************************************************/
OSErr OTTE(TransStream stream, OSErr generalError, OSErr specificError, short file, short line)
{
	short	errorString = 0;
	OSErr	MacTCPErr = noErr;
	
	if ((generalError != noErr)
		&& (stream==0 || !stream->BeSilent) 
		&& !AmQuitting
		&& (!CommandPeriod || stream==0 || stream->Opening && !PrefIsSet(PREF_OFFLINE) && !PrefIsSet(PREF_NO_OFF_OFFER)))
	{
		Str255 message;
		Str255 tcpMessage;
		Str63 debugStr;
		Str31 rawNumber;
		short realSettingsRef = SettingsRefN;

		tcpMessage[0] = 0;
		
		NumToString(specificError,rawNumber);
		
		SettingsRefN = GetMainGlobalSettingsRefN();		
		OTErrorToString(specificError, tcpMessage);
		OTTELo ( generalError, specificError, message );
		ComposeRString(debugStr,FILE_LINE_FMT,file,line);
		SettingsRefN = realSettingsRef;

		
		MyParamText(message,rawNumber,tcpMessage,debugStr);
		if (stream==0 || stream->Opening)
		{
			if (2==ReallyDoAnAlert(OPEN_ERR_ALRT,Caution))
				SetPref(PREF_OFFLINE,YesStr);
		}
		else ReallyDoAnAlert(BIG_OK_ALRT,Caution);

	}
		
	return (generalError);
}

/*********************************************************************************
 *	OTErrorToString - pick an error string that best describes the
 *	specific error.
 *********************************************************************************/
void OTErrorToString(short specificError, Str255 tcpMessage)
{	
	short errorString = 0;
	tcpMessage[0] = 0;

	//Was this some error that I defined?
	if (IsMyOTError(specificError))
	{
		switch (specificError)
		{
			case errOTInetSvcs:
				errorString = OT_INET_SVCS_ERR;
				break;
			
			case errLostConnection:
				specificError = TCPRemoteAbort;
				break;
			
			case errPPPStateUnknown:
				errorString = OT_PPP_STATE_ERR;
				break;
					
			case errPPPPrefNotFound:
				errorString = OT_TCPIP_PREF_ERR;
				break;
			
			case errOTMissingLib:
				errorString = OT_MISSING_LIBRARY;
				break;
				
			default:
				errorString = OT_UNKNOWN_ERR;
				break;
		}
	}
	
	if (errorString!=0) GetRString(tcpMessage,errorString);
	else
	{	
		if (IsXTIError(specificError))
			GetRString(tcpMessage,OTTCP_ERR_STRN + XTI2OSStatus(specificError));
		else if (IsOTPPPError(specificError))							// was this an OT/PPP related error?
			GetRString(tcpMessage,OTPPP_ERR_STRN - specificError + kCCLErrorBaseCode - 1);
		else if (-23000>=specificError && specificError >=-23048)		// was this a MacTCP error code?
			GetRString(tcpMessage,MACTCP_ERR_STRN-22999-specificError);	
		else if (2<=specificError && specificError<=9)
			GetRString(tcpMessage,MACTCP_ERR_STRN+specificError+(23048-23000));
		else
			tcpMessage = 0;
	}
}


/*********************************************************************************
 * DestroyMyTStream - deallocate everything a MyStream grabs for itself.
 *********************************************************************************/
void DestroyMyTStream(MyOTTCPStreamPtr myStream)
{
	OSErr destroyErr = noErr;
	
	if (myStream)
	{		
		//clean up after the myStream
		if (myStream->ref != 0)
		{
			//make sure the connection is closed.
			if (!myStream->otherSideClosed || !myStream->ourSideClosed) OTSndDisconnect(myStream->ref, nil);
			
			//unbind the endpoint
			myStream->status = inProgress;
			destroyErr = OTUnbind(myStream->ref);
			if (destroyErr == noErr) destroyErr = SpinOnWithConnectionCheck(&(myStream->status),0,True,False);
	
			// remove the endpoint's notifier.  We won't be needing it anymore.
			OTRemoveNotifier(myStream->ref);
			
			// now we can kill the endpoint.
			destroyErr = OTCloseProvider(myStream->ref);	
			myStream->ref = 0;
		}
		
		if (gActiveConnections) gActiveConnections--;
	}
}

/*********************************************************************************
 * EnqueueMyTStream - put myStream in the queue of streams waiting to close.
 *********************************************************************************/
void EnqueueMyTStream(MyOTTCPStreamPtr myStream)
{
	MyOTTCPStreamPtr queueScan = 0;
	OSStatus status = noErr;
	
	ASSERT(myStream);
	
	if (pendingCloses == 0)		//this is the only stream in the queue
	{
		pendingCloses = myStream;
		myStream->next = 0;
		myStream->prev = 0;
	}	
	else						//there are some other streams waiting to close
	{
		queueScan = pendingCloses;
		while (queueScan->next != 0) queueScan = queueScan->next;
		queueScan->next = myStream;
		myStream->prev = queueScan;
		myStream->next = 0;
	}
}

/*********************************************************************************
 * KillDeadMyTStreams - deallocate memory and TStreams that have received an
 * orderly disconnect.  destroy determines whether to kill all the streams, or
 * only the ones that have been disconnected in an orderly fashion.
 *********************************************************************************/
 void KillDeadMyTStreams(Boolean destroy)
 {
 	MyOTTCPStreamPtr queueScan = pendingCloses;
 	MyOTTCPStreamPtr releaseThisOne = 0;
 	
 	//loop through and find stream that can be released.
 	while (queueScan != 0)
 	{
 		OSStatus queueScanStatus;
 		
 		if (queueScan->ref == 0) queueScan->releaseMe = true;	//this should never happen ...
 		else
 		{
	 		queueScanStatus = OTLook(queueScan->ref);
	 		
	 		//if this endpoint has some data waiting to be read, read it.
			if (queueScanStatus == T_DATA || queueScanStatus == T_EXDATA)	
			{
				do {
					queueScanStatus = OTRcv(queueScan->ref, queueScan->dummyBuffer, sizeof(queueScan->dummyBuffer), nil);
				} while (queueScanStatus >= 0);
				
				queueScan->age = TickCount();	//this stream made a noise, earning it a new minute.
			}
				
			if ((TickCount() - queueScan->age) > 3600) 
				queueScan->releaseMe = true;	//keep silent connections around for 1 minute.
		}
 		
 		//the MyOTStream callback will set releaseMe once this stream can die quietly
 		if (destroy || queueScan->releaseMe)
 		{	
 			releaseThisOne = queueScan;
 			queueScan = queueScan->next;
 			
 			if (releaseThisOne == pendingCloses) pendingCloses = releaseThisOne->next;
 			
 			if (releaseThisOne->prev) (releaseThisOne->prev)->next = releaseThisOne->next;
 			if (releaseThisOne->next) (releaseThisOne->next)->prev = releaseThisOne->prev;
 			DestroyMyTStream(releaseThisOne);
 			ZapPtr(releaseThisOne);
 		}
 		else
 			queueScan = queueScan->next;
 	}
}
 
 
/*********************************************************************************
 * OTVerifyOpen - Make sure OT TCP is ready to go.  May have to conenct with PPP
 * or SLIP, depending on what is selected in the TCP/IP control panel
 *********************************************************************************/
OSErr OTVerifyOpen(TransStream stream)
{
	Boolean weAttemptedPPP = false;
	
	stream->streamErr = noErr;

	//signal everyone else that, no, PPP is not needed for the connection	
	needPPPConnection = false;
	gPPPConnectFailed = false;
		
	if (!PrefIsSet(PREF_IGNORE_PPP))	//this pref lets us ignore MacSLIP/PPP if we want
	{	
		if ((stream->streamErr = SelectedConnectionMode(&connectionSelection, true)) == noErr)
		{
			if (dialingThePhone)
			{
				//sit and wait for a connection.
				if (connectionSelection == kPPPSelected)
				{
					ProgressMessageR(kpSubTitle,OT_PPP_WAIT);
					do
					{
						MiniEvents();
						if (CommandPeriod) 	return (stream->streamErr = userCancelled);
					}
					while (dialingThePhone);
					if (PPPDown()) return(stream->streamErr = errPPPConnect);
				}
			}
			else if (connectionSelection == kPPPSelected)
			{
				dialingThePhone = true;
				needPPPConnection = true;	//let everyone know the connection is being made over PPP.
				gConnecting = true;			//likewise, tell the world we're dialing the phone.
				if (stream->streamErr = OTPPPConnect(&weAttemptedPPP))
				{
					gPPPConnectFailed = true;	//flag to tell the world that our PPP connection attempt failed.
					
					// Holy crap, Batman! This has been broken all these years.  Only force clase the connection
					// if we were the fools to think we could connect it to begin with. -JDB 12/16/98
					OTPPPDisconnect(weAttemptedPPP, true);
				}
				gConnecting = false;
				dialingThePhone = false;
			}
		}
	}
	return (stream->streamErr);
}

/*********************************************************************************
 * OTPPPConnect - Cause an OTPPP connection to happen.  I will create one single
 * OTPPP control endpoint, and keep it around.  Apple says there's a bug with the
 * current OT/PPP that causes OTCloseProvider() to crash when closing a PPP control
 * endpoint.
 *
 *	6/18/97	This routine can be called from multiple threads.  The first call
 *		will initiate the PPP connection.  Subsequent calls will wait for the PPP
 *		connection.
 *
 *	12/16/98 Added attemotedConnection paramter to let caller know whether we make
 *		the connection attempt or not.  There might be one underway, in which case
 *		we wait for it.
 *
 *	May, 1999 Added a delay after (a) the connection is made, or (b) the connection
 *		we were waiting for finishes.  We delat for <x-eudora-setting:13102> seconds
 *		to allow ARA to actually connect.
 *********************************************************************************/ 
OSErr OTPPPConnect(Boolean *attemptedConnection)
{					
	OSErr err = noErr;
	OTResult result = kOTNoError;
	unsigned long PPPState = 0;
	Boolean PPPInForeGround = PrefIsSet(PREF_PPP_FOREGROUND);
	Str255 scratch;
	Boolean redial = false;
	long numRedials = 0;
	long delay = 0;
	long dialCount = 0;
	Boolean doDelay = false;	// do we need to delay for stupid ARA?
	
#ifdef	DEBUG
	ASSERT(attemptedConnection);	// must have this parameter
#endif
					
	pppErr = noErr;
	*attemptedConnection = false;
					
	//Make a new PPP endpoint.  We're only going to keep one around.
	if (MyOTPPPInfo.ref == 0) result = NewPPPControlEndpoint();
	
	if ((result == kOTNoError) && (MyOTPPPInfo.ref != kOTInvalidEndpointRef))
	{	
		// Check the current state of the PPP connection.  
		err = GetPPPConnectionState(MyOTPPPInfo.ref, &PPPState);
		if (err == noErr)
		{		
			// have we already connected PPP at some point in the past?
			if (MyOTPPPInfo.weConnectedPPP==true && MyOTPPPInfo.state==kPPPUp && PPPState==kPPPStateOpened) return noErr;
			
			//If we don't already have an open connection, open one.
			if ((PPPState != kPPPStateOpened && PPPState != kPPPStateOpening) || MyOTPPPInfo.state == kPPPClosing)
			{
				// remember that we're the one starting this connection
				*attemptedConnection = true;
				
				// we'll have to delay for ARA
				doDelay = true;
				
				// Get the redial information
				if (!PPPInForeGround)
				{
					err = OTPPPDialingInformation(&redial, &numRedials, &delay);
					if (err != noErr || numRedials < 1) redial = false;	// couldn't get redial data.  Ignore it.							
				
					dialCount = redial ? numRedials + 1 : 1;
				}
				else dialCount = 1;		//redials are handled in the PPP connection dialog already.
				
				// Establish a new connection		
				TurnOnPPPConnectionDialog(MyOTPPPInfo.ref, PPPInForeGround);
				
				// force the current connection to close if it hasn't been to to already
				if (PPPState == kPPPStateOpened || PPPState == kPPPStateOpening)
				{					
					OTPPPDisconnect(true, false);
					if (pppErr = WaitForOTPPPDisconnect(true)) return (pppErr);
					MyOTPPPInfo.state = kPPPDown;
				}
		
				MyOTPPPInfo.result = noErr;
				ProgressMessageR(kpSubTitle,OT_PPP_CONNECT);				
				if (!PPPInForeGround) 				
				{
					GetRString(scratch, OT_PPP_CONNECT_MESSAGE);
					ProgressMessage(kpMessage,scratch);
				}

				// Actually connect OT/PPP
				MyOTPPPInfo.code = 0;	
				MyOTPPPInfo.state = kPPPOpening;
				result = kCCLErrorLineBusyErr;
				pppErr = OTIoctl(MyOTPPPInfo.ref, I_OTConnect, NULL);			
				
				// Bug 1648 - if we're connecting PPP with the connection dialog, we should sit and spin until it finishes dialig the phone.
				if (PPPInForeGround)
				{
					MyOTPPPInfo.status = inProgress;
					if (pppErr == noErr) pppErr = SpinOn(&(MyOTPPPInfo.status),0,True,False);
				}
				
				// Spin until we can do TCP/IP over the connection
				while (dialCount > 0 && result == kCCLErrorLineBusyErr && !PPPInForeGround && pppErr == noErr)
				{
					while (true)
					{
						MyOTPPPInfo.event = 0;
						MyOTPPPInfo.result = noErr;
						MyOTPPPInfo.status = inProgress;
						if (pppErr == noErr) pppErr = SpinOn(&(MyOTPPPInfo.status),0,True,False);
						
						result = MyOTPPPInfo.result;
						if (MyOTPPPInfo.state == kPPPUp || MyOTPPPInfo.state == kPPPDown || pppErr != noErr || MyOTPPPInfo.result < 0) break;
							
						// Update the progress dialog.
						GetRString(scratch, MyOTPPPInfo.event);
						ProgressMessage(kpMessage,scratch);
					}
					dialCount--;
					if (redial && result == kCCLErrorLineBusyErr) ProgressMessageR(kpSubTitle,OT_PPP_REDIALING);
				}
			}
			else
			{	
				//there's a connection current trying to open
				if (PPPState == kPPPStateOpening)
				{
					// do the ARA delay ...
					doDelay = true;
					
					//Spin until we can talk TCP over the connection
					ProgressMessageR(kpSubTitle,OT_PPP_CONNECT);				
					GetRString(scratch, OT_PPP_CONNECT_MESSAGE);
					ProgressMessage(kpMessage,scratch);
					
					MyOTPPPInfo.state = kPPPOpening;
					while (true)
					{
						MyOTPPPInfo.event = 0;
						MyOTPPPInfo.result = noErr;
						MyOTPPPInfo.status = inProgress;
						err = SpinOn(&(MyOTPPPInfo.status),0,True,False);
					
						result = MyOTPPPInfo.result;
						if (MyOTPPPInfo.state == kPPPUp || MyOTPPPInfo.state == kPPPDown || err != noErr || MyOTPPPInfo.result < 0) break;
							
						// Update the progress dialog.
						GetRString(scratch, MyOTPPPInfo.event);
						ProgressMessage(kpMessage,scratch);
					}
					
					if (err == noErr) err = MyOTPPPInfo.result;
				}
				
				if (err == noErr)
				{
					MyOTPPPInfo.state = kPPPUp;
					*attemptedConnection = false;	// PPP is up, but we didn't connect it.
					pppErr = noErr;
				}
				else return (pppErr = err);
			}
		}
		else	//could not determine the state of the PPP connection
		{
			return (pppErr = errPPPStateUnknown);		
		}
	}				
	else
		return (pppErr = result);
	
	// The connection is up because we connected it, or someone else did
	if (pppErr == noErr)
	{
		if (MyOTPPPInfo.state == kPPPUp) 
		{
			// must we delay for ARA?
			if (doDelay)
			{
				// 	PPP Race Condition Hack.  
				//
				//	Since ARA and PPP have been combined, it's now possible to get the kPPPConnectCompletedEvent
				//	*before* ARA does.  This will cause the first network operation to fail.  So, let's Pause
				//	for a while, and let ARA know about it's successful connection.
				long delay = GetRLong(OT_PPP_RACE_HACK);
				Str255 delayStr;
				
				if (delay > 0) 
				{
					NumToString(delay, delayStr);
					ComposeRString(scratch,OT_PPP_SMART_ASS,delayStr);
					ProgressMessage(kpMessage,scratch);
					
					Pause(60*delay);
				}
			}			
			
			MyOTPPPInfo.weConnectedPPP = *attemptedConnection;	// remember if we started the connections ourselves or not.	
		}
		else
		{
			if (!PPPInForeGround) pppErr = MyOTPPPInfo.result ? MyOTPPPInfo.result : kCCLErrorGeneric;	//return some sort of error
			else pppErr = userCancelled;	//errors are handled in the PPP connection dialog
		}
	}
					
	return (pppErr);
}

/*********************************************************************************
 * NewPPPControlEndpoint - Set up the global PPP enpoint we use to control PPP.
 *********************************************************************************/
OTResult NewPPPControlEndpoint(void)
{
	OTResult	result = kOTNoError;
	TEndpointInfo epInfo;
	short oldResFile = CurResFile();
	DECLARE_UPP(MyPPPEndpointNotifier,OTNotify);
	
	INIT_UPP(MyPPPEndpointNotifier,OTNotify);

	MyOTPPPInfo.ref = OTOpenEndpointInContext(OTCreateConfiguration(kPPPControlName),0, &epInfo, &result,nil);
	UseResFile(oldResFile);	// bug in Modem control panel resets curresfile SD 8/5/98
	
	if((result == kOTNoError) && (MyOTPPPInfo.ref != kOTInvalidEndpointRef))
	{
		result = OTInstallNotifier(MyOTPPPInfo.ref, MyPPPEndpointNotifierUPP, (void *)NULL);
		if(result == kOTNoError)
		{
			if ((result = OTIoctl(MyOTPPPInfo.ref, I_OTGetMiscellaneousEvents, (void*)1)) == kOTNoError ) 
			{
				MyOTPPPInfo.state = kPPPDown;		//kPPPDown means PPP has not been touched by us yet.
			}
			OTSetAsynchronous(MyOTPPPInfo.ref);
		}
	}

	//if some error occurred, return 0, but don't close the endpoint.
	if (result != kOTNoError) MyOTPPPInfo.ref = kOTInvalidEndpointRef;

	return (result);
}
	
	
/*********************************************************************************
 * TurnOnPPPConnectionDialog - make the PPP connection a modal event
 *
 * If on is true, then we will turn the connection dialog ON.  If on is false,
 * we will restore the conenction dialog to its previous state.
 *********************************************************************************/
OSErr TurnOnPPPConnectionDialog(EndpointRef endPoint, Boolean on)
{
	OSErr err = noErr;
	long dlogState = 0;

	err = GetCurrentUInt32Option(endPoint, OPT_ALERTENABLE, &dlogState);
	if (err == noErr)
	{
		if (oldDlogState == 0) oldDlogState = dlogState;
		
		if (on)		//set the kPPPConnectionStatusDialogsFlag
			dlogState = dlogState | kPPPConnectionStatusDialogsFlag;
		else 		//turn the kPPPConnectionStatusDialogsFlag off.
			dlogState = dlogState & (kPPPAllAlertsEnabledFlag - kPPPConnectionStatusDialogsFlag);
		
		if (dlogState != oldDlogState)
			err = SetCurrentUInt32Option(endPoint, OPT_ALERTENABLE, dlogState);
	}
	
	return (err);
}

/*********************************************************************************
 * ResetPPPConnectionDialog - rest the statusDialogFlag to what it was before we
 * started messing with it.
 *********************************************************************************/
OSErr ResetPPPConnectionDialog(EndpointRef endPoint)
{
	OSErr err = noErr;
	long dlogState = 0;

	err = GetCurrentUInt32Option(endPoint, OPT_ALERTENABLE, &dlogState);
	if (err == noErr)
	{	
		if (dlogState != oldDlogState)	
		{
			if (!(oldDlogState&kPPPConnectionStatusDialogsFlag))	//if it was off to begin with, turn it off to reset
				dlogState = dlogState & (kPPPAllAlertsEnabledFlag - kPPPConnectionStatusDialogsFlag);
			else 	//if it was on before, turn it back on to reset.
				dlogState = dlogState | kPPPConnectionStatusDialogsFlag;
			
			err = SetCurrentUInt32Option(endPoint, OPT_ALERTENABLE, dlogState);
		}
		oldDlogState = 0;
	}
	
	return (err);
}

/*********************************************************************************
 * GetPPPConnectionState - determine the state of PPP
 *********************************************************************************/
OSErr GetPPPConnectionState(EndpointRef endPoint, unsigned long *PPPState)
{
	OSErr			err = noErr;

	err = GetCurrentUInt32Option(endPoint, PPP_OPT_GETCURRENTSTATE, PPPState);
	
	if (err != noErr) *PPPState = kPPPStateInitial;
		
	return (err);
}

/*********************************************************************************
 * GetCurrentUInt32Option - gets a UInt32 option from a PPP control point.
 *********************************************************************************/
OSErr GetCurrentUInt32Option(EndpointRef endPoint, OTXTIName theOption, UInt32 *value)
{
	OSErr err = noErr;
	unsigned char buf[sizeof(TOption)];
	TOption *option;
	TOptMgmt command;

	*value = 0;
	
	command.opt.buf = buf;
	command.opt.len = sizeof(TOption);
	command.opt.maxlen = sizeof(TOption);
	command.flags = T_CURRENT;
	
	option = (TOption *)buf;
	option->len = sizeof(TOption);
	option->level = COM_PPP;
	option->name = theOption;
	option->status = 0;
	option->value[0] = 0;
	
	//get the current alert flags
	err = OTOptionManagement(endPoint, &command, &command);
	
	if ((err != noErr) || (option->status == T_FAILURE) || (option->status == T_NOTSUPPORT))
		*value = 0L;
	else
		*value = option->value[0];
		
	return (err);
}

/*********************************************************************************
 * SetCurrentUInt32Option - sets a UInt32 option for PPP control point options.
 *********************************************************************************/
OSErr SetCurrentUInt32Option(EndpointRef endPoint, OTXTIName theOption, UInt32 value)
{
	OSErr err = noErr;
	unsigned char buf[sizeof(TOption)];
	TOption *option;
	TOptMgmt command;
	
	command.opt.buf = buf;
	command.opt.len = sizeof(TOption);
	command.opt.maxlen = sizeof(TOption);
	command.flags = T_NEGOTIATE;
	
	option = (TOption *)buf;
	option->len = sizeof(TOption);
	option->level = COM_PPP;
	option->name = theOption;
	option->status = 0;
	option->value[0] = value;
	
	//get the current alert flags
	err = OTOptionManagement(endPoint, &command, &command);
	
	return (err);
}

/*********************************************************************************
 * OTPPPDisconnect - disconnect OTPPP if Eudora connected it in the first place.
 * if (forceDisconnect) then we disconnect no matter who connected PPP
 * if (endConnectionAttempt) then we reset the PPP connection dialog.
 *********************************************************************************/
OSErr OTPPPDisconnect(Boolean forceDisconnect, Boolean endConnectionAttempt)
{
	OSErr err = noErr;
	
	if ((gHasOTPPP == true) 
		&& ((MyOTPPPInfo.ref && PrefIsSet(PREF_PPP_DISC) && MyOTPPPInfo.weConnectedPPP) || forceDisconnect))
	{						
		err = OTIoctl(MyOTPPPInfo.ref, I_OTDisconnect, 0);
		
		if (endConnectionAttempt) 
		{
			ResetPPPConnectionDialog(MyOTPPPInfo.ref);
		}
		
		MyOTPPPInfo.weConnectedPPP = false;
		MyOTPPPInfo.status = MyOTPPPInfo.result = 0;
	}
	
	return (err);
}

/*********************************************************************************
 * OTPPPConnectForLink - connect OT/PPP to follow a link.  Ignore the fact that
 *	we connected it, so we don't go try to close the connection later.
 *********************************************************************************/
OSErr OTConnectForLink(void)
{
	OSErr err = userCanceledErr;
	unsigned long conMethod = 0;
	Boolean weAttemptedPPP = false;
	
	// Only makes sense to do this when OT is present
	if (gUseOT)
	{
		// How are we connecting to the internet? Read from the preference files or NS database
		err = SelectedConnectionMode(&conMethod,true);
			
		// do we have PPP?
		if (gHasOTPPP)
		{
			// Is OT/PPP the mode of connection?
			if (conMethod == kPPPSelected)
			{
				dialingThePhone = true;
				needPPPConnection = true;	
				gConnecting = true;	
				if (err = OTPPPConnect(&weAttemptedPPP))
				{					
					OTPPPDisconnect(weAttemptedPPP, true);
				}
				gConnecting = false;
				dialingThePhone = false;
				
				// Forget about the fact that we connected OT/PPP.
				MyOTPPPInfo.weConnectedPPP = false;
			}
		}
	}
		
	return (err);
}
			
/*********************************************************************************
 * SelectedConnectionMode - determine the connection mode set in the TCP/IP control
 * panel.
 *
 *	12-16-98 JDB
 *	 Added the forceRead paramter to read from the TCP file, even if cache time
 *	has not yet run out.  We really want to know the state of TCP/IP at each
 *	connection attempt.  We don't care as much at idle time for things like
 *	the next check menu item.
 *
 *	5-19-99 JDB
 *	 Added calls to the Network Setup library.  This should prevent breakage.
 *
 *	7-16-99 JDB
 *	 Use the cached connection method unless told not to
 *
 *	12-3-99 JDB
 *	 Use the cached connection method if we just read the preferences
 *********************************************************************************/
OSErr SelectedConnectionMode(unsigned long *connectionSelection, Boolean forceRead)
{
	OSErr err = noErr;
	char currentPortName[kMaxProviderNameSize];
	char junk[kMaxProviderNameSize];
	Boolean enabled;
	static uLong method;
	static uLong lastRead = 0;

	if (HaveOSX()) 
	{
		// there's no way to tell if we're dialed up or not under OS X.
		*connectionSelection = kOtherSelected;
		return (noErr);
	}
	
	// do NOT call this unless OT is installed
	if (OTIs == false) return (fnfErr);
			
	// did we just read the preferences recently?
	if (!forceRead && (lastRead>0) && ((TickCount()-lastRead) < GetRLong(TCP_PREF_REUSE_INTERVAL)))
	{
		*connectionSelection = method;
		return (noErr);
	}

	// are we falling asleep?  Then return value from the last time, no matter what.
	if (UserIdle(TICKS2MINS) && (lastRead>0))
	{
		*connectionSelection = method;
		return (noErr);
	}

#ifdef	DEBUG
	Log(LOG_TRANS,"\pReading TCP/IP preferences from the disk now.");
#endif

#ifdef	USE_NETWORK_SETUP
	if (UseNetworkSetup()) 
	{
		if (IsNetworkSetupAvailable())
		{
			err = GetConnectionModeFromDatabase(connectionSelection);

			lastRead = TickCount();
			method = *connectionSelection;
		}
		else
		{
			// we have to use the Network Setup Library, but it's not available.  
			// Assume non-PPP and non-MacSLIP connection.
			lastRead = TickCount();
			method = *connectionSelection = kOtherSelected;
		}
	}
	else
	{
#endif	
		*connectionSelection = kOtherSelected;
		
		// read the port name from the TCP/IP preference file
		err = GetCurrentPortNameFromFile(currentPortName, junk, &enabled);
		if (err == noErr)
		{
			if (StringSame(currentPortName,PPP_NAME))	
				*connectionSelection = kPPPSelected;
		}
		
		if (!err)
		{
			lastRead = TickCount();
			method = *connectionSelection;
		}
#ifdef	USE_NETWORK_SETUP
	}
#endif
		
	return (err);
}

/*******************************************************************************
 * UserIdle - see if the user has been idle.
 *******************************************************************************/
Boolean UserIdle(uLong ticks)
{
	Boolean result = false;
#ifdef	HAVE_GETLASTACTIVITY	
	static uLong idleTicks;
	ActivityInfo info;
	OSErr err = noErr;

	if (((GestaltBits(gestaltPowerMgrAttr)&(1<<gestaltPMgrDispatchExists))!=0))
	{
		info.ActivityTime = 0;
		info.ActivityType = UsrActivity;
		if (!(err=GetLastActivity(&info)))
		idleTicks = TickCount()-info.ActivityTime;
		
		if (idleTicks > ticks)
			result = true;	
	}
#endif

	return (result);
}

/*******************************************************************************
 * CanCheckPPPState - is it possible for us to check PPP's state?
 *******************************************************************************/
Boolean CanCheckPPPState(void)
{
	return !PrefIsSet(PREF_IGNORE_PPP) && (HaveTheDiseaseCalledOSX()||!gMissingNSLib);
}

/*******************************************************************************
 * CanChangePPPState - is it possible for us to change PPP's state?
 *******************************************************************************/
Boolean CanChangePPPState(void)
{
	return !PrefIsSet(PREF_IGNORE_PPP) && !gMissingNSLib && !HaveTheDiseaseCalledOSX();
}

/*******************************************************************************
 * PPPDown - is PPP installed, the selected mode of connection & disconnected?
 *******************************************************************************/
Boolean PPPDown(void)
{
	unsigned long con = 0;
	static Boolean ret = false;
	static uLong lastCheck;

	ASSERT(!PrefIsSet(PREF_IGNORE_PPP));
	
	if (HaveTheDiseaseCalledOSX())
	{
		if (TickCount()-lastCheck > GetRLong(TCP_PREF_REUSE_INTERVAL)/10+1)
		{
			Str255 host;
			
			// grab a specific host to check; if there
			// isn't one, use the mailhost
			if (!*GetRString(host,PPP_REACHABLE_HOST))
				GetPOPInfo(nil,host);
			
			// check it
			if (*host && CHostUnreachableByPPP(host+1)) ret = true;
			else ret = false;
			
			lastCheck = TickCount();
		}
	}
	else if (gHasOTPPP)
	{
		SelectedConnectionMode(&con,false);
		if (con == kPPPSelected)
		{
			if (MyOTPPPInfo.ref == 0) NewPPPControlEndpoint();
			GetPPPConnectionState(MyOTPPPInfo.ref, &con);
			if (con != kPPPStateOpened) ret = true;
		}
	}
	
	return (ret);
}

#pragma segment Main
/*********************************************************************************
 * MyPPPEndpointNotifier - notifier function for PPP endpoint events.  It directly
 * modifies the MyOTPPPInfo global structure.
 *********************************************************************************/
pascal void MyPPPEndpointNotifier(void *context, OTEventCode code, OTResult result, void *cookie)
{		
	if (code > kPPPEvent && code <= kPPPDCECallFinishedEvent) 
	{
		MyOTPPPInfo.status = 0;
		MyOTPPPInfo.result = (OTResult)cookie;
		
		if ((code == kPPPConnectCompleteEvent) && (MyOTPPPInfo.result != kOTNoError))			
			MyOTPPPInfo.event = OTPPP_MSG_STRN + (kPPPDCECallFinishedEvent + 1) - kPPPEvent + 1;
		else
			MyOTPPPInfo.event = OTPPP_MSG_STRN + code - kPPPEvent + 1;
	}
	
	if (code == kPPPDisconnectCompleteEvent)	// Disconnect has completed.
		MyOTPPPInfo.state = MyOTPPPInfo.result ? kPPPClosing : kPPPDown;
	if (code == kPPPLowerLayerDownEvent)	//remote server isn't responding	added this 11-21-97
		MyOTPPPInfo.state = kPPPDown;
	else if ((code == kPPPConnectCompleteEvent) && (MyOTPPPInfo.result == kOTNoError))			
		MyOTPPPInfo.state = kPPPUp;
}
#pragma segment TcpTrans

/*********************************************************************************
 * WaitForOTPPPDisconnect - sit and spin until the PPP state is kPPPStateInitial
 *********************************************************************************/
OSErr WaitForOTPPPDisconnect(Boolean showStatus)
{
	OSErr 			err = noErr;
	long 			ticks=TickCount();
	long 			startTicks=ticks+120;
	long 			now;
	Str255 			scratch;
	unsigned long 	PPPState;
	
	//wait for the connection to close before we start another
	if (showStatus)
	{
		GetRString(scratch, OT_PPP_DISCONNECT);
		ProgressMessage(kpMessage,scratch);
	}
	
	do
	{
		now = TickCount();
		if (now>startTicks && now-ticks>10) 
		{
			CyclePendulum();
			ticks=now;
		}
		MiniEvents();
		if (CommandPeriod) return(pppErr = userCancelled);
		err = GetPPPConnectionState(MyOTPPPInfo.ref, &PPPState);
	}
	while ((PPPState != kPPPStateInitial) && (err == noErr));
	
	if (err != noErr && err != userCancelled) err = errPPPStateUnknown;
	
	return (err);
}


/*********************************************************************************
 * OTPPPDialingInformation - retrieve the redial options from the PPP settings file.
 *
 * 	- Look in Preferences folder for lzcn/rmot, the Remote Access Connections file
 *	- Open the resource fork
 *	- Fetch 'cdia' id 128, which contains the info we need.
 *	
 *		the fourth long in this resource contains 3 or 4 if we are to redial.
 *		the fifth long contains the number of redials to do before giving up.
 *		the sixth long contains 1000*number of seconds to delay.
 *
 * If any of this fails, we continue on as if nothing happened.  Redialing just won't
 * happen.
 *
 * The spec locating the rmot preference file is cached.  This way, we can check
 * the preference file periodically, and make sure the setting hasn't changed.
 *
 * This will break in a future OT. 
 *	
 *	5-19-99 JDB
 *	 Added calls to the Network Setup library.  This should prevent breakage.
 *********************************************************************************/
OSErr OTPPPDialingInformation(Boolean *redial, unsigned long *numRedials, unsigned long *delay)
{
	OSErr err = noErr;
	short vRef = 0;
	long dirId = 0;
	CInfoPBRec hfi;
	Str31 name;
	short refNum = 0;
	Handle probe = 0;
	short oldRes;
	
	oldRes = CurResFile();
	
	//do NOT call this unless OT/PPP is installed and being used
	if (gHasOTPPP == false) return (fnfErr);
	
	*numRedials = *delay = 0;
	*redial = false;

#ifdef	USE_NETWORK_SETUP
	// grok the settings from the TCP/IP preference file using the Network Setup Library.
	if (UseNetworkSetup()) 
	{
		err = GetPPPDialingInformationFromDatabase(redial, numRedials, delay);
		return (err);
	}
#endif
		
	if (PPPprefFileSpec.name[0] == 0)	//have we not yet already located the PPP pref file?
	{
		/* Locate the PPP preferences file */
				
		//find the active Preferences folder
		err = FindFolder(kOnSystemDisk,kPreferencesFolderType,False,&vRef,&dirId);
		if (err == noErr)
		{
			hfi.hFileInfo.ioNamePtr = name;
			hfi.hFileInfo.ioVRefNum = vRef;
			SearchDirectoryForFile(&hfi, dirId, PPP_PREF_FILE_TYPE, PPP_PREF_FILE_CREATOR);
		}
	}
		
	if (PPPprefFileSpec.name[0] != 0)	//have we located the PPP pref file?
	{
		refNum = FSpOpenResFile(&PPPprefFileSpec,fsRdPerm);
		err = ResError();
		if (err == noErr && refNum >= 0)
		{	
			//this could breka in future versions of OT
			probe = Get1Resource(DIAL_RESOURCE,DIAL_RESOURCE_ID);
			err = ResError();
			if (err == noErr && probe != 0)
			{
				//this is agonna break in future versions ot fer sure
				if (((long *)*probe)[3] == 2)
				{
					*redial = false;
					*numRedials = *delay = 0;
				}
				else
				{
					*redial = true;
					*numRedials = ((long *)*probe)[4];
					*delay = ((long *)*probe)[5]/1000;
				}
			}
		}
		CloseResFile(refNum);
	}
		
	if ((err != noErr) || (PPPprefFileSpec.name[0] == 0)) err = errPPPPrefNotFound;
	
	UseResFile(oldRes);
	
	return (err);
}

/*********************************************************************************
 * SearchDirectoryForFile - Recursively search a directory for a file with a
 * given creator and file type.  This is an expensive function to call.
 *********************************************************************************/
Boolean SearchDirectoryForFile(CInfoPBRec *info, long dirToSearch, OSType type, OSType creator)
{
	short index = 1;
	OSErr err = noErr;
	Boolean static foundIt;
	
	foundIt = false;
	
	do
	{
		info->hFileInfo.ioFDirIndex = index;
		info->hFileInfo.ioDirID = dirToSearch;
		
		err = PBGetCatInfoSync(info);
		
		if (err == noErr)
		{
			//found a directory
			if ((info->hFileInfo.ioFlAttrib & ioDirMask)	!= 0)
			{
				SearchDirectoryForFile(info, info->hFileInfo.ioDirID, type, creator);
				err = noErr;
			}
			else	//found a file
			{
				if ((info->hFileInfo.ioFlFndrInfo.fdType == type &&
						info->hFileInfo.ioFlFndrInfo.fdCreator == creator))
				{
					// Found it.  Stop the search!
					foundIt = true;
					FSMakeFSSpec(info->hFileInfo.ioVRefNum, info->hFileInfo.ioFlParID, info->hFileInfo.ioNamePtr, &PPPprefFileSpec);
				}
			}
			++index;
		}
	} while (err == noErr && !foundIt);
	
	return (foundIt);
}

/*********************************************************************************
 * SpinOnWithConnectionCheck - spin until a return code is not inProgress.  Check 
 * connection while spinning.
 *********************************************************************************/
short SpinOnWithConnectionCheck(short *rtnCodeAddr,long maxTicks,Boolean allowCancel,Boolean forever)
{
	long ticks=TickCount();
	long startTicks=ticks+120;
	long now;
#ifdef CTB
	extern ConnHandle CnH;
#endif
	Boolean oldCommandPeriod = CommandPeriod;
	Boolean slow = False;
	static short slowThresh;
	
	if (!slowThresh) slowThresh = GetRLong(SPIN_LENGTH);
	
	if (allowCancel) YieldTicks = 0;
	if (allowCancel || *rtnCodeAddr==inProgress)
	{
		CommandPeriod = False;
		do
		{
			// check to see if we need ppp, but the connection is down
			if (needPPPConnection && MyOTPPPInfo.state != kPPPUp) return(errLostConnection);
					
			now = TickCount();
			if (now>startTicks && now-ticks>slowThresh) {slow = True;if (!InAThread()) CyclePendulum(); else MyYieldToAnyThread();ticks=now;}
			MiniEvents();
			if (CommandPeriod  && !forever) return(userCancelled);
			if (maxTicks && startTicks+maxTicks < now+120) break;
		}
		while (*rtnCodeAddr == inProgress);
		if (CommandPeriod) return(userCancelled);
		CommandPeriod = oldCommandPeriod;
	}
	return(*rtnCodeAddr);
}

//Some sticky stuff

/*********************************************************************************
 * GetHostByAddr - Call either TCPGetHostByAddr or OTGetHostByAddr, depending on
 * whether OT is installed or not.
 *********************************************************************************/
OSErr GetHostByAddr(struct hostInfo *hostInfoPtr,long addr)
{
	// Do we have a NAT?
	if (0x0A000000 == (addr&0xff000000) ||
			0xAC100000 == (addr&0xfff00000) ||
			0xC0A80000 == (addr&0xffff0000))
	{
		Str31 literal;
		ComposeRString(literal,NAT_FMT,addr);
		literal[*literal+1] = 0;
		strcpy(hostInfoPtr->cname,literal+1);
		hostInfoPtr->addr[1] = addr;
		hostInfoPtr->rtnCode = 0;
		return noErr;
	}
	
	if (gUseOT == true)		// OT is installed
	{
		InetHostInfo domainName;
		OSErr err = OTGetHostByAddr(addr, &domainName);
		
		if (err == noErr)	// our caller expects the hostInfoPtr to point a hostInfo struct.
		{
			short count;
			
			strcpy(hostInfoPtr->cname,domainName.name);		
			for (count = 0; count < MIN(NUM_ALT_ADDRS,kMaxHostAddrs); count ++)
				hostInfoPtr->addr[count] = domainName.addrs[count];
			hostInfoPtr->rtnCode = 0;
		}
		return (err);
	}
	ASSERT ( false );
	return unimpErr;	/* unreachable, I think */
}


/*********************************************************************************
 * GetHostByName - Call either TCPGetHostByName or OTGetHostByName, depending on
 * whether OT is installed or not.
 *
 * The caller expects hostInfoPtr to be pointing to a TCP hostInfo struct.  So
 * if we do the OT thing, point hostInfoPtr at a hostInfo struct we fill with
 * the results of the OTGetHostByName call.
 *********************************************************************************/
int GetHostByName(UPtr name, struct hostInfo **hostInfoPtr)
{
	static struct hostInfo trickCaller;
	
	if (gUseOT == true)	// OT is installed
	{
		InetHostInfo domainName;
		int err = noErr;
		short count;
		InetDomainName hostName;
		
		PtoCcpy(hostName,name);
		
		err = OTGetHostByName(hostName, &domainName);
		
		if (err == noErr)	// our caller expects the hostInfoPtr to point a hostInfo struct.
		{					
			*hostInfoPtr = &trickCaller;
			
			strcpy(trickCaller.cname,domainName.name);
			for (count = 0; count < MIN(NUM_ALT_ADDRS,kMaxHostAddrs); count ++)
				trickCaller.addr[count] = domainName.addrs[count];
			trickCaller.rtnCode = 0;
		}
		return (err);
	}
	ASSERT ( false );
	return unimpErr;	/* unreachable, I think */
}

/*********************************************************************************
 * PPPIsMostDefinitelyUpAndRunning - return true if we're connected with PPP, or
 *	it's not an issue.
 *********************************************************************************/
Boolean PPPIsMostDefinitelyUpAndRunning(void)
{
	Boolean connected = true;
	unsigned long con = 0;
	
	if (gHasOTPPP)
	{
		SelectedConnectionMode(&con,false);
		if ((con == kPPPSelected) && (MyOTPPPInfo.state != kPPPUp)) connected = false;
	}
	
	return (connected);
}

/*********************************************************************************
 * UpdateCachedTCPIPPrefInfo - read from the preference files or NS Library now
 *********************************************************************************/
void UpdateCachedConnectionMethodInfo(void)
{
	unsigned long con = 0;
	static uLong method;
	
	if (SelectedConnectionMode(&con, true)==noErr)
	{
		// return true if the method has changed
		if (con != method)
		{
			method = con;
			gUpdateTPWindow = true;	// update the TP window
		}
	}
}

/*********************************************************************************
 * NeedToUpdateTP - do we need to adjust the next check time in the TP window?
 *********************************************************************************/
Boolean NeedToUpdateTP(void)
{
	Boolean updateIt = false;
	
	if (gUpdateTPWindow)
	{
		updateIt = true;
		gUpdateTPWindow = false;
	}
	
	return (updateIt);
}

/*********************************************************************************
 * AutoCheckOKWithDBRead - read from the preference files or NS Library and see
 *	if an autocheck is appropriate.
 *********************************************************************************/
Boolean AutoCheckOKWithDBRead(Boolean updatePers)
{
	Boolean result = false;
	OSErr err = noErr;
	
	// are we set up to not check when not connected?
	if (gUseOT && !PrefIsSet(PREF_IGNORE_PPP) && PrefIsSet(PREF_PPP_NOAUTO))
	{
		// make sure we're really, truly connected
		UpdateCachedConnectionMethodInfo();
		result = AutoCheckOK();
		
		// we're not.  Tell this personality to cram it.
		if (!result && updatePers) 
		{
			PersSkipNextCheck();
			gUpdateTPWindow = true;
		}
	}
	else
	{
		// not set to not check when not connected.  Do the normal thing.
		result = AutoCheckOK();
	}	
	
	return (result);
}

#ifdef ESSL
// Declare the routine to set up the TransVector for doing SSL
TransVector ESSLSetupVector(TransVector theTrans);
#endif

TransVector GetTCPTrans()
{
	TransVector theTrans;
	
	theTrans = OTTCPTrans;
#ifdef ESSL
	return ESSLSetupVector(theTrans);
#else
	return theTrans;
#endif
}

/************************************************************************
 * TcpFastFlush - run through the queue, killing off defunct streams
 * Call KillDeadKyTStreams if we happen to be using open transport.
 ************************************************************************/
void TcpFastFlush(Boolean destroy)
{
	static Boolean flushing = false;
	
	// are we already flushing streams from somewhere?
	if (flushing) return;
	
	// kill defunct streams
	flushing = true;
	
	if (gUseOT) 
		KillDeadMyTStreams(destroy);
	
	// we're done flushing streams for now.
	flushing = false;
}


/**********************************************************************
 * CheckConnectionSettings - Attempt to connect to a host/port pair.
 **********************************************************************/
OSErr CheckConnectionSettings ( UPtr host, long port, StringPtr errorMessage ) {
	OSErr err = noErr;
	TransStream stream = NULL;
	Boolean oldPref = PrefIsSet(PREF_IGNORE_PPP);

	
//	Init the TransStream
	if ( noErr == ( err = NewTransStream ( &stream ))) {
	
	//	See if the host is there....
		SetPref(PREF_IGNORE_PPP,YesStr);
		err = ConnectTrans ( stream, host, port, true, GetRLong(SHORT_OPEN_TIMEOUT));
		SetPref(PREF_IGNORE_PPP,oldPref ? YesStr : NoStr);

	//	Grab an error message if we failed
		if ( noErr != err && errorMessage != NULL ) {
			short realSettingsRef = SettingsRefN;

			SettingsRefN = GetMainGlobalSettingsRefN();		
			OTTELo ( errOpenStream, stream->streamErr, errorMessage );
			SettingsRefN = realSettingsRef;
			}
			
	//	That's all we wanted.  Cleanup.
		if ( noErr == err )
			DestroyTrans(stream);
		ZapTransStream ( &stream );
		}
	
	return err;
	}

