static char rcsid[] = "ccache_ftp.c,v 1.17 1996/01/04 04:12:23 duane Exp";
/******************************************************************************
 *
 * FILE
 *      ftp.c
 *
 * DESCRIPTION
 *      Contains code for ftp client, these calls are used by processes
 *	desiring ftp services
 *	Contains functions for communicating with FTP server (i.e. socket
 *	level control of data transfer and request)
 *
 * FUNCTIONS
 *	FTPInit()
 *	Login()
 *	Disconnect()
 *	Retrieve()
 *
 *	InitSocket()
 *	ReadServReply()
 *	SendMessage()
 *	ReadOutText()
 *	CheckServReply()
 *	PrepareDataConnect()
 *	RetrieveFile()	
 *	WriteToSocket()
 *
 *  David Merkel & Mark Peterson, University of Colorado - Boulder, July 1994  
 *
 *  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
******************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include "ccache.h"

extern int select();		/* UNIX sys call */

/******************************************************************************
** function name: FTPInit()
**
** preconditions: hostAddr should be a valid IP address.  portNum is a TCP
**	port to connect to.  errorReply has been allocated by caller.
**
** postconditions: On success a socket descriptor is returned to caller
**	that is connect to hostAddr, portNum.  The value is a negative
**	error otherwise.
**
** author/credits: David Merkel, 4/94
**
******************************************************************************/

int FTPInit(hostAddr, portNum, errorReply)
     u_long hostAddr;		/* address to connect to */
     int portNum;		/* TCP port */
     char *errorReply;		/* string for returning FTP server reply */
{
	int theSocket;		/* socket descriptor to return */
	int error;
	/* string to get FTP server reply */
	char servReply[SERV_REPLY_LENGTH];

	/* initialize socket by connecting to hostAddr, portNum */
	theSocket = InitSocket(hostAddr, portNum);
	if (theSocket < 0)
		return (INIT_SOCKET_ERR);

	/* read FTP server reply */
	error = ReadServReply(theSocket, servReply);
	if (error != NO_ERROR)
		return (error);

	/* check to see if we can continue */
	error = CheckServReply(CONNECT_CHK, servReply);

	/* error found, return to caller */
	if (error != NO_ERROR) {
		memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
		return (error);
	}
	/* no errors, return socket descriptor */
	return (theSocket);
}


/******************************************************************************
** function name: Login()
**
** preconditions: userName and password are valid (i.e. allocated), theSocket
**	is a socket descriptor that has been connected to an FTP server,
**	firstUse indicates if this is the first login attempted on theSocket,
**	and errorReply is preallocated by caller
**
** postconditions: on success userName has been logged in to FTP server and
**	may begin FTP operations.  A negative error value is returned
**	on failure.  If success, the mode is STREAM, and the type
**	is IMAGE (see rfc959).
**
** author/credits: David Merkel, 4/94
**
******************************************************************************/

int Login(userName, password, theSocket, firstUse, errorReply)
     char *userName;		/* user to log into FTP server */
     char *password;		/* password of user */
     int theSocket;		/* socket descriptor to use for communication */
     Boolean firstUse;		/* is this the first login on theSocket? */
     char *errorReply;		/* for returning server replies */
{
	int error;
	char servReply[SERV_REPLY_LENGTH];	/* for storing server replies */
	char modeCode[2], typeCode[2];	/* for setting mode and type */


#if DEBUG > 4
	printf("Login: '%s' @ '%s'   %s\n", userName, password, firstUse ? "TRUE" : "FALSE");
#endif
	/* we've already used this socket before */
	if (!firstUse) {
		/* must send 'REIN' message to reset state of login on
		 * FTP server
		 */
		error = SendMessage(REINIT, NULL, theSocket);
		if (error != NO_ERROR)
			return (error);

		/* read reply for REIN command */
		error = ReadServReply(theSocket, servReply);
		if (error != NO_ERROR)
			return (error);

		/* check reply for REIN command */
		error = CheckServReply(CONNECT_CHK, servReply);
		if (error != NO_ERROR) {
			memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
			return (error);
		}
	}
	/* send USER command */
	error = SendMessage(USER, userName, theSocket);
	if (error != NO_ERROR)
		return (error);

	error = ReadServReply(theSocket, servReply);
	if (error != NO_ERROR)
		return (error);

	/* check for errors on USER reply */
	error = CheckServReply(USER_CHK, servReply);
	if (error == NO_PASS_REQ)
		return (NO_ERROR);
	else if (error != NO_ERROR) {
		memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
		return (error);
	}
	/* finish login sequence with PASS */
	error = SendMessage(PASSWD, password, theSocket);
	if (error != NO_ERROR)
		return (error);

	error = ReadServReply(theSocket, servReply);
	if (error != NO_ERROR)
		return (error);

	/* check for errors */
	error = CheckServReply(PASSWD_CHK, servReply);
	if (error != NO_ERROR) {
		memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
		return (error);
	}
	/* if this is first use, setup STREAM mode, IMAGE data type */
	if (firstUse) {
		modeCode[0] = STREAM;
		modeCode[1] = '\0';

		/* send MODE message */
		error = SendMessage(MODE, modeCode, theSocket);
		if (error != NO_ERROR)
			return (error);

		error = ReadServReply(theSocket, servReply);
		if (error != NO_ERROR)
			return (error);

		/* check MODE reply */
		error = CheckServReply(MODE_CHK, servReply);
		if (error != NO_ERROR) {
			memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
			return (error);
		}
		typeCode[0] = IMAGE;
		typeCode[1] = '\0';

		/* send TYPE message */
		error = SendMessage(TYPE, typeCode, theSocket);
		if (error != NO_ERROR)
			return (error);

		error = ReadServReply(theSocket, servReply);
		if (error != NO_ERROR)
			return (error);

		/* check TYPE reply */
		error = CheckServReply(TYPE_CHK, servReply);
		if (error != NO_ERROR) {
			memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
			return (error);
		}
	}
	return (NO_ERROR);
}


/******************************************************************************
** function name: Disconnect()
**
** preconditions: errorReply is preallocated for return of FTP server errors,
**	theSocket is connected to an FTP server.
**
** postconditions: theSocket has been disconnected from the FTP server.  If
**	an error occurred the result code is returned in errorReply
**
** author/credits: David Merkel, 4/94
**
******************************************************************************/

int Disconnect(theSocket, errorReply)
     int theSocket;		/* socket identifier */
     char *errorReply;		/* string for return of reply codes */
{
	char servReply[SERV_REPLY_LENGTH];	/* to check reply codes */
	int error;

	/* Tell FTP server we're disconnecting */
	error = SendMessage(DISCONNECT, NULL, theSocket);
	if (error != NO_ERROR) {
		close(theSocket);
		return (error);
	}
	/* read its reply */
	error = ReadServReply(theSocket, errorReply);

	/* if no reply, continue disconnect anyway */
	if (error == NO_REPLY_PRESENT) {
		close(theSocket);
		return (NO_ERROR);
	}
	/* return error code to caller */
	memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
	close(theSocket);
	return (SERV_REPLY_ERROR);
}


/******************************************************************************
** function name:  Retrieve()
**
** preconditions:  fileName is name of file to retrieve, theSocket is 
**	the control connection, dataSocket is a socket for data retrieval,
**	isListening tells us if theSocket is already listening for a
**	data connection, sendPort indicates whether or not to use FTP port 
**	command, errorReply is preallocated for return of FTP error codes,
**	data is the returns the retrieved data (preallocated by caller)
**
** postconditions: 
**	File returned inside of data.
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

int Retrieve(fileName, theSocket, dataSocket, isListening, sendPort, errorReply, data)
     char *fileName;		/* name of file to retrieve */
     int theSocket;		/* control connection */
     int dataSocket;		/* data connection (not required) */
     Boolean isListening;	/* theSocket is already listening */
     Boolean sendPort;		/* use PORT command */
     char *errorReply;		/* return error codes */
     DataReturn *data;		/* data storage struct */
{
	extern int gethostname();	/* unix system call */
	struct sockaddr_in theAddr;	/* for use in PORT */
	struct hostent *theEntry = NULL;	/* for use in PORT */

	/* return strings for error codes and send strings for FTP PORT
	 * command
	 */
	char servReply[SERV_REPLY_LENGTH];
	char portMessage[PORT_MESSAGE_LENGTH];
	char hostName[HOST_NAME_LENGTH];

	char *port, *host;	/* for use in PORT */
	int error;
	int newSocket;		/* new data socket */
	int addrLen = sizeof(theAddr);

	/* if using port command or default method with no listen, prepare
	 * a new data connection
	 */
	if ((sendPort) || ((!sendPort) && (!isListening))) {
		newSocket = PrepareDataConnect(theSocket, sendPort);
		if (newSocket < 0)
			return (newSocket);
	} else
		newSocket = dataSocket;

	/* using port command, construct command string */
	if (sendPort) {
		memset(&theAddr, '\0', addrLen);

		/* get host address and port number to tell FTP server where
		 * to send data 
		 */
		if (getsockname(newSocket, (struct sockaddr *) &theAddr,
			&addrLen) < 0)
			return (GET_SOCKNAME_ERR);

		if (gethostname(hostName, HOST_NAME_LENGTH) < 0)
			return (GET_HOSTNAME_ERR);
		theEntry = gethostbyname(hostName);
		if (!theEntry)
			return (GET_HOSTBYNAME_ERR);

		host = (char *) theEntry->h_addr;
		port = (char *) &theAddr.sin_port;

		/* set up final portMessage string */
		sprintf(portMessage, "%d,%d,%d,%d,%d,%d", (u_char) host[0],
		    (u_char) host[1], (u_char) host[2],
		    (u_char) host[3], (u_char) port[0],
		    (u_char) port[1]);

		/* send PORT command */
		error = SendMessage(PORT, portMessage, theSocket);
		free(theEntry);
		if (error != NO_ERROR)
			return (error);

		/* read reply and check for errors */
		error = ReadServReply(theSocket, servReply);
		if (error != NO_ERROR)
			return (error);

		error = CheckServReply(PORT_CHK, servReply);
		if (error != NO_ERROR) {
			memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
			return (error);
		}
	} else
		free(theEntry);

	/* send retrieval command */
#if DEBUG > 4
	printf("Retrieving: %s\n", fileName);
#endif
	error = SendMessage(RETRIEVE, fileName, theSocket);
	if (error != NO_ERROR)
		return (error);

	/* check for errors */
	error = ReadServReply(theSocket, servReply);
	if (error != NO_ERROR)
		return (error);

	error = CheckServReply(RETRIEVE_CHK, servReply);
	if (error != NO_ERROR) {
		memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
		return (error);
	}
	/* retrieve the data over the data socket */
	error = RetrieveFile(newSocket, data);
	if (error < 0)
		return (error);

	/* check for errors */
	error = ReadServReply(theSocket, servReply);
	if (error != NO_ERROR)
		return (error);

	error = CheckServReply(RETRIEVE_CHK, servReply);
	if (error != NO_ERROR) {
		memcpy(errorReply, servReply, SERV_REPLY_LENGTH);
		return (error);
	}
	/* might need to close new data socket */
	if (sendPort) {
		close(newSocket);
		return (NO_ERROR);
	} else
		return (newSocket);
}


/******************************************************************************
** function name: InitSocket()
**
** preconditions: hostAddr contains the address of the host machine running
**      the FTP server, portNum is the number of the TCP port on
**	the machine specified by hostName to connect to
**
** postconditions: socket for client has been created and connection to 
**	FTP server established
**
** author/credits: David Merkel, 4/94
**
******************************************************************************/

int InitSocket(hostAddr, portNum)
     u_long hostAddr;		/* name of host to connect to */
     int portNum;		/* TCP port on host to connect to */
{
	int theSocket;
	struct sockaddr_in theAddr;
	struct hostent *theEntry;

	/* zero out theAddr structure */
	memset(&theAddr, '\0', sizeof(theAddr));

	/* get a socket */
	theSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (theSocket < 0)
		return (GET_SOCKET_ERR);

	theAddr.sin_family = AF_INET;
	theAddr.sin_port = htons(portNum);

	/* get hostent for hostAddr */
	theEntry = gethostbyaddr((char *) &hostAddr, sizeof(u_long), AF_INET);
	if (theEntry == NULL)
		return (GETHOST_ERR);

	/* copy host information into theAddr */
	memcpy(&theAddr.sin_addr.s_addr, theEntry->h_addr, theEntry->h_length);

	/* establish connection */
	if (connect(theSocket, (struct sockaddr *) &theAddr, sizeof(theAddr)) < 0)
		return (CONNECT_ERR);

	return (theSocket);
}


/******************************************************************************
** function name: ReadServReply()
**
** preconditions: theSocket is the control connection for an FTP session,
**	servReply is preallocated by caller
**
** postconditions: an FTP reply has been read from theSocket and returned in
**	servReply, or an error occurred (i.e. timeout) and a negative error
**	code is returned to caller.
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

int ReadServReply(theSocket, servReply)
     int theSocket;		/* FTP control connection */
     char *servReply;		/* storage for FTP reply */
{
	char servText;		/* for checking end of reply */
	struct timeval timeout;	/* for timeout on looking for
				 * reply */
	int i = 0, index = 0;
	int readLength, selectResult;
	Boolean done = FALSE;	/* loop control */
	fd_set dataDetect;	/* detect data on theSocket */

	timeout.tv_usec = 0;

	/* checking for data on control connection */
	while ((!done) && (i < READ_TIMEOUT)) {
		FD_ZERO(&dataDetect);
		FD_SET(theSocket, &dataDetect);

		timeout.tv_sec = timeout.tv_usec = 0;

		/* poll with select and see if data on theSocket */
		selectResult = select(theSocket + 1, &dataDetect,
		    (fd_set *) 0, (fd_set *) 0,
		    &timeout);

		/* if data present */
		if (FD_ISSET(theSocket, &dataDetect)) {
			/* start reading server reply */
			readLength = read(theSocket,
			    servReply + index, SERV_REPLY_LENGTH - index);
			if (readLength == 0)
				return (NO_REPLY_PRESENT);

			/* walk to next index for subsequent reads */
			index += readLength;
			if (index == SERV_REPLY_LENGTH)
				done = TRUE;
			i = 0;
		}
		/* no data on line, might not be here yet, wait 1 second
		 * and then continue 
		 */
		else {
			timeout.tv_sec = 1;
			timeout.tv_usec = 0;
			selectResult = select(theSocket + 1, &dataDetect,
			    (fd_set *) 0, (fd_set *) 0,
			    &timeout);

			i++;
		}

	}

	/* we got our reply, now we must parse out extra text attached
	 * to reply
	 */
	if (read(theSocket, &servText, 1)) {
		/* ReadOutText empties theSocket of extra reply data */
		if (servText == MULTI_LINE_CODE)
			ReadOutText(theSocket, TRUE, servReply);
		else
			ReadOutText(theSocket, FALSE, NULL);
	}
	return (NO_ERROR);
}


/******************************************************************************
** function name: WriteToSocket()
**
** preconditions: theSocket is a valid socket, and outBuf is a preallocated
**      char * holding data to be written to theSocket.
**
** postconditions: The data in outBuf has been written to theSocket, and the
**      total number of bytes written to theSocket has been returned to the
**      caller.
**
** author/credits: Code written by W. Richard Stevens,
**      from UNIX Network Programming, c1990, Prentice Hall, pp. 279-280
**
******************************************************************************/

int WriteToSocket(theSocket, outBuf, numOfBytes)
     int theSocket;		/* socket to write to */
     char *outBuf;		/* the data to write to the socket */
     int numOfBytes;		/* number of bytes inside of outBuf */
{
	int bytesLeft, bytesWritten, i = 0;

	bytesLeft = numOfBytes;
	while (bytesLeft > 0) {
		bytesWritten = write(theSocket, &outBuf[i], bytesLeft);
		if (bytesWritten <= 0)
			return (bytesWritten);

		bytesLeft -= bytesWritten;
		i += bytesWritten;
	}
	return (numOfBytes - bytesLeft);
}

/******************************************************************************
** function name: SendMessage()
**
** preconditions: msgType is preallocated, containing the type of message to
**	send, argument (if needed) is preallocated and constructed by
**	caller, theSocket is control connection for FTP session 
**
** postconditions: a message of msgType with argument has been sent to 
**	connected FTP server
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

int SendMessage(msgType, argument, theSocket)
     char *msgType;		/* type of message to send */
     char *argument;		/* arguments for message */
     int theSocket;		/* FTP control connection */
{
	char *theMessage;	/* final message buffer */
	int msgSize;		/* size of message */
	int sendLength;		/* for checking amount sent */
	int msgLen = strlen(msgType);

	if (msgType == NULL)
		return (MEMORY_ERROR);

	/* argument is valid and its length should be added to msgSize */
	if (argument != NULL)
		msgSize = msgLen + strlen(argument) + 3;

	else
		msgSize = msgLen + 3;

	/* allocate message buffer */
	theMessage = (char *) malloc(msgSize);
	if (theMessage == NULL)
		return (MEMORY_ERROR);

	/* argument should be added to theMessage */
	if (argument != NULL)
		sprintf(theMessage, "%s %s", msgType, argument);

	else
		sprintf(theMessage, "%s", msgType);

	/* all FTP messages end in CR LF */
	theMessage[msgSize - 2] = CARRG_RET;
	theMessage[msgSize - 1] = LINE_FEED;

	/* send message */
	sendLength = WriteToSocket(theSocket, theMessage, msgSize);
	free(theMessage);

	/* make sure all of message was sent */
	if (sendLength != msgSize)
		return (WRITE_TO_SOCK_ERR);
	else
		return (NO_ERROR);
}


/******************************************************************************
** function name: ReadOutText()
**
** preconditions: theSocket is control connection for FTP session, isMultiLine
**	if we need to read out multi-line reply, theCode is the FTP reply
**	code that determines where the end of reply is	
**
** postconditions: extra text associated with an FTP server reply is read off
**	of theSocket and discarded.
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

void ReadOutText(theSocket, isMultiLine, theCode)
     int theSocket;		/* FTP control connection */
     Boolean isMultiLine;	/* indicates multi-line server response */
     char *theCode;		/* code to check for on end of reply */
{
	int whichDigit = 0;	/* tracks which digits of theCode
				 * we have found */
	Boolean done = FALSE, hasCR = FALSE, lastLine = FALSE;
	char inChar;
	int readLength;

	/* read until end of server reply message */
	while (!done) {
		if ((readLength = read(theSocket, &inChar, 1)) == 1) {
			/* read one character at a time and check for match with
			 * theCode
			 */
			if ((isMultiLine) && (!lastLine)) {
				switch (whichDigit) {
				case 0:
					if (inChar == theCode[0])
						whichDigit = 1;
					break;

				case 1:
					if (inChar == theCode[1])
						whichDigit = 2;
					else
						whichDigit = 0;
					break;

				case 2:
					if (inChar == theCode[2])
						whichDigit = 3;
					else
						whichDigit = 0;
					break;

				case 3:
					if (inChar == ' ')
						lastLine = TRUE;
					else
						whichDigit = 0;
					break;

				default:
					break;
				}
			}
			/* if at last line of server reply (or not a multi-line
			 * reply) read until end of line reached 
			 */
			else if ((!isMultiLine) || (lastLine)) {
				if (inChar == CARRG_RET)
					hasCR = TRUE;

				else if ((hasCR) && (inChar == LINE_FEED))
					done = TRUE;

				else
					hasCR = FALSE;
			}
		}
	}
}


/******************************************************************************
** function name: CheckServReply()
**
** preconditions: msgType must indicate a valid type of message to check for
**	errors on, servReply is preallocated and contains the FTP
**	server reply
**
** postconditions: error code returned to caller indicating whether or not
**	servReply is an error message for msgType.
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

int CheckServReply(msgType, servReply)
     int msgType;		/* type of message to check for errors on */
     char *servReply;		/* message to check */
{
	char locReply[4];	/* temp reply storage */
	u_long reply;		/* for cast to long so switch may be used */

	/* if servReply exists, cast to long for quick compares */
	if (servReply != NULL) {
		strncpy(locReply, servReply, 3);
		locReply[3] = '\0';
		reply = htonl(*(u_long *) locReply);
	} else
		reply = 0;

	/* check the message types */
	switch (msgType) {
	case CONNECT_CHK:
	case REINIT_CHK:
		if (CONNECT_EST == reply)
			return (NO_ERROR);
		return (SERV_REPLY_ERROR);

	case USER_CHK:
		switch (reply) {
		case SEND_PASS:
			return (NO_ERROR);
		case USER_LOGIN:
			return (NO_PASS_REQ);
		default:
			break;
		}
		return (SERV_REPLY_ERROR);

	case PASSWD_CHK:
		if (USER_LOGIN == reply)
			return (NO_ERROR);
		return (SERV_REPLY_ERROR);

	case TYPE_CHK:
	case MODE_CHK:
	case PORT_CHK:
		if (CMD_OKAY == reply)
			return (NO_ERROR);
		return (SERV_REPLY_ERROR);

	case RETRIEVE_CHK:
		switch (reply) {
		case DATA_CONN_OPEN:
		case START_TRANS:
		case TRANS_SUCCESS:
			return (NO_ERROR);
		default:
			break;
		}
		return (SERV_REPLY_ERROR);

	case DISCONNECT_CHK:
		if (CLOSING == reply)
			return (NO_ERROR);
		return (SERV_REPLY_ERROR);

	default:
		return (NO_ERROR);
	}

	return (NO_ERROR);
}


/******************************************************************************
** function name: PrepareDataConnect()
**
** preconditions: theSocket is FTP control connection, sendPort indicates 
**	whether or not the PORT command will be used
**
** postconditions: a socket descriptor is returned to caller for an FTP
**	data connection
**
** author/credits: Dave Merkel, 4/94
**	Code for reusing existing socket for data connection taken from
**	ftp client source, Regents of the University of California, with
**	permission as indicated in source files
**
******************************************************************************/

int PrepareDataConnect(theSocket, sendPort)
     int theSocket;		/* control connection */
     Boolean sendPort;		/* PORT command */
{
	extern int gethostname();	/* unix system call */
	/* for use with PORT */
	struct hostent *theEntry;
	struct sockaddr_in theAddr;
	char hostName[HOST_NAME_LENGTH];
	int on = 1;
	int addrLen = sizeof(theAddr);

	/* data connection to return */
	int newSocket;

	newSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (newSocket < 0)
		return (GET_SOCKET_ERR);

	memset(&theAddr, '\0', addrLen);

	/* if not using PORT, data connection will be control connection,
	 * so we must reuse theSocket
	 */
	if (!sendPort) {
		/* code taken from University of California ftp source code */
		if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR,
			(char *) &on, sizeof(on)) < 0)
			return (SET_SOCKOPT_ERR);
		if (getsockname(theSocket, (struct sockaddr *) &theAddr, &addrLen) < 0)
			return (GET_SOCKNAME_ERR);
	}
	/* we're using the port command, so get information to prepare new 
	 * socket 
	 */
	else {
		if (gethostname(hostName, HOST_NAME_LENGTH) < 0)
			return (GET_HOSTNAME_ERR);
		if (!(theEntry = gethostbyname(hostName)))
			return (GET_HOSTBYNAME_ERR);
		theAddr.sin_family = theEntry->h_addrtype;
		theAddr.sin_port = 0;
	}

	/* bind the new socket */
	if (bind(newSocket, (struct sockaddr *) &theAddr, addrLen) < 0)
		return (BIND_ERR);

	/* listen for data connection */
	listen(newSocket, BACK_LOG);

	return (newSocket);
}


/******************************************************************************
** function name: RetrieveFile()
**
** preconditions: theSocket is data connection for FTP session, data is 
**	preallocated by caller
**
** postconditions: file returned to caller inside of data struct
**
** author/credits: Dave Merkel, 4/94
**
******************************************************************************/

int RetrieveFile(theSocket, data)
     int theSocket;		/* FTP data connection */
     DataReturn *data;		/* struct to return file in */
{
	struct sockaddr_in theAddr;	/* for accepting data connect */
	struct timeval timeout;	/* for read timeouts */
	Boolean done = FALSE;	/* loop control */
	fd_set dataDetect;	/* select read detects */

	/* data and filename storage buffers */
	char *theData, *tmpFile;

	/* indices into array storage */
	long index = 0, readLength, maxIndex = 0;

	int addrLen = sizeof(theAddr);
	int dataSocket = 0, selectResult, i;
	int flags, mode, theFile = 0;

	/* get sockaddr_in information for accept calls */
	if (getsockname(theSocket, (struct sockaddr *) &theAddr, &addrLen) < 0)
		return (GET_SOCKNAME_ERR);

	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	FD_ZERO(&dataDetect);
	FD_SET(dataSocket, &dataDetect);

	/* prepare to accept data connect, timeout after ACCEPT_TIMEOUT
	 * seconds
	 */
	for (i = 0; i < ACCEPT_TIMEOUT; i++) {
		/* poll for connection */
		selectResult = select(dataSocket + 1, &dataDetect, (fd_set *) 0,
		    (fd_set *) 0, &timeout);

		/* connection requested */
		if (selectResult == 0)
			dataSocket = accept(theSocket, (struct sockaddr *)
			    &theAddr, &addrLen);

		if (dataSocket >= 0)
			break;

		if (i == 0)
			timeout.tv_sec = 1;
	}

	/* no accept received, timed out */
	if (i == ACCEPT_TIMEOUT)
		return (ACCEPT_TIMEOUT_ERR);

	/* if we should return data in memory */
	if (data->inMemory) {
		theData = (char *) malloc(INIT_FILE_SIZE);
		maxIndex = INIT_FILE_SIZE - 1;
	}
	/* otherwise stored in file */
	else {
		theData = (char *) malloc(BUFFER_SIZE);
		flags = O_TRUNC | O_RDWR | O_CREAT | O_NDELAY;
		mode = INIT_PERMISSION;

		/* if we should store in temp file */
		if (data->useTempFile) {
			tmpFile = xstrdup(tempnam(NULL, "ccurl"));
			strcpy(data->fileName, tmpFile);
			if (tmpFile == NULL) {
				close(dataSocket);
				return (CANT_GET_TMPNAME);
			}
		}
		/* open storage file */
		theFile = open(data->fileName, flags, mode);
		if (theFile < 0) {
			close(dataSocket);
			return (FILE_OPEN_ERR);
		}
		theData = (char *) malloc(BUFFER_SIZE);
	}


	/* if we couldn't allocate buffers */
	if (!theData) {
		close(dataSocket);
		if (!data->inMemory)
			close(theFile);
		return (MEMORY_ERROR);
	}
	i = 0;
	while ((!done) && (i < READ_TIMEOUT)) {
		/* start read by doing select on dataSocket, when
		 * select is given, but a read brings 0 bytes, 
		 * dataSocket is closed by server (in STREAM mode)
		 * and file is complete
		 */

		FD_ZERO(&dataDetect);
		FD_SET(dataSocket, &dataDetect);

		timeout.tv_sec = 0;

		/* poll for arrival of data */
		selectResult = select(dataSocket + 1, &dataDetect,
		    (fd_set *) 0, (fd_set *) 0,
		    &timeout);

		/* data arrived */
		if (FD_ISSET(dataSocket, &dataDetect)) {
			/* if returning data in memory */
			if (data->inMemory) {
				readLength = read(dataSocket,
				    &theData[index], maxIndex - index);

				index += readLength;

				/* if we have filled memory buffer, try
				 * to realloc space
				 */
				if (index >= maxIndex) {
					theData = (char *) realloc(theData, maxIndex
					    + 1 + REALLOC_BLOCK);

					/* couldn't allocate more space */
					if (!theData) {
						data->buffer = theData;
						close(dataSocket);
						return (MEMORY_ERROR);
					}
					/* possible realloc problem, need to 
					 * point to possibly moved new block
					 */
					maxIndex += REALLOC_BLOCK;
				}
			}
			/* if we are using file storage */
			else {
				/* read data and write to theFile */
				readLength = read(dataSocket, theData,
				    BUFFER_SIZE);

				index += readLength;
				if (write(theFile, theData, readLength) <
				    readLength) {
					close(dataSocket);
					close(theFile);
					return (WRITE_FILE_ERR);
				}
			}

			/* if we read 0 bytes from successful select poll,
			 * the data connection is closed and EOF has
			 * been reached
			 */
			if (readLength == 0)
				done = TRUE;

			i = 0;
		}
		/* no data present, so wait 1 second and continue */
		else {
			timeout.tv_sec = 1;
			selectResult = select(dataSocket + 1, &dataDetect,
			    (fd_set *) 0, (fd_set *) 0,
			    &timeout);
			i++;
		}
	}

	/* close data connection, point to new data if inMemory, otherwise
	 * close data file
	 */
	close(dataSocket);
	if (data->inMemory)
		data->buffer = theData;
	else
		close(theFile);

	/* return size of retrieved file */
	data->fileSize = index;

	if (i >= READ_TIMEOUT)
		return (READ_TIMEOUT_ERR);

	return (NO_ERROR);
}
