home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Otherware
/
Otherware_1_SB_Development.iso
/
mac
/
util
/
comm
/
news102.sit
/
NewsWatcher
/
source
/
ftplow.c
< prev
next >
Wrap
Text File
|
1991-04-03
|
15KB
|
598 lines
/*----------------------------------------------------------
#
# NewsWatcher - Macintosh NNTP Client Application
#
# Written by Steven Falkenburg
# ⌐1990 Apple Computer, Inc.
#
#-----------------------------------------------------------
#
# ftplow.c
#
# This file contains the low-level implementation of the
# file transfer protocol. This protocol is implemented
# using the TCP protocol, from calls in TCPHi.c and
# TCPRoutines.c
#
#-----------------------------------------------------------*/
#include "compat.h"
#include <string.h>
#include <stdio.h>
#ifdef PROTOS
#include <Types.h>
#include <Memory.h>
#include <OSUtils.h>
#include <Packages.h>
#include <Desk.h>
#include <CursorCtl.h>
#include <Strings.h>
#include <Errors.h>
#include <StdIO.h>
#include <Lists.h>
#endif
#include "MacTCPCommonTypes.h"
#include "TCPPB.h"
#include "TCPHi.h"
#include "TCPRoutines.h"
#include "nntp.h"
#include "FTPLow.h"
#include "netstuff.h"
#include "miscstuff.h"
/* local protos */
OSErr SayHello(unsigned long connID);
OSErr UserAuth(unsigned long connID,char *userID,char *password);
OSErr SendCommand(unsigned long connID,char *command,Ptr *response,unsigned short *respLen);
OSErr MakeDataStream(unsigned long *dataConnID,tcp_port lPort,TCPiopb **pBlock);
OSErr GetDirectData(unsigned long dataConnID,Ptr *directory,TCPiopb *pBlock);
OSErr SendFileData(unsigned long dataConnID,char *fileName,char *data,TransType tType,long size,TCPiopb *pBlock);
OSErr RemoveDataStream(unsigned long dataConnID);
OSErr SendPortComm(unsigned long dataConnID, short portNum);
tcp_port GetPortNum(void);
OSErr FTPTextMode(unsigned long connID);
void FixCRLF(char *data, long *length);
void AddCRLF(char **data, long *length);
#define cSleepTime 20L
#define cWaitForOpen 120L
#define cRBufLen 32768L
#define cDataLen 32768
#define cRBufDataLen 65535L
#define cXferBlockSize 32768
#define cSendBlockSize 16384
#define cControlPort 21
#define cInvalidHost -1
#define cConnErr -2
#define cAccessErr -3
#define cNotCompErr -4
#define kConnectionTimeOut 25
ProcPtr gStatusProc; /* status procedure for callbacks */
/* SayHello is called after a control-port connection has been established.
It checks the status of the connection.
*/
OSErr SayHello(unsigned long connID)
{
OSErr err;
Ptr dataPtr;
unsigned short dataLen = cDataLen;
dataPtr = MyNewPtr((unsigned long)cDataLen);
if (MyMemErr() != noErr)
return MyMemErr();
if ((err = RecvData(connID,dataPtr,&dataLen,false)) == noErr) {
err = (strncmp((char *)dataPtr,"220",3)==0) ? noErr : cConnErr;
}
MyDisposPtr(dataPtr);
return err;
}
/* UserAuth is called once a control connection has been opened and
checked. It sends the user's name and password across the network
for authentication. If the user is sucessfully authenticated, a
code of noErr is returned.
*/
OSErr UserAuth(unsigned long connID,char *userID,char *password)
{
OSErr err;
Ptr sendBuff;
Ptr respBuff;
unsigned short respLen;
sendBuff = MyNewPtr((unsigned long) (10+(strlen(userID)>strlen(password) ? strlen(userID) : strlen(password))));
if (MyMemErr() != noErr)
return MyMemErr();
strcpy((char *)sendBuff,"USER ");
strcat((char *)sendBuff,userID);
strcat((char *)sendBuff,CRLF);
if ((err = SendCommand(connID,sendBuff,&respBuff,&respLen)) == noErr) {
if ((err = (strncmp((char *)respBuff,"331",3)==0) ? noErr : cAccessErr) == noErr) {
MyDisposPtr(respBuff);
strcpy((char *)sendBuff,"PASS ");
strcat((char *)sendBuff,password);
strcat((char *)sendBuff,CRLF);
if ((err = SendCommand(connID,sendBuff,&respBuff,&respLen)) == noErr) {
err = (strncmp((char *)respBuff,"230",3)==0) ? noErr : cAccessErr;
}
}
}
MyDisposPtr(sendBuff);
MyDisposPtr(respBuff);
return err;
}
/* SendCommand is used to send a command along a TCP connection and get a
response back. It is useful in handshaking protocols such as the one
used to set up transfers for FTP.
*/
OSErr SendCommand(unsigned long connID,char *command,Ptr *response,unsigned short *respLen)
{
OSErr err;
unsigned short respLeft,oneLen;
char *bufPtr;
respLeft = (unsigned short) cDataLen - 1;
*respLen = 0;
bufPtr = *response = MyNewPtr((unsigned long)cDataLen);
*bufPtr = '\0';
if (MyMemErr() != noErr)
return MyMemErr();
if ((err = SendData(connID,(Ptr)command,(unsigned short)strlen(command),false)) == noErr)
do {
oneLen = respLeft;
err = RecvData(connID,bufPtr,&oneLen,false);
if (err==noErr) {
bufPtr += oneLen;
*bufPtr = '\0';
*respLen += oneLen;
respLeft -= oneLen;
}
}
while (err==noErr && respLeft > 0 && *(bufPtr-1)!=LF);
if (err == noErr)
(gStatusProc)(*response);
return err;
}
/* MakeDataStream is called when a data transfer is about to be started.
It sets up a secondary TCP communications channed between the client
and server for the transfer of data.
*/
OSErr MakeDataStream(unsigned long *dataConnID,tcp_port localPort,TCPiopb **pBlock)
{
OSErr err;
Ptr recvBPtr;
unsigned long recvBLen = cRBufDataLen;
long remoteHost = 0;
short remotePort = 0;
recvBPtr = MyNewPtr(recvBLen);
if (MyMemErr() != noErr)
return MyMemErr();
err = CreateStream(dataConnID,recvBLen);
if (err == noErr)
AsyncWaitForConnection(*dataConnID,kConnectionTimeOut,localPort,remoteHost,
remotePort,pBlock);
return err;
}
/* GetDirectData is called to receive textual data, such as the response
from an 'ls' command. The data received is passed back in an allocated
pointer.
*/
OSErr GetDirectData(unsigned long dataConnID,Ptr *directory,TCPiopb *pBlock)
{
OSErr err;
unsigned short length = cXferBlockSize;
long remoteHost;
short remotePort;
Ptr transferBlock;
Size totalLength = 0;
OSErr err2;
Handle theHndl;
while (pBlock->ioResult > 0)
GiveTime(cSleepTime);
err = AsyncGetConnectionData(pBlock,&remoteHost,&remotePort);
if (err == noErr) {
theHndl = MyNewHandle((unsigned long)cXferBlockSize);
if (MyMemErr() != noErr)
return MyMemErr();
HLock(theHndl);
*directory = transferBlock = *theHndl;
**directory = '\0';
do {
length = cXferBlockSize;
err = RecvData(dataConnID,transferBlock,&length,false);
if (err == noErr || err == connectionClosing) {
totalLength += length;
HUnlock(theHndl);
MySetHandleSize(theHndl,totalLength+cXferBlockSize);
HLock(theHndl);
*directory = *theHndl;
if ((err2 = MyMemErr()) != noErr) {
MyDisposHandle(theHndl);
return err2;
}
transferBlock += (unsigned long) length;
}
GiveTime(cSleepTime);
} while (err == noErr);
if (err == connectionClosing) {
err = noErr;
if (totalLength)
FixCRLF(*directory,&totalLength);
*directory = MyNewPtr(totalLength);
if ((err = MyMemErr()) == noErr)
BlockMove(*theHndl,*directory,totalLength);
}
HUnlock(theHndl);
MyDisposHandle(theHndl);
}
return err;
}
/* SendFileData is called once a transfer has been initiated to send data
from a client to a server. The data is sent out along the secondary
data channel which has been negotiated between the hosts.
*/
/* warning-- char *data is disposed by this routine!!!!!!!!! */
OSErr SendFileData(unsigned long dataConnID,char *fileName,char *data,
TransType tType,long size,TCPiopb *pBlock)
{
#pragma unused (fileName,tType)
OSErr err;
long remoteHost;
short remotePort;
long index;
unsigned short sendLen;
/* wait for connect to complete */
while (pBlock->ioResult > 0)
GiveTime(cSleepTime);
/* get completion code from data connection opening */
err = AsyncGetConnectionData(pBlock,&remoteHost,&remotePort);
if (err == noErr) {
AddCRLF(&data,&size);
index = 0;
while (index < size && err==noErr) {
sendLen = (unsigned short)cSendBlockSize;
if ((index+(unsigned long)sendLen) > size)
sendLen = (unsigned short)size-(unsigned short)index;
err = SendData(dataConnID,data+index,sendLen,false);
index += cSendBlockSize;
}
MyDisposPtr(data);
}
return err;
}
/* RemoveDataStream is called once a data transfer has been completed.
It shuts down the secondary data channel between the client and
server which was used solely for one data transfer.
*/
OSErr RemoveDataStream(unsigned long dataConnID)
{
OSErr err;
CloseConnection(dataConnID);
err = ReleaseStream(dataConnID);
return err;
}
/* GetPortNum is a routine which picks a random port number for use in data
transfers.
*/
tcp_port GetPortNum(void)
{
return (40000 + (((unsigned long) TickCount()) & 0x3fff)); /* get a random port */
}
/* SendPortComm is called to send the 'PORT' command, telling the server which
port is to be used for the transfer of data between server and client.
*/
OSErr SendPortComm(unsigned long dataConnID, short portNum)
{
OSErr err;
Ptr respBuff;
unsigned short respLen;
ip_addr myNetNum;
char commandStr[40];
if ((err = GetMyIP(&myNetNum)) == noErr) {
sprintf(commandStr,"PORT %lu,%lu,%lu,%lu,%u,%u",
( myNetNum >> 24), /* converts addr to dot notation */
((myNetNum & 0x00FF0000) >> 16),
((myNetNum & 0x0000FF00) >> 8),
( myNetNum & 0x000000FF),
( portNum >> 8),
( portNum & 0x00FF));
strcat(commandStr,CRLF);
err = SendCommand(dataConnID,commandStr,&respBuff,&respLen);
MyDisposPtr(respBuff);
}
return err;
}
/* FTPTextMode is called to switch the mode of the data transfer to text mode.
*/
OSErr FTPTextMode(unsigned long connID)
{
Ptr respBuff;
unsigned short respLen;
OSErr err;
char commandStr[256];
strcpy(commandStr,"TYPE A");
strcat(commandStr,CRLF);
if ((err = SendCommand(connID,commandStr,&respBuff,&respLen)) == noErr) {
err = (strncmp((char *)respBuff,"200",3)==0);
}
MyDisposPtr(respBuff);
return err;
}
/* FixCRLF removes the LF from CRLF pairs received on the data connection.
*/
void FixCRLF(char *data, long *length)
{
register char *source,*dest;
if (*length > 0) {
source = dest = data;
while ((source - data) < (*length-1)) {
if (*source == LF)
source++;
*dest++ = *source++;
}
if (*source != LF && (source - data) < *length)
*dest++ = *source++;
*length = dest - data;
}
}
/* AddCRLF adds a LF to each CR for data to be sent to the remote host
in text (ascii) mode.
*/
void AddCRLF(char **data, long *length)
{
register char *curOld,*curNew,*newData;
newData = curNew = MyNewPtr((unsigned long)cSendBlockSize*2);
if (MyMemErr() != noErr)
return;
curOld = *data;
if (*curOld == LF)
curOld++;
while ( (curOld-*data) < *length ) {
*curNew++ = *curOld++;
if (*(curOld-1) == CR && *curOld != LF)
*curNew++ = LF;
}
*length = curNew-newData;
MyDisposPtr(*data);
*data = newData;
}
/*------------------------------------------------------------*/
/* FTPInit is called to initialize the necessary drivers for FTP connections.
In addition, it registers a callback routine for status messages.
*/
OSErr FTPInit(ProcPtr statusproc)
{
gStatusProc = statusproc;
return InitNetwork();
}
/* FTPFinish is called when all FTP transactions have been completed.
In this implementation, the routine actually does nothing.
*/
OSErr FTPFinish(void)
{
return noErr;
}
/* FTPConnect sets up an FTP control connection between the local machine
and a remote FTP server.
*/
OSErr FTPConnect(unsigned long *connID,char *address,char *userID,char *password)
{
ip_addr connAddr;
tcp_port lPort = 0;
OSErr err = cInvalidHost;
if (((err = ConvertStringToAddr(address,&connAddr)) == noErr) &&
((err = CreateStream(connID,cRBufLen)) == noErr)) {
if (((err = OpenConnection(*connID,connAddr,cControlPort,kConnectionTimeOut)) == noErr) &&
((err = SayHello(*connID)) == noErr))
err = UserAuth(*connID,userID,password);
if (err != noErr)
FTPDisconnect(*connID);
}
return err;
}
/* FTPDisconnect shuts down an FTP connection which was opened using FTPConnect.
*/
OSErr FTPDisconnect(unsigned long connID)
{
OSErr err;
Ptr recvPtr;
unsigned short respLen;
char commandStr[256];
strcpy(commandStr,"QUIT");
strcat(commandStr,CRLF);
SendCommand(connID,commandStr,&recvPtr,&respLen);
MyDisposPtr(recvPtr);
CloseConnection(connID);
err = ReleaseStream(connID);
return err;
}
/* FTPViewFile is called to get a text-mode file from a remote machine
and store the contents of the file in a pointer allocated within
the call.
*/
OSErr FTPViewFile(unsigned long connID,Ptr *file,char *fileName)
{
OSErr err = noErr;
unsigned long dataConnID;
Ptr respBuff,dataPtr;
unsigned short respLen;
short dataPort;
unsigned short dataLen = cDataLen;
char *command;
TCPiopb *pBlock;
char compStr[256];
strcpy(compStr,CRLF);
strcat(compStr,"226");
GiveTime(cSleepTime);
command = MyNewPtr(256);
if (MyMemErr() != noErr)
return MyMemErr();
strcpy(command,"RETR ");
strcat(command,fileName);
strcat(command,CRLF);
FTPTextMode(connID);
dataPort = GetPortNum();
StatusWindow("Opening Data Connection...",-1);
GiveTime(0);
MakeDataStream(&dataConnID,dataPort,&pBlock);
StatusWindow("Sending PORT Command...",-1);
GiveTime(0);
if ((err = SendPortComm(connID,dataPort)) == noErr) {
StatusWindow("Requesting File...",-1);
GiveTime(0);
if (((err = SendCommand(connID,command,&respBuff,&respLen)) == noErr) &&
((err = (strncmp((char *)respBuff,"150",3)==0) ? noErr : cNotCompErr) == noErr) &&
(StatusWindow("Receiving File...",-1)) &&
((err = GetDirectData(dataConnID,file,pBlock)) == noErr)) {
if (!(strstr(respBuff,compStr))) {
dataPtr = MyNewPtr((unsigned long)cDataLen);
if (MyMemErr() == noErr) {
err = RecvData(connID,dataPtr,&dataLen,false);
MyDisposPtr(dataPtr);
}
else err = MyMemErr();
}
}
MyDisposPtr(respBuff);
}
RemoveDataStream(dataConnID);
MyDisposPtr(command);
return err;
}
/* FTPPutFile sends a buffer from the local machine to the remote host.
The transfer is completed in text mode.
*/
OSErr FTPPutFile(unsigned long connID,char *fileName,char *data,long size)
{
OSErr err = noErr;
unsigned long dataConnID;
Ptr respBuff;
unsigned short respLen;
tcp_port dataPort;
char *getCommand;
unsigned short dataLen = cDataLen;
TCPiopb *pBlock;
GiveTime(cSleepTime);
FTPTextMode(connID);
getCommand = MyNewPtr(256);
if (MyMemErr() != noErr)
return MyMemErr();
strcpy(getCommand,"STOR ");
strcat(getCommand,fileName);
strcat(getCommand,CRLF);
dataPort = GetPortNum();
StatusWindow("Opening Data Connection...",-1);
GiveTime(0);
MakeDataStream(&dataConnID,dataPort,&pBlock);
if ((err = SendPortComm(connID,dataPort)) == noErr) {
StatusWindow("Requesting Transfer...",-1);
GiveTime(0);
if (((err = SendCommand(connID,getCommand,&respBuff,&respLen)) == noErr) &&
((err = (strncmp((char *)respBuff,"150",3)==0) ? noErr : cNotCompErr) == noErr)) {
StatusWindow("Sending Data...",-1);
GiveTime(0);
err = SendFileData(dataConnID,fileName,data,text,size,pBlock);
}
MyDisposPtr(respBuff);
}
RemoveDataStream(dataConnID);
MyDisposPtr(getCommand);
SetCursor(&QDARROW);
return err;
}