/* rcmd.c
 *
 * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
 * All rights reserved.
 *
 */

#include "syshdrs.h"

#if !defined(NO_SIGNALS) && (USE_SIO || !defined(SIGALRM) || !defined(SIGPIPE) || !defined(SIGINT))
#	define NO_SIGNALS 1
#endif

#ifndef NO_SIGNALS

#ifdef HAVE_SIGSETJMP
static sigjmp_buf gBrokenCtrlJmp;
#else
static jmp_buf gBrokenCtrlJmp;
#endif	/* HAVE_SIGSETJMP */

static void
BrokenCtrl(int UNUSED(signumIgnored))
{
	LIBNCFTP_USE_VAR(signumIgnored); 		/* Shut up gcc */
#ifdef HAVE_SIGSETJMP
	siglongjmp(gBrokenCtrlJmp, 1);
#else
	longjmp(gBrokenCtrlJmp, 1);
#endif	/* HAVE_SIGSETJMP */
}	/* BrokenCtrl */

#endif	/* NO_SIGNALS */


/* A 'Response' parameter block is simply zeroed to be considered init'ed. */
ResponsePtr
InitResponse(void)
{
	ResponsePtr rp;

	rp = (ResponsePtr) calloc(SZ(1), sizeof(Response));
	if (rp != NULL)
		InitLineList(&rp->msg);
	return (rp);
}	/* InitResponse */




/* If we don't print it to the screen, we may want to save it to our
 * trace log.
 */
void
TraceResponse(const FTPCIPtr cip, ResponsePtr rp)
{
	LinePtr lp;

	if (rp != NULL)	{
		lp = rp->msg.first;
		if (lp != NULL) {
			PrintF(cip, "%3d: %s\n", rp->code, lp->line);
			for (lp = lp->next; lp != NULL; lp = lp->next)
				PrintF(cip, "     %s\n", lp->line);
		}
	}
}	/* TraceResponse */





void
PrintResponse(const FTPCIPtr cip, LineListPtr llp)
{
	LinePtr lp;

	if (llp != NULL) {
		for (lp = llp->first; lp != NULL; lp = lp->next)
			PrintF(cip, "%s\n", lp->line);
	}
}	/* PrintResponse */





static void
SaveLastResponse(const FTPCIPtr cip, ResponsePtr rp)
{
	if (rp == NULL) {
		cip->lastFTPCmdResultStr[0] = '\0';
		cip->lastFTPCmdResultNum = -1;
		DisposeLineListContents(&cip->lastFTPCmdResultLL);
	} else if ((rp->msg.first == NULL) || (rp->msg.first->line == NULL)) {
		cip->lastFTPCmdResultStr[0] = '\0';
		cip->lastFTPCmdResultNum = rp->code;
		DisposeLineListContents(&cip->lastFTPCmdResultLL);
	} else {
		(void) STRNCPY(cip->lastFTPCmdResultStr, rp->msg.first->line);
		cip->lastFTPCmdResultNum = rp->code;

		/* Dispose previous command's line list. */
		DisposeLineListContents(&cip->lastFTPCmdResultLL);

		/* Save this command's line list. */
		cip->lastFTPCmdResultLL = rp->msg;
	}
}	/* SaveLastResponse */



void
DoneWithResponse(const FTPCIPtr cip, ResponsePtr rp)
{
	/* Dispose space taken up by the Response, and clear it out
	 * again.  For some reason, I like to return memory to zeroed
	 * when not in use.
	 */
	if (rp != NULL) {
		TraceResponse(cip, rp);
		if (cip->printResponseProc != 0) {
			if ((rp->printMode & kResponseNoProc) == 0)
				(*cip->printResponseProc)(cip, rp);
		}
		if ((rp->printMode & kResponseNoSave) == 0)
			SaveLastResponse(cip, rp);
		else
			DisposeLineListContents(&rp->msg);
		(void) memset(rp, 0, sizeof(Response));
		free(rp);
	}
}	/* DoneWithResponse */




/* This takes an existing Response and recycles it, by clearing out
 * the current contents.
 */
void
ReInitResponse(const FTPCIPtr cip, ResponsePtr rp)
{
	if (rp != NULL) {
		TraceResponse(cip, rp);
		if (cip->printResponseProc != 0) {
			if ((rp->printMode & kResponseNoProc) == 0)
				(*cip->printResponseProc)(cip, rp);
		}
		if ((rp->printMode & kResponseNoSave) == 0)
			SaveLastResponse(cip, rp);
		else
			DisposeLineListContents(&rp->msg);
		(void) memset(rp, 0, sizeof(Response));
	}
}	/* ReInitResponse */




#ifndef NO_SIGNALS

/* Since the control stream is defined by the Telnet protocol (RFC 854),
 * we follow Telnet rules when reading the control stream.  We use this
 * routine when we want to read a response from the host.
 */
int
GetTelnetString(const FTPCIPtr cip, char *str, size_t siz, FILE *cin, FILE *cout)
{
	int c;
	size_t n;
	int eofError;
	char *cp;

	cp = str;
	--siz;		/* We'll need room for the \0. */

	if ((cin == NULL) || (cout == NULL)) {
		eofError = -1;
		goto done;
	}

	for (n = (size_t)0, eofError = 0; ; ) {
		c = fgetc(cin);
checkChar:
		if (c == EOF) {
eof:
			eofError = -1;
			break;
		} else if (c == '\r') {
			/* A telnet string can have a CR by itself.  But to denote that,
			 * the protocol uses \r\0;  an end of line is denoted \r\n.
			 */
			c = fgetc(cin);
			if (c == '\n') {
				/* Had \r\n, so done. */
				goto done;
			} else if (c == EOF) {
				goto eof;
			} else if (c == '\0') {
				c = '\r';
				goto addChar;
			} else {
				/* Telnet protocol violation! */
				goto checkChar;
			}
		} else if (c == '\n') {
			/* Really shouldn't get here.  If we do, the other side
			 * violated the TELNET protocol, since eoln's are CR/LF,
			 * and not just LF.
			 */
			PrintF(cip, "TELNET protocol violation:  raw LF.\n");
			goto done;
		} else if (c == IAC) {
			/* Since the control connection uses the TELNET protocol,
			 * we have to handle some of its commands ourselves.
			 * IAC is the protocol's escape character, meaning that
			 * the next character after the IAC (Interpret as Command)
			 * character is a telnet command.  But, if there just
			 * happened to be a character in the text stream with the
			 * same numerical value of IAC, 255, the sender denotes
			 * that by having an IAC followed by another IAC.
			 */

			/* Get the telnet command. */
			c = fgetc(cin);

			switch (c) {
				case WILL:
				case WONT:
					/* Get the option code. */
					c = fgetc(cin);

					/* Tell the other side that we don't want
					 * to do what they're offering to do.
					 */
					(void) fprintf(cout, "%c%c%c",IAC,DONT,c);
					(void) fflush(cout);
					break;
				case DO:
				case DONT:
					/* Get the option code. */
					c = fgetc(cin);

					/* The other side said they are DOing (or not)
					 * something, which would happen if our side
					 * asked them to.  Since we didn't do that,
					 * ask them to not do this option.
					 */
					(void) fprintf(cout, "%c%c%c",IAC,WONT,c);
					(void) fflush(cout);
					break;

				case EOF:
					goto eof;

				default:
					/* Just add this character, since it was most likely
					 * just an escaped IAC character.
					 */
					goto addChar;
			}
		} else {
addChar:
			/* If the buffer supplied has room, add this character to it. */
			if (n < siz) {
				*cp++ = c;
				++n;
			}
		}
	}

done:
	*cp = '\0';
	return (eofError);
}	/* GetTelnetString */

#endif	/* NO_SIGNALS */



/* Returns 0 if a response was read, or (-1) if an error occurs.
 * This reads the entire response text into a LineList, which is kept
 * in the 'Response' structure.
 */
int
GetResponse(const FTPCIPtr cip, ResponsePtr rp)
{
	longstring str;
	int eofError;
	str16 code;
	char *cp;
	int continuation;
	volatile FTPCIPtr vcip;
#ifdef NO_SIGNALS
	int result;
#else	/* NO_SIGNALS */
	volatile FTPSigProc osigpipe;
	int sj;
#endif	/* NO_SIGNALS */

	/* RFC 959 states that a reply may span multiple lines.  A single
	 * line message would have the 3-digit code <space> then the msg.
	 * A multi-line message would have the code <dash> and the first
	 * line of the msg, then additional lines, until the last line,
	 * which has the code <space> and last line of the msg.
	 *
	 * For example:
	 *	123-First line
	 *	Second line
	 *	234 A line beginning with numbers
	 *	123 The last line
	 */

#ifdef NO_SIGNALS
	vcip = cip;
#else	/* NO_SIGNALS */
	osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenCtrl);
	vcip = cip;

#ifdef HAVE_SIGSETJMP
	sj = sigsetjmp(gBrokenCtrlJmp, 1);
#else
	sj = setjmp(gBrokenCtrlJmp);
#endif	/* HAVE_SIGSETJMP */

	if (sj != 0) {
		(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
		FTPShutdownHost(vcip);
		vcip->errNo = kErrRemoteHostClosedConnection;
		return(vcip->errNo);
	}
#endif	/* NO_SIGNALS */

#ifdef NO_SIGNALS
	cp = str;
	eofError = 0;
	if (cip->dataTimedOut > 0) {
		/* Give up immediately unless the server had already
		 * sent a message. Odds are since the data is timed
		 * out, so is the control.
		 */
		if (SWaitUntilReadyForReading(cip->ctrlSocketR, 0) == 0) {
			/* timeout */
			Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
			FTPShutdownHost(vcip);
			cip->errNo = kErrControlTimedOut;
			return (cip->errNo);
		}
	}
	result = SReadline(&cip->ctrlSrl, str, sizeof(str) - 1);
	if (result == kTimeoutErr) {
		/* timeout */
		Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
		FTPShutdownHost(vcip);
		cip->errNo = kErrControlTimedOut;
		return (cip->errNo);
	} else if (result == 0) {
		/* eof */
		eofError = 1;
		rp->hadEof = 1;
		if (rp->eofOkay == 0)
			Error(cip, kDontPerror, "Remote host has closed the connection.\n");
		FTPShutdownHost(vcip);
		cip->errNo = kErrRemoteHostClosedConnection;
		return (cip->errNo);
	} else if (result < 0) {
		/* error */
		Error(cip, kDoPerror, "Could not read reply from control connection");
		FTPShutdownHost(vcip);
		cip->errNo = kErrInvalidReplyFromServer;
		return (cip->errNo);
	}

	if (str[result - 1] == '\n')
		str[result - 1] = '\0';

#else	/* NO_SIGNALS */
	/* Get the first line of the response. */
	eofError = GetTelnetString(cip, str, sizeof(str), cip->cin, cip->cout);

	cp = str;
	if (*cp == '\0') {
		if (eofError < 0) {
			/* No bytes read for reply, and EOF detected. */
			rp->hadEof = 1;
			if (rp->eofOkay == 0)
				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
			FTPShutdownHost(vcip);
			cip->errNo = kErrRemoteHostClosedConnection;
			(void) signal(SIGPIPE, osigpipe);
			return(cip->errNo);
		}
	}
#endif	/* NO_SIGNALS */

	if (!isdigit((int) *cp)) {
		Error(cip, kDontPerror, "Invalid reply: \"%s\"\n", cp);
		cip->errNo = kErrInvalidReplyFromServer;
#ifndef NO_SIGNALS
		(void) signal(SIGPIPE, osigpipe);
#endif
		return (cip->errNo);
	}

	rp->codeType = *cp - '0';
	cp += 3;
	continuation = (*cp == '-');
	*cp++ = '\0';
	(void) STRNCPY(code, str);
	rp->code = atoi(code);
	(void) AddLine(&rp->msg, cp);
	if (eofError < 0) {
		/* Read reply, but EOF was there also. */
		rp->hadEof = 1;
	}

	while (continuation) {

#ifdef NO_SIGNALS
		result = SReadline(&cip->ctrlSrl, str, sizeof(str) - 1);
		if (result == kTimeoutErr) {
			/* timeout */
			Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
			FTPShutdownHost(vcip);
			cip->errNo = kErrControlTimedOut;
			return (cip->errNo);
		} else if (result == 0) {
			/* eof */
			eofError = 1;
			rp->hadEof = 1;
			if (rp->eofOkay == 0)
				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
			FTPShutdownHost(vcip);
			cip->errNo = kErrRemoteHostClosedConnection;
			return (cip->errNo);
		} else if (result < 0) {
			/* error */
			Error(cip, kDoPerror, "Could not read reply from control connection");
			FTPShutdownHost(vcip);
			cip->errNo = kErrInvalidReplyFromServer;
			return (cip->errNo);
		}

		if (str[result - 1] == '\n')
			str[result - 1] = '\0';
#else	/* NO_SIGNALS */
		eofError = GetTelnetString(cip, str, sizeof(str), cip->cin, cip->cout);
		if (eofError < 0) {
			/* Read reply, but EOF was there also. */
			rp->hadEof = 1;
			continuation = 0;
		}
#endif	/* NO_SIGNALS */
		cp = str;
		if (strncmp(code, cp, SZ(3)) == 0) {
			cp += 3;
			if (*cp == ' ')
				continuation = 0;
			++cp;
		}
		(void) AddLine(&rp->msg, cp);
	}

	if (rp->code == 421) {
		/*
		 *   421 Service not available, closing control connection.
		 *       This may be a reply to any command if the service knows it
		 *       must shut down.
		 */
		if (rp->eofOkay == 0)
			Error(cip, kDontPerror, "Remote host has closed the connection.\n");
		FTPShutdownHost(vcip);
		cip->errNo = kErrRemoteHostClosedConnection;
#ifndef NO_SIGNALS
		(void) signal(SIGPIPE, osigpipe);
#endif
		return(cip->errNo);
	}

#ifndef NO_SIGNALS
	(void) signal(SIGPIPE, osigpipe);
#endif
	return (kNoErr);
}	/* GetResponse */




/* This creates the complete command text to send, and writes it
 * on the stream.
 */
#ifdef NO_SIGNALS

static int
SendCommand(const FTPCIPtr cip, const char *cmdspec, va_list ap)
{
	longstring command;
	int result;

	if (cip->ctrlSocketW != kClosedFileDescriptor) {
#ifdef HAVE_VSNPRINTF
		(void) vsnprintf(command, sizeof(command) - 1, cmdspec, ap);
		command[sizeof(command) - 1] = '\0';
#else
		(void) vsprintf(command, cmdspec, ap);
#endif
		if ((strncmp(command, "PASS", SZ(4)) != 0) || ((strcmp(cip->user, "anonymous") == 0) && (cip->firewallType == kFirewallNotInUse)))
			PrintF(cip, "Cmd: %s\n", command);
		else
			PrintF(cip, "Cmd: %s\n", "PASS xxxxxxxx");
		(void) STRNCAT(command, "\r\n");	/* Use TELNET end-of-line. */
		cip->lastFTPCmdResultStr[0] = '\0';
		cip->lastFTPCmdResultNum = -1;

		result = SWrite(cip->ctrlSocketW, command, strlen(command), (int) cip->ctrlTimeout, 0);

		if (result < 0) {
			cip->errNo = kErrSocketWriteFailed;
			Error(cip, kDoPerror, "Could not write to control stream.\n");
			return (cip->errNo);
		}
		return (kNoErr);
	}
	return (kErrNotConnected);
}	/* SendCommand */

#else	/* NO_SIGNALS */

static int
SendCommand(const FTPCIPtr cip, const char *cmdspec, va_list ap)
{
	longstring command;
	int result;
	volatile FTPCIPtr vcip;
	volatile FTPSigProc osigpipe;
	int sj;

	if (cip->cout != NULL) {
#ifdef HAVE_VSNPRINTF
		(void) vsnprintf(command, sizeof(command) - 1, cmdspec, ap);
		command[sizeof(command) - 1] = '\0';
#else
		(void) vsprintf(command, cmdspec, ap);
#endif
		if ((strncmp(command, "PASS", SZ(4)) != 0) || ((strcmp(cip->user, "anonymous") == 0) && (cip->firewallType == kFirewallNotInUse)))
			PrintF(cip, "Cmd: %s\n", command);
		else
			PrintF(cip, "Cmd: %s\n", "PASS xxxxxxxx");
		(void) STRNCAT(command, "\r\n");	/* Use TELNET end-of-line. */
		cip->lastFTPCmdResultStr[0] = '\0';
		cip->lastFTPCmdResultNum = -1;

		osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenCtrl);
		vcip = cip;

#ifdef HAVE_SIGSETJMP
		sj = sigsetjmp(gBrokenCtrlJmp, 1);
#else
		sj = setjmp(gBrokenCtrlJmp);
#endif	/* HAVE_SIGSETJMP */

		if (sj != 0) {
			(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
			FTPShutdownHost(vcip);
			if (vcip->eofOkay == 0) {
				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
				vcip->errNo = kErrRemoteHostClosedConnection;
				return(vcip->errNo);
			}
			return (kNoErr);
		}

		result = fputs(command, cip->cout);
		if (result < 0) {
			(void) signal(SIGPIPE, osigpipe);
			cip->errNo = kErrSocketWriteFailed;
			Error(cip, kDoPerror, "Could not write to control stream.\n");
			return (cip->errNo);
		}
		result = fflush(cip->cout);
		if (result < 0) {
			(void) signal(SIGPIPE, osigpipe);
			cip->errNo = kErrSocketWriteFailed;
			Error(cip, kDoPerror, "Could not write to control stream.\n");
			return (cip->errNo);
		}
		(void) signal(SIGPIPE, osigpipe);
		return (kNoErr);
	}
	return (kErrNotConnected);
}	/* SendCommand */
#endif	/* NO_SIGNALS */



/* For "simple" (i.e. not data transfer) commands, this routine is used
 * to send the command and receive one response.  It returns the codeType
 * field of the 'Response' as the result, or a negative number upon error.
 */
/*VARARGS*/
int
FTPCmd(const FTPCIPtr cip, const char *const cmdspec, ...)
{
	va_list ap;
	int result;
	ResponsePtr rp;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	rp = InitResponse();
	if (rp == NULL) {
		result = kErrMallocFailed;
		cip->errNo = kErrMallocFailed;
		Error(cip, kDontPerror, "Malloc failed.\n");
		return (cip->errNo);
	}

	va_start(ap, cmdspec);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(cip->ctrlTimeout);
#endif	/* NO_SIGNALS */
	result = SendCommand(cip, cmdspec, ap);
	va_end(ap);
	if (result < 0) {
#ifndef NO_SIGNALS
		if (cip->ctrlTimeout > 0)
			(void) alarm(0);
#endif	/* NO_SIGNALS */
		return (result);
	}

	/* Get the response to the command we sent. */
	result = GetResponse(cip, rp);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(0);
#endif	/* NO_SIGNALS */

	if (result == kNoErr)
		result = rp->codeType;
	DoneWithResponse(cip, rp);
	return (result);
}	/* FTPCmd */




/* This is for debugging the library -- don't use. */
/*VARARGS*/
int
FTPCmdNoResponse(const FTPCIPtr cip, const char *const cmdspec, ...)
{
	va_list ap;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	va_start(ap, cmdspec);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(cip->ctrlTimeout);
#endif	/* NO_SIGNALS */
	(void) SendCommand(cip, cmdspec, ap);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(0);
#endif	/* NO_SIGNALS */
	va_end(ap);

	return (kNoErr);
}	/* FTPCmdNoResponse */




int
WaitResponse(const FTPCIPtr cip, unsigned int sec)
{
	int result;
	fd_set ss;
	struct timeval tv;
	int fd;

#ifdef NO_SIGNALS
	fd = cip->ctrlSocketR;
#else	/* NO_SIGNALS */
	if (cip->cin == NULL)
		return (-1);
	fd = fileno(cip->cin);
#endif	/* NO_SIGNALS */
	if (fd < 0)
		return (-1);
	FD_ZERO(&ss);
	FD_SET(fd, &ss);
	tv.tv_sec = (unsigned long) sec;
	tv.tv_usec = 0;
	result = select(fd + 1, SELECT_TYPE_ARG234 &ss, NULL, NULL, &tv);
	return (result);
}	/* WaitResponse */




/* For "simple" (i.e. not data transfer) commands, this routine is used
 * to send the command and receive one response.  It returns the codeType
 * field of the 'Response' as the result, or a negative number upon error.
 */

/*VARARGS*/
int
RCmd(const FTPCIPtr cip, ResponsePtr rp, const char *cmdspec, ...)
{
	va_list ap;
	int result;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	va_start(ap, cmdspec);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(cip->ctrlTimeout);
#endif	/* NO_SIGNALS */
	result = SendCommand(cip, cmdspec, ap);
	va_end(ap);
	if (result < 0) {
#ifndef NO_SIGNALS
		if (cip->ctrlTimeout > 0)
			(void) alarm(0);
#endif	/* NO_SIGNALS */
		return (result);
	}

	/* Get the response to the command we sent. */
	result = GetResponse(cip, rp);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(0);
#endif	/* NO_SIGNALS */

	if (result == kNoErr)
		result = rp->codeType;
	return (result);
}	/* RCmd */



/* Returns -1 if an error occurred, or 0 if not.
 * This differs from RCmd, which returns the code class of a response.
 */

/*VARARGS*/
int
FTPStartDataCmd(const FTPCIPtr cip, int netMode, int type, longest_int startPoint, const char *cmdspec, ...)
{
	va_list ap;
	int result;
	int respCode;
	ResponsePtr rp;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	result = FTPSetTransferType(cip, type);
	if (result < 0)
		return (result);

	/* Re-set the cancellation flag. */
	cip->cancelXfer = 0;

	/* To transfer data, we do these things in order as specifed by
	 * the RFC.
	 *
	 * First, we tell the other side to set up a data line.  This
	 * is done below by calling OpenDataConnection(), which sets up
	 * the socket.  When we do that, the other side detects a connection
	 * attempt, so it knows we're there.  Then tell the other side
	 * (by using listen()) that we're willing to receive a connection
	 * going to our side.
	 */

	if ((result = OpenDataConnection(cip, cip->dataPortMode)) < 0)
		goto done;

	/* If asked, attempt to start at a later position in the remote file. */
	if (startPoint != (longest_int) 0) {
		if ((startPoint == kSizeUnknown) || ((result = SetStartOffset(cip, startPoint)) != 0))
			startPoint = (longest_int) 0;
	}
	cip->startPoint = startPoint;

	/* Now we tell the server what we want to do.  This sends the
	 * the type of transfer we want (RETR, STOR, LIST, etc) and the
	 * parameters for that (files to send, directories to list, etc).
	 */
	va_start(ap, cmdspec);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(cip->ctrlTimeout);
#endif	/* NO_SIGNALS */
	result = SendCommand(cip, cmdspec, ap);
	va_end(ap);
	if (result < 0) {
#ifndef NO_SIGNALS
		if (cip->ctrlTimeout > 0)
			(void) alarm(0);
#endif	/* NO_SIGNALS */
		goto done;
	}

	/* Get the response to the transfer command we sent, to see if
	 * they can accomodate the request.  If everything went okay,
	 * we will get a preliminary response saying that the transfer
	 * initiation was successful and that the data is there for
	 * reading (for retrieves;  for sends, they will be waiting for
	 * us to send them something).
	 */
	rp = InitResponse();
	if (rp == NULL) {
		Error(cip, kDontPerror, "Malloc failed.\n");
		cip->errNo = kErrMallocFailed;
		result = cip->errNo;
		goto done;
	}
	result = GetResponse(cip, rp);
#ifndef NO_SIGNALS
	if (cip->ctrlTimeout > 0)
		(void) alarm(0);
#endif	/* NO_SIGNALS */

	if (result < 0)
		goto done;
	respCode = rp->codeType;
	DoneWithResponse(cip, rp);

	if (respCode > 2) {
		cip->errNo = kErrCouldNotStartDataTransfer;
		result = cip->errNo;
		goto done;
	}

	/* Now we accept the data connection that the other side is offering
	 * to us.  Then we can do the actual I/O on the data we want.
	 */
	cip->netMode = netMode;
	if ((result = AcceptDataConnection(cip)) < 0)
		goto done;
	return (kNoErr);

done:
	(void) FTPEndDataCmd(cip, 0);
	return (result);
}	/* FTPStartDataCmd */




void
FTPAbortDataTransfer(const FTPCIPtr cip)
{
	ResponsePtr rp;
	int result;

	if (cip->dataSocket != kClosedFileDescriptor) {
		PrintF(cip, "Starting abort sequence.\n");
		SendTelnetInterrupt(cip);		/* Probably could get by w/o doing this. */

		result = FTPCmdNoResponse(cip, "ABOR");
		if (result != kNoErr) {
			/* Linger could cause close to block, so unset it. */
			(void) SetLinger(cip, cip->dataSocket, 0);
			CloseDataConnection(cip);
			PrintF(cip, "Could not send abort command.\n");
			return;
		}

		if (cip->abortTimeout > 0) {
			result = WaitResponse(cip, (unsigned int) cip->abortTimeout);
			if (result <= 0) {
				/* Error or no response received to ABOR in time. */
				(void) SetLinger(cip, cip->dataSocket, 0);
				CloseDataConnection(cip);
				PrintF(cip, "No response received to abort request.\n");
				return;
			}
		}

		rp = InitResponse();
		if (rp == NULL) {
			Error(cip, kDontPerror, "Malloc failed.\n");
			cip->errNo = kErrMallocFailed;
			result = cip->errNo;
			return;
		}

		result = GetResponse(cip, rp);
		if (result < 0) {
			/* Shouldn't happen, and doesn't matter if it does. */
			(void) SetLinger(cip, cip->dataSocket, 0);
			CloseDataConnection(cip);
			PrintF(cip, "Invalid response to abort request.\n");
			DoneWithResponse(cip, rp);
			return;
		}
		DoneWithResponse(cip, rp);

		/* A response to the abort request has been received.
		 * Now the only thing left to do is close the data
		 * connection, making sure to turn off linger mode
		 * since we don't care about straggling data bits.
		 */
		(void) SetLinger(cip, cip->dataSocket, 0);
		CloseDataConnection(cip);		/* Must close (by protocol). */
		PrintF(cip, "End abort.\n");
	}
}	/* FTPAbortDataTransfer */




int
FTPEndDataCmd(const FTPCIPtr cip, int didXfer)
{
	int result;
	int respCode;
	ResponsePtr rp;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	CloseDataConnection(cip);
	result = kNoErr;
	if (didXfer) {
		/* Get the response to the data transferred.  Most likely a message
		 * saying that the transfer completed succesfully.  However, if
		 * we tried to abort the transfer using ABOR, we will have a response
		 * to that command instead.
		 */
		rp = InitResponse();
		if (rp == NULL) {
			Error(cip, kDontPerror, "Malloc failed.\n");
			cip->errNo = kErrMallocFailed;
			result = cip->errNo;
			return (result);
		}
		result = GetResponse(cip, rp);
		if (result < 0)
			return (result);
		respCode = rp->codeType;
		DoneWithResponse(cip, rp);
		if (respCode != 2) {
			cip->errNo = kErrDataTransferFailed;
			result = cip->errNo;
		} else {
			result = kNoErr;
		}
	}
	return (result);
}	/* FTPEndDataCmd */




int
BufferGets(char *buf, size_t bufsize, int inStream, char *secondaryBuf, char **secBufPtr, char **secBufLimit, size_t secBufSize)
{
	int err;
	char *src;
	char *dst;
	char *dstlim;
	int len;
	int nr;
	int haveEof = 0;

	err = 0;
	dst = buf;
	dstlim = dst + bufsize - 1;		/* Leave room for NUL. */
	src = (*secBufPtr);
	for ( ; dst < dstlim; ) {
		if (src >= (*secBufLimit)) {
			/* Fill the buffer. */

/* Don't need to poll it here.  The routines that use BufferGets don't
 * need any special processing during timeouts (i.e. progress reports),
 * so go ahead and just let it block until there is data to read.
 */
			nr = (int) read(inStream, secondaryBuf, secBufSize);
			if (nr == 0) {
				/* EOF. */
				haveEof = 1;
				goto done;
			} else if (nr < 0) {
				/* Error. */
				err = -1;
				goto done;
			}
			(*secBufPtr) = secondaryBuf;
			(*secBufLimit) = secondaryBuf + nr;
			src = (*secBufPtr);
			if (nr < (int) secBufSize)
				src[nr] = '\0';
		}
		if (*src == '\r') {
			++src;
		} else {
			if (*src == '\n') {
				/* *dst++ = *src++; */	++src;
				goto done;
			}
			*dst++ = *src++;
		}
	}

done:
	(*secBufPtr) = src;
	*dst = '\0';
	len = (int) (dst - buf);
	if (err < 0)
		return (err);
	if ((len == 0) && (haveEof == 1))
		return (-1);
	return (len);	/* May be zero, if a blank line. */
}	/* BufferGets */

/* eof */
