/* 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 then the msg. * A multi-line message would have the code and the first * line of the msg, then additional lines, until the last line, * which has the code 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 */