mirror of
https://github.com/reactos/reactos.git
synced 2024-11-20 06:15:26 +00:00
1038 lines
24 KiB
C
1038 lines
24 KiB
C
/* 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 */
|