home *** CD-ROM | disk | FTP | other *** search
- /*
- * ftp.c : This code implements an asynchronous ftp client.
- *
- * George Ferguson, ferguson@cs.rochester.edu, 23 Apr 1993.
- *
- * The client is represented by an instance of the FtpContext structure,
- * which includes the state of the client with respect to a finite-state
- * model of the FTP process. All aspects of the connection are contained
- * in the structure, so a process can have multiple outstanding FTP
- * sessions (subject to filesystem limitations, of course).
- *
- * Refer to RFC959 for details of the state model.
- *
- * Two external functions must be provided:
- * void RegisterFtpFd( ftpc, fd, flags, func )
- * Should arrange to call FUNC passing FTPC whenever FD is ready
- * as indicated by FLAGS (from <fcntl.h>).
- * void UnregisterFtpFd( ftpc, fd )
- * Cancels a previous registration of FD for FTPC.
- * Samples of these using select(2) are given below in the STANDALONE
- * section. Xarchie uses the XtAppAddInput() mechanism. The code also
- * uses alert0() and status0() for messages, sysError() for error
- * messages from system calls, and ftpPrompt() if prompting is enabled.
- *
- * To use these routines:
- * ftpc = ftpNewContext(...);
- * ftpStart(ftpc);
- * The function you registered as the "done" parameter in ftpNewContext
- * will be called back when the process finishes (for any reason). You
- * should call ftpFreeContext() in this callback (or register it as the
- * callback, or register NULL).
- *
- * This code is decidely *not* a general-purpose FTP library. It is
- * designed to support the batch transfers required by xarchie. I suppose
- * that it could be modified to be more general without too much effort.
- * In particular, many of the routines called by ftpProcessReply could
- * be made into external interface functions. Volunteers?
- */
- #include <stdio.h>
- #include <errno.h>
- #include "config.h"
- #ifdef HAVE_SYS_PARAM_H
- #include <sys/param.h>
- #endif
- #include "ftp.h" /* includes <sys/types.h> and <netinet/in.h> */
- #include "sysdefs.h" /* char *malloc() */
- #include "stringdefs.h" /* char *strcpy(), *index(); */
- #include "debug.h" /* MSG[0-3]() macros */
-
- /* These functions have to be provided: */
- extern void RegisterFtpFd(/* ftpc, fd, O_RDWR | O_RDONLY, func */);
- extern void UnregisterFtpFd(/* ftpc, fd */);
- extern int ftpPrompt(/* ftpc */);
- extern void status0(/* str */);
- extern void alert0(/* str */);
- extern void sysError(/* str */);
-
- /*
- * Portable non-blocking I/O macros, I hope.
- * hp300 : From Andy.Linton@comp.vuw.ac.nz
- * sgi : From amoss@cs.huji.ac.il
- */
- #if defined(hp300)
- /* Here's for BSD, maybe, but Sys5 can't tell this from EOF. */
- # include <fcntl.h>
- # define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NDELAY)
- # define ITWOULDBLOCK EWOULDBLOCK
- #else
- #if defined(sgi)
- # include <fcntl.h>
- # define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NONBLOCK)
- # define ITWOULDBLOCK EWOULDBLOCK
- #else
- /* This is POSIX, the default, which uses EAGAIN. */
- # include <fcntl.h>
- # define MAKE_NONBLOCKING(fd) fcntl(fd,F_SETFL,O_NONBLOCK)
- # define ITWOULDBLOCK EAGAIN
- #endif
- #endif
- /*
- * Networking includes from Brendan Kehoe (dirsend.c)
- */
- #include <netdb.h>
- #include <sys/socket.h>
- #ifndef hpux
- # include <arpa/inet.h>
- #endif
- /* Interactive UNIX keeps some of the socket definitions in funny places. */
- #ifdef ISC
- # include <net/errno.h> /* I wonder about this... */
- #endif /* ISC */
-
- #ifndef IPPORT_FTP
- #define IPPORT_FTP 21 /* From <netinet/in.h> */
- #endif
-
- #define NewString(S) strcpy(malloc(strlen(S)+1),S)
- #define ftpfilecmdstr(X) ((X)==FTP_GET ? "GET" : "PUT")
-
- /*
- * Functions defined here:
- */
- FtpContext *ftpNewContext();
- void ftpStart(),ftpAbort();
- void ftpFreeContext();
-
- /* Initialization */
- static int ftpGetHostAndPort();
- /* Connection establishment */
- static void ftpConnectCallback();
- static int ftpGetSocket(),ftpConnect();
- /* Reading server replies */
- static void ftpInitReply(),ftpReplyCallback();
- /* Main FTP client state-machine */
- static void ftpProcessReply();
- static void ftpAlert(),ftpHandleErrorThenQuit(),ftpDone();
- static void ftpSendType(),ftpSendNextCwd();
- static int ftpSendPort();
- static void ftpSendGetPut();
- static int ftpAcceptDataConn(),ftpSetupLocalData();
- /* Data connection processing */
- static void ftpDataCallback();
- static void ftpReadData(),ftpWriteData(),ftpCleanupDataConn();
- /* Sending commands to the server */
- static void ftpStartSendingCmd(),ftpWriteCallback();
- static void ftpSendCmd(),ftpSendAbort();
- #ifdef DEBUG
- char *ftpstatestr();
- #endif
-
- /* - - - - - - - - */
- /* Exported functions: */
-
- /*
- * Returns a pointer to a new FtpContext for which the connection is
- * started. The external function RegisterFtpFd() is used to register
- * the control socket for subsequent processing by ftpProcess().
- */
- FtpContext *
- ftpNewContext(hostname,user,pass,cwd,local_dir,type,stripcr,prompt,
- filecmd,files,num_files,trace,done1,done)
- char *hostname,*user,*pass,*cwd,*local_dir;
- int type,stripcr,prompt,filecmd;
- char **files;
- int num_files;
- FtpTraceProc trace;
- FtpCallbackProc done1,done;
- {
- FtpContext *ftpc;
- int i;
-
- /* Initialize the data structure */
- if ((ftpc=(FtpContext *)malloc(sizeof(FtpContext))) == NULL) {
- fprintf(stderr,"ftpNewContext: malloc failed\n");
- return(NULL);
- }
- /* User-specified stuff */
- ftpc->hostname = NewString(hostname);
- ftpc->user = NewString(user);
- ftpc->pass = NewString(pass);
- ftpc->cwd = NewString(cwd);
- ftpc->wd = ftpc->cwd;
- ftpc->local_dir = NewString(local_dir);
- ftpc->type = type;
- ftpc->stripcr = stripcr;
- ftpc->prompt = prompt;
- ftpc->filecmd = filecmd;
- ftpc->num_files = num_files;
- ftpc->files = (char **)calloc(num_files,sizeof(char *));
- for (i=0; i < num_files; i++)
- ftpc->files[i] = NewString(files[i]);
- ftpc->this_file = -1; /* preincrement later */
- ftpc->this_size = 0;
- ftpc->trace = trace;
- ftpc->done1 = done1;
- ftpc->done = done;
- /* Default stuff */
- ftpc->reply[0] = '\0';
- ftpc->stripcr = 1;
- /* Undefined stuff */
- ftpc->ctrl = ftpc->data = ftpc->port = ftpc->local_fd = -1;
- ftpc->state = ftpc->iostate = ftpc->reply_state = ftpc->saved_state = 0;
- ftpc->retcode = ftpc->tmpcode = 0;
- ftpc->cmd = NULL;
- /* Go start the connection */
- if (ftpGetHostAndPort(ftpc) < 0) {
- ftpFreeContext(ftpc);
- return(NULL);
- }
- ftpc->state = 0;
- return(ftpc);
- }
-
- void
- ftpFreeContext(ftpc)
- FtpContext *ftpc;
- {
- int i;
-
- DEBUG1("ftpFreeContext: ftpc=0x%x\n",ftpc);
- if (ftpc->ctrl != -1) {
- UnregisterFtpFd(ftpc,ftpc->ctrl);
- DEBUG1("ftpFreeContext: closing ctrl %d\n",ftpc->ctrl);
- close(ftpc->ctrl);
- }
- if (ftpc->data != -1) {
- UnregisterFtpFd(ftpc,ftpc->data);
- DEBUG1("ftpFreeContext: closing data %d\n",ftpc->data);
- close(ftpc->data);
- }
- if (ftpc->port != -1) {
- DEBUG1("ftpFreeContext: closing port %d\n",ftpc->port);
- close(ftpc->port);
- }
- if (ftpc->local_fd != -1) {
- DEBUG1("ftpFreeContext: closing local_fd %d\n",ftpc->local_fd);
- close(ftpc->local_fd);
- }
- if (ftpc->hostname)
- free(ftpc->hostname);
- if (ftpc->user)
- free(ftpc->user);
- if (ftpc->pass)
- free(ftpc->pass);
- if (ftpc->cwd)
- free(ftpc->cwd);
- if (ftpc->local_dir)
- free(ftpc->local_dir);
- if (ftpc->files) {
- for (i=0; i < ftpc->num_files; i++)
- if (ftpc->files[i]) free(ftpc->files[i]);
- free((char *)(ftpc->files));
- }
- if (ftpc->h_addr_list)
- free((char *)(ftpc->h_addr_list));
- DEBUG0("ftpFreeContext: done\n");
- }
-
- void
- ftpStart(ftpc)
- FtpContext *ftpc;
- {
- DEBUG1("ftpStart: ftpc=0x%x\n",ftpc);
- ftpc->state = FTPS_OPEN;
- ftpConnectCallback(ftpc); /* get things started */
- DEBUG0("ftpStart: done\n");
- }
-
- void
- ftpAbort(ftpc)
- FtpContext *ftpc;
- {
- DEBUG1("ftpAbort: ftpc=0x%x\n",ftpc);
- /*
- * If we're aborting before any files transferred or after an
- * abort or quit, just close down the whole connection.
- */
- if (ftpc->state < FTPS_READY || ftpc->state > FTPS_EOF) {
- ftpDone(ftpc);
- return;
- }
- /*
- * Close the dataconn so we don't get swamped in select() while
- * waiting for the response to the abort on the ctrlconn.
- */
- if (ftpc->data != -1) {
- UnregisterFtpFd(ftpc,ftpc->data);
- }
- /* Then send the abort sequence */
- ftpSendAbort(ftpc);
- DEBUG0("ftpAbort: done\n");
- }
-
- /* - - - - - - - - */
- /* Initialization: */
- /*
- * This function is called from ftpNewContext() to initialize the host
- * address information in the FTPC.
- */
- static int
- ftpGetHostAndPort(ftpc)
- FtpContext *ftpc;
- {
- struct servent *serv;
- struct hostent *host;
- unsigned long hostaddr;
- char msg[256];
- int i;
-
- DEBUG2("ftpGetHostAndPort: ftpc=0x%x: \"%s\"\n",ftpc,ftpc->hostname);
- /*
- * Get ftp port
- */
- DEBUG0("ftpGetHostAndPort: getting ftp service port\n");
- serv = getservbyname("ftp","tcp");
- /* UCX needs 0 or -1 */
- if (serv == (struct servent *)0 || serv == (struct servent *)-1) {
- alert0("Can't find service 'ftp/tcp' in list of services -- using default port");
- ftpc->servport = htons((unsigned short)IPPORT_FTP);
- } else {
- ftpc->servport = (unsigned short)serv->s_port;
- }
- DEBUG1("ftpGetHostAndPort: ftp service port is %d\n",ftpc->servport);
- /*
- * Get host address
- */
- sprintf(msg,"Getting address for host \"%.200s\"",ftpc->hostname);
- status0(msg);
- if ((host=gethostbyname(ftpc->hostname)) == NULL) {
- DEBUG0("ftpGetHostAndPort: gethostbyname failed, trying inet_addr()\n");
- /*
- * If gethostbyname fails, then maybe we've been given an IP
- * address directly. Let's see.
- */
- hostaddr = inet_addr(ftpc->hostname);
- if (hostaddr == (unsigned long)-1) {
- /*
- * Nope - complete failure.
- */
- sprintf(msg,"Can't find address of host \"%.200s\"",
- ftpc->hostname);
- alert0(msg);
- return(-1);
- } else {
- /*
- * inet_addr succeeded, so we make a dummy array of hostaddrs.
- */
- ftpc->h_addr_list = (char **)calloc(2,sizeof(char *));
- bcopy((char *)&hostaddr,ftpc->h_addr_list[0],sizeof(char *));
- hostaddr = (unsigned long)0;
- bcopy((char *)&hostaddr,ftpc->h_addr_list[1],sizeof(char *));
- ftpc->this_addr = ftpc->h_addr_list-1; /* preincrement later */
- DEBUG1("ftpGetHostAndPort: inet_addr returned %s\n",
- inet_ntoa(*(struct in_addr*)(ftpc->h_addr_list[0])));
- return(0);
- }
- } else {
- /*
- * If gethostbyname succeeeds, it fills in a list of addresses.
- * We'll copy them into the ftpc.
- */
- for (i=0; host->h_addr_list[i]; i++)
- /*EMPTY*/;
- DEBUG1("ftpGetHostAndPort: gethostbynname returned %d addr(s)\n",i);
- ftpc->h_addr_list = (char **)calloc(i+1,sizeof(char *));
- for (i=0; host->h_addr_list[i]; i++) {
- bcopy(host->h_addr_list[i],
- (char *)(ftpc->h_addr_list+i),sizeof(char *));
- DEBUG2("ftpGetHostAndPort: h_addr_list[%d] = %s\n",i,
- inet_ntoa(*(struct in_addr*)(ftpc->h_addr_list+i)));
- }
- bzero((char *)(ftpc->h_addr_list+i),sizeof(char *));
- ftpc->this_addr = ftpc->h_addr_list-1; /* preincrement later */
- return(0);
- }
- }
-
- /* - - - - - - - - */
- /* Connection establishment: */
- /*
- * This function goes through the list of addresses trying to connect to
- * the server. It is initially called from ftpNewContext(), and subsequently
- * whenever the ctrl socket it ready for writing (indicating connect()
- * completed, possibly with an error).
- */
- static void
- ftpConnectCallback(ftpc)
- FtpContext *ftpc;
- {
- int retcode;
- char msg[256];
-
- DEBUG2("ftpConnectCallback: ftpc=0x%x, state=%s\n",
- ftpc,ftpstatestr(ftpc->state));
- redo:
- switch (ftpc->state) {
- case FTPS_OPEN:
- ftpc->this_addr += 1; /* preincrement */
- DEBUG1("ftpConnectCallback: OPEN: %s\n",
- inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
- if (*(ftpc->this_addr)) { /* Not at last host */
- if (ftpGetSocket(ftpc) < 0) {
- ftpDone(ftpc);
- } else {
- /* Arrange to get called back if connect() would block */
- RegisterFtpFd(ftpc,ftpc->ctrl,O_RDWR,ftpConnectCallback);
- ftpc->state = FTPS_CONNECT;
- goto redo;
- }
- } else { /* All hosts failed */
- sprintf(msg,"Couldn't connect to host \"%s\"",ftpc->hostname);
- alert0(msg);
- ftpDone(ftpc);
- }
- break;
- case FTPS_CONNECT:
- /* We previously registered either above or below */
- UnregisterFtpFd(ftpc,ftpc->ctrl);
- retcode = ftpConnect(ftpc);
- if (retcode < 0) {
- DEBUG1("ftpConnectCallback: ftpConnect failed, closing ctrl %d\n",
- ftpc->ctrl);
- close(ftpc->ctrl);
- ftpc->ctrl = -1;
- ftpc->state = FTPS_OPEN; /* try next one */
- goto redo;
- } else if (retcode == 0) {
- DEBUG0("ftpConnectCallback: ftpConnect would block\n");
- /* Arrange to get called back when connect() won't block */
- RegisterFtpFd(ftpc,ftpc->ctrl,O_RDWR,ftpConnectCallback);
- ftpc->state = FTPS_CONNECT; /* try again when ready */
- } else {
- DEBUG0("ftpConnectCallback: ftpConnect ok\n");
- ftpInitReply(ftpc); /* ok... */
- ftpc->state = FTPS_CONNECTED; /* move on */
- }
- break;
- default:
- fprintf(stderr,"ftpConnectCallback: unknown state %d\n",ftpc->state);
- abort();
- }
- DEBUG0("ftpConnectCallback: done\n");
- }
-
- /* - - - - - - - - */
- /* Functions called by ftpConnectCallback(): */
- /*
- * This function opens a new socket for the ctrl connection, and sets
- * it up for non-blocking IO. Everything is setup for later connect().
- */
- static int
- ftpGetSocket(ftpc)
- FtpContext *ftpc;
- {
- DEBUG1("ftpGetSocket: addr=%s\n",
- inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
- /* Get a socket */
- if ((ftpc->ctrl=socket(AF_INET,SOCK_STREAM,0)) < 0) {
- sysError("socket(ftpGetSocket)");
- return(-1);
- }
- DEBUG1("ftpGetSocket: socket() returned %d\n",ftpc->ctrl);
- /* Setup socket for async i/o */
- MAKE_NONBLOCKING(ftpc->ctrl);
- /* Set up the address spec. */
- bzero((char *)&(ftpc->saddr),sizeof(struct sockaddr_in));
- ftpc->saddr.sin_family = AF_INET;
- ftpc->saddr.sin_port = ftpc->servport;
- ftpc->saddr.sin_addr = *((struct in_addr *)(ftpc->this_addr));
- /* Ready to connect() */
- DEBUG0("ftpGetSocket: done\n");
- return(0);
- }
-
- /*
- * Calls connect(). Returns -1 if this host is botched, 0 if connect()
- * would have blocked, or 1 if we should proceed.
- */
- static int
- ftpConnect(ftpc)
- FtpContext *ftpc;
- {
- int retcode,addrlen;
- char msg[256];
-
- sprintf(msg,"Connecting to %.200s (%s)",
- ftpc->hostname,inet_ntoa(*(struct in_addr*)(ftpc->this_addr)));
- status0(msg);
- DEBUG0("ftpConnect: calling connect()...\n");
- retcode = connect(ftpc->ctrl,(struct sockaddr *)&(ftpc->saddr),
- sizeof(struct sockaddr_in));
- if (retcode < 0 && errno == EINPROGRESS) {
- DEBUG0("ftpConnect: connect() EINPROGRESS\n");
- return(0);
- } else if (retcode < 0 && errno != EISCONN) {
- #ifdef DEBUG
- perror("ftpConnect: connect()");
- #endif
- return(-1);
- }
- #ifdef DEBUG
- else if (retcode < 0 && errno == EISCONN)
- fprintf(stderr,"ftpConnect: connect() EISCONN\n");
- else
- fprintf(stderr,"ftpConnect: connect() returned ok\n");
- #endif
- /* Get name (address) of socket */
- addrlen = sizeof(struct sockaddr_in);
- retcode =
- getsockname(ftpc->ctrl,(struct sockaddr *)&(ftpc->saddr),&addrlen);
- if (retcode < 0) {
- sysError("getsockname(ftpConnect)");
- return(-1);
- }
- DEBUG1("ftpConnect: getsockname() returned %s\n",
- inet_ntoa(ftpc->saddr.sin_addr));
- return(1);
- }
-
- /* - - - - - - - - */
- /* Reading server replies: */
- /*
- * This function must be called to initialize the reply in the FTPC and
- * to ensure that the appropriate callback is registered to read it.
- */
- static void
- ftpInitReply(ftpc)
- FtpContext *ftpc;
- {
- DEBUG1("ftpInitReply: ftpc=0x%lx\n",ftpc);
- ftpc->retcode = 0;
- ftpc->reply_len = 0;
- *(ftpc->reply) = '\0';
- ftpc->reply_state = FTPS_REPLY_CODE;
- /* We're expecting a reply, so register ctrl for reading only */
- RegisterFtpFd(ftpc,ftpc->ctrl,O_RDONLY,ftpReplyCallback);
- DEBUG0("ftpInitReply: done\n");
- }
-
- /*
- * This function is the low-level reply parser. It reads and
- * parses reply messages from the server, handles TELNET commands,
- * and translate codes in the reply prefix into integers. It stores the
- * reply code in ftpc->retcode and puts the text of the reply in ftpc->reply.
- * It is called whenever the ctrlconn is ready for reading and we are
- * expecting a reply (ie., after ftpInitReply()). Once the reply is
- * complete, we call ftpProcessReply().
- */
- static void
- ftpReplyCallback(ftpc)
- FtpContext *ftpc;
- {
- int n;
- unsigned char c,cc;
-
- for (;;) {
- /*
- DEBUG2("ftpReplyCallback: ftpc=0x%x, reply_state=%s\n",
- ftpc,ftpstatestr(ftpc->reply_state));
- */
- n = read(ftpc->ctrl,&c,1); /* Read a byte */
- if (n < 0 && errno != ITWOULDBLOCK) { /* Error */
- sysError("read(ftpReplyCallback)");
- DEBUG0("ftpReplyCallback: error reading ctrlconn\n");
- ftpDone(ftpc);
- return;
- } else if (n < 0 && errno == ITWOULDBLOCK) { /* Would block */
- /*DEBUG0("ftpReplyCallback: ctrlconn would block\n");*/
- return;
- } else if (n == 0) { /* EOF */
- *(ftpc->reply+ftpc->reply_len) = '\0';
- /* Could check against current retcode, if any... */
- if (ftpc->state == FTPS_QUIT)
- ftpc->retcode = FTP_SERVICE_CLOSING;
- else
- ftpc->retcode = FTP_SERVICE_UNAVAILABLE;
- DEBUG1("ftpReplyCallback: EOF: retcode = %d\n",ftpc->retcode);
- /* Reply is done, go process it */
- break;
- }
- /*
- * Otherwise, we got something, process it.
- */
- if (c == IAC) { /* Telnet IAC */
- ftpc->saved_state = ftpc->reply_state;
- ftpc->reply_state = FTPS_REPLY_IAC1;
- DEBUG1("ftpReplyCallback: IAC (saved_state=%d)\n",ftpc->saved_state);
- continue;
- } else if (c == '\r') { /* Skip <CR>, is this ok? */
- continue;
- }
- /*
- * We got something that's not IAC, process it.
- */
- /*DEBUG2("ftpReplyCallback: `%c' (%d)\n",c,c);*/
- switch (ftpc->reply_state) {
- case FTPS_REPLY_IAC1:
- DEBUG1("ftpReplyCallback: IAC %c",c);
- ftpc->reply_state = FTPS_REPLY_IAC2;
- break;
- case FTPS_REPLY_IAC2:
- switch (c) {
- case WILL:
- case WONT: c = DONT;
- break;
- case DO:
- case DONT: c = WONT;
- break;
- default:
- DEBUG0(" (ignored)\n");
- continue;
- }
- DEBUG1(", reply %c\n",c);
- cc = IAC;
- write(ftpc->ctrl,&cc,1);
- write(ftpc->ctrl,&c,1);
- ftpc->reply_state = FTPS_REPLY_IAC3;
- break;
- case FTPS_REPLY_IAC3:
- write(ftpc->ctrl,&c,1);
- ftpc->reply_state = ftpc->saved_state;
- DEBUG1("ftpReplyCallback: IAC done, restored state = %d\n",
- ftpc->reply_state);
- break;
- case FTPS_REPLY_CODE:
- if (c < '0' || c > '9') {
- *(ftpc->reply+ftpc->reply_len) = '\0';
- DEBUG1("ftpReplyCallback: CODE: retcode = %d\n",ftpc->retcode);
- goto done;
- }
- *(ftpc->reply+ftpc->reply_len++) = c;
- ftpc->retcode = ftpc->retcode * 10 + c - '0';
- /*DEBUG1("ftpReplyCallback: retcode now %d\n",ftpc->retcode);*/
- if (ftpc->retcode >= 100)
- ftpc->reply_state = FTPS_REPLY_CONT;
- break;
- case FTPS_REPLY_CONT:
- /* we reach here after we finished reading the code or when we
- * struck a line beginning with at least three digits, check if
- * this is the last line of the reply
- */
- *(ftpc->reply+ftpc->reply_len++) = c;
- if (c == '-') {
- /*
- DEBUG0("ftpReplyCallback: continuation\n");
- */
- ftpc->reply_state = FTPS_REPLY_MORE;
- } else {
- /*
- DEBUG0("ftpReplyCallback: final line\n");
- */
- ftpc->reply_state = FTPS_REPLY_LAST;
- }
- break;
- case FTPS_REPLY_LAST:
- if (c == '\n') {
- *(ftpc->reply+ftpc->reply_len) = '\0';
- /*
- DEBUG1("ftpReplyCallback: LAST: retcode = %d\n",ftpc->retcode);
- */
- goto done;
- } else {
- *(ftpc->reply+ftpc->reply_len++) = c;
- /*
- DEBUG3("ftpReplyCallback: LAST: %03d: \"%.*s\"\n",
- ftpc->reply_len,ftpc->reply_len,ftpc->reply);
- */
- }
- break;
- case FTPS_REPLY_MORE:
- *(ftpc->reply+ftpc->reply_len++) = c;
- /*
- DEBUG3("ftpReplyCallback: MORE: %03d: \"%.*s\"\n",
- ftpc->reply_len,ftpc->reply_len,ftpc->reply);
- */
- if (c == '\n') {
- ftpc->tmpcode = 0;
- ftpc->reply_state = FTPS_REPLY_CHCK;
- }
- break;
- case FTPS_REPLY_CHCK:
- if (c < '0' || c > '9') {
- *(ftpc->reply+ftpc->reply_len++) = c;
- /*
- DEBUG3("ftpReplyCallback: CHCK: %03d: \"%.*s\"\n",
- ftpc->reply_len,ftpc->reply_len,ftpc->reply);
- */
- ftpc->reply_state = FTPS_REPLY_MORE;
- } else {
- ftpc->tmpcode = ftpc->tmpcode * 10 + c - '0';
- if (ftpc->tmpcode >= 100) {
- if (ftpc->tmpcode != ftpc->retcode)
- ftpc->reply_state = FTPS_REPLY_MORE;
- else
- ftpc->reply_state = FTPS_REPLY_CONT;
- }
- }
- break;
- default:
- fprintf(stderr,"ftpReplyCallback: unknown reply_state %d\n",
- ftpc->reply_state);
- abort();
- }
- }
- /* if we get here, the reply is complete */
- done:
- UnregisterFtpFd(ftpc,ftpc->ctrl);
- if (ftpc->trace) {
- (*(ftpc->trace))(ftpc,0,ftpc->reply);
- }
- ftpProcessReply(ftpc);
- }
-
- /* - - - - - - - - */
- /*
- * This function implements the high-level FTP protocol using the state
- * field of the FTPC. It is called from ftpReplyCallback() once we've read
- * a reply and need to process it.
- */
- static void
- ftpProcessReply(ftpc)
- FtpContext *ftpc;
- {
- char cmd[256];
-
- DEBUG3("ftpProcessReply: ftpc=0x%x, state=%s, retcode=%d\n",
- ftpc,ftpstatestr(ftpc->state),ftpc->retcode);
- redo:
- switch (ftpc->state) {
- case FTPS_CONNECTED:
- if (ftpc->retcode == FTP_SERVICE_RDY_TIME) { /* delay NNN minutes */
- DEBUG0("ftpProcessReply: server not ready\n");
- ftpAlert(ftpc);
- } else if (ftpc->retcode == FTP_SERVICE_RDY_USER) {/* ready for USER */
- sprintf(cmd,"USER %s",ftpc->user);
- status0(cmd);
- ftpc->state = FTPS_USER;
- ftpSendCmd(ftpc,cmd);
- } else { /* FTP_SERVICE_UNAVAILABLE */
- ftpAlert(ftpc);
- ftpDone(ftpc);
- }
- break;
- case FTPS_USER:
- if (ftpc->retcode == FTP_LOGIN_OK) { /* USER ok, no PASS needed */
- ftpc->retcode = FTP_FILE_ACTION_OK;
- ftpc->state = FTPS_CWD;
- goto redo;
- } else if (ftpc->retcode == FTP_LOGIN_NEED_PASSWD) { /* USER ok */
- sprintf(cmd,"PASS %s",ftpc->pass); /* need PASS */
- status0(cmd);
- ftpc->state = FTPS_PASS;
- ftpSendCmd(ftpc,cmd);
- } else { /* ACCT needed or error */
- ftpHandleErrorThenQuit(ftpc);
- }
- break;
- case FTPS_PASS:
- if (ftpc->retcode == FTP_LOGIN_OK) { /* PASS ok, ready to go */
- ftpc->retcode = FTP_FILE_ACTION_OK;
- ftpc->state = FTPS_CWD;
- goto redo;
- } else { /* ACCT needed or error */
- ftpHandleErrorThenQuit(ftpc);
- }
- break;
- case FTPS_CWD:
- /* can come here direct from USER or PASS also... */
- if (ftpc->retcode == FTP_FILE_ACTION_OK) { /* last CWD ok */
- if (ftpc->wd == NULL || *(ftpc->wd) == '\0') { /* CWD done */
- ftpc->state = FTPS_TYPE;
- ftpSendType(ftpc);
- } else { /* send next part of CWD */
- ftpc->state = FTPS_CWD;
- ftpSendNextCwd(ftpc);
- }
- } else { /* last CWD failed */
- ftpHandleErrorThenQuit(ftpc);
- }
- break;
- case FTPS_TYPE:
- if (ftpc->retcode == FTP_COMMAND_OK) { /* TYPE ok */
- ftpc->retcode = FTP_FILE_ACTION_OK;
- ftpc->state = FTPS_READY;
- goto redo;
- } else { /* TYPE failed */
- ftpHandleErrorThenQuit(ftpc);
- }
- break;
- case FTPS_READY:
- /* can get here from TYPE or EOF */
- ftpc->this_file += 1;
- if (ftpc->this_file < ftpc->num_files) { /* files left to transfer */
- sprintf(cmd,"File %s?",ftpc->files[ftpc->this_file]);
- status0(cmd);
- if (ftpc->prompt && !ftpPrompt(ftpc)) {
- goto redo;
- }
- if (ftpSendPort(ftpc) == 0) { /* PORT ok locally */
- ftpc->state = FTPS_PORT;
- } else { /* PORT failed locally */
- ftpc->state = FTPS_QUIT; /* bag the whole thing */
- ftpSendCmd(ftpc,"QUIT");
- }
- } else { /* no files left to transfer */
- DEBUG0("ftpProcessReply: no more files\n");
- ftpc->state = FTPS_QUIT;
- ftpSendCmd(ftpc,"QUIT");
- }
- break;
- case FTPS_PORT:
- if (ftpc->retcode == FTP_COMMAND_OK) { /* PORT command ok */
- ftpc->state = FTPS_GETPUT;
- ftpSendGetPut(ftpc);
- } else { /* PORT failed */
- if (ftpc->port >= 0) {
- DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
- close(ftpc->port);
- ftpc->port = -1;
- }
- ftpHandleErrorThenQuit(ftpc);
- }
- break;
- case FTPS_GETPUT:
- if (FTP_REPLY_PRELIM(ftpc->retcode)) { /* dataconn ready */
- status0(ftpc->reply+4);
- if (sscanf(ftpc->reply,"%*[^(](%d bytes)",&(ftpc->this_size)) != 1)
- ftpc->this_size = 0;
- if (ftpAcceptDataConn(ftpc) < 0) { /* local failure */
- if (ftpc->port != -1) { /* give up */
- DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
- close(ftpc->port);
- ftpc->port = -1;
- }
- ftpc->state = FTPS_QUIT;
- ftpSendCmd(ftpc,"QUIT");
- } else if (ftpSetupLocalData(ftpc) < 0) { /* local failure */
- UnregisterFtpFd(ftpc,ftpc->data); /* don't give up */
- if (ftpc->data != -1) {
- DEBUG1("ftpProcessReply: closing data %d\n",ftpc->data);
- close(ftpc->data);
- ftpc->data = -1;
- }
- ftpc->state = FTPS_READY; /* do next file */
- goto redo;
- } else { /* all ok locally */
- ftpc->state = FTPS_TRANSFER;
- }
- } else if (ftpc->retcode == FTP_FILE_UNAVAILABLE || /* datacon */
- ftpc->retcode == FTP_ACTION_NOT_TAKEN) { /* failed */
- if (ftpc->port != -1) { /* minor */
- DEBUG1("ftpProcessReply: closing port %d\n",ftpc->port);
- close(ftpc->port); /* error */
- ftpc->port = -1;
- }
- ftpAlert(ftpc); /* error msg */
- ftpc->state = FTPS_READY; /* next file */
- goto redo;
- } else {
- ftpHandleErrorThenQuit(ftpc); /* real error */
- }
- break;
- case FTPS_TRANSFER:
- /* Shouldn't get here since dataconn is separately managed */
- DEBUG0("ftpProcessReply: called in state TRANSFER!\n");
- break;
- case FTPS_EOF:
- /* Called when the dataconn is closed, for whatever reason */
- if (!FTP_REPLY_OK(ftpc->retcode)) { /* dataconn closed error */
- ftpAlert(ftpc);
- }
- ftpc->state = FTPS_READY;
- goto redo;
- case FTPS_QUIT:
- if (ftpc->retcode != FTP_SERVICE_CLOSING) { /* error */
- ftpAlert(ftpc);
- }
- ftpDone(ftpc); /* close, deallocate */
- break;
- case FTPS_ABORT:
- if (ftpc->retcode == 552) { /* NIC-style abort */
- ftpc->state = FTPS_ABORT; /* get another reply */
- } else {
- if (ftpc->retcode != FTP_DATA_CLOSE_ABORT) { /* 426 */
- ftpAlert(ftpc); /* expected */
- }
- ftpc->state = FTPS_ABORT2; /* get real reply */
- ftpInitReply(ftpc);
- }
- break;
- case FTPS_ABORT2:
- if (!FTP_REPLY_OK(ftpc->retcode)) { /* abort error */
- ftpAlert(ftpc);
- }
- ftpCleanupDataConn(ftpc);
- ftpc->state = FTPS_READY;
- goto redo;
- default:
- fprintf(stderr,"ftpProcessReply: unknown state: %d\n",ftpc->state);
- abort();
- }
- DEBUG0("ftpProcessReply: done\n");
- }
-
- /*
- * This function just puts up an FTP error message.
- */
- static void
- ftpAlert(ftpc)
- FtpContext *ftpc;
- {
- char buf[256],*msg;
- int len;
-
- msg = ftpc->reply+4; /* skip code */
- len = strlen(msg);
- if (len < 230) {
- sprintf(buf,"FTP Error %d:\n %s",ftpc->retcode,msg);
- } else {
- sprintf(buf,"FTP Error %d: ...\n %.230s",ftpc->retcode,msg+len-230);
- }
- alert0(buf);
- }
-
- /*
- * This prints an error message, then sends the QUIT command (or
- * calls ftpDone() if the server shut down).
- */
- static void
- ftpHandleErrorThenQuit(ftpc)
- FtpContext *ftpc;
- {
- ftpAlert(ftpc);
- /* Don't bother with QUIT if remote host closing down */
- if (ftpc->retcode == FTP_SERVICE_UNAVAILABLE) {
- ftpDone(ftpc);
- } else {
- status0("Quitting...");
- ftpc->state = FTPS_QUIT;
- ftpSendCmd(ftpc,"QUIT");
- }
- }
-
- /*
- * This calls the done function (or ftpFreeContext() if none is defined
- * for FTPC. All connections should pass through here to be cleaned up
- * regardless of how they got here.
- */
- static void
- ftpDone(ftpc)
- FtpContext *ftpc;
- {
- DEBUG1("ftpDone: ftpc=0x%x\n",ftpc);
- if (ftpc->done != NULL) {
- (*(ftpc->done))(ftpc);
- } else {
- ftpFreeContext(ftpc);
- }
- DEBUG0("ftpDone: done\n");
- }
-
- /* - - - - - - - - */
- /* Functions called by ftpProcessReply(): */
-
- static void
- ftpSendType(ftpc)
- FtpContext *ftpc;
- {
- char cmd[16];
-
- DEBUG2("ftpSendType: ftpc=0x%x, type=%d\n",ftpc,ftpc->type);
- ftpc->state = FTPS_TYPE;
- switch (ftpc->type) {
- case TYPE_A:
- sprintf(cmd,"TYPE A");
- break;
- case TYPE_E:
- sprintf(cmd,"TYPE E");
- break;
- case TYPE_I:
- sprintf(cmd,"TYPE I");
- break;
- default:
- sprintf(cmd,"TYPE L %d",(char *)ftpc->type);
- }
- status0(cmd);
- ftpSendCmd(ftpc,cmd);
- DEBUG0("ftpSendType: done\n");
- }
-
- static void
- ftpSendNextCwd(ftpc)
- FtpContext *ftpc;
- {
- char *slash,cmd[256];
-
- DEBUG2("ftpSendNextCwd: ftpc=0x%x, wd=\"%s\"\n",ftpc,ftpc->wd);
- if (*(ftpc->wd) == '/') { /* Leading slash treated specially... */
- ftpc->state = FTPS_CWD;
- ftpSendCmd(ftpc,"CWD /");
- ftpc->wd += 1;
- } else { /* Normal case */
- if ((slash=index(ftpc->wd,'/')) != NULL) {
- *slash = '\0';
- }
- sprintf(cmd,"CWD %s",ftpc->wd);
- status0(cmd);
- ftpc->state = FTPS_CWD;
- ftpSendCmd(ftpc,cmd);
- if (slash) {
- ftpc->wd = slash+1;
- } else {
- /* set wd to end of string */
- while (*(ftpc->wd) != '\0')
- ftpc->wd += 1;
- }
- }
- DEBUG0("ftpSendNextCwd: done\n");
- }
-
- /*
- * Performs the PORT command. The new port is saved in f->port.
- * Returns < 0 if some local error, otherwise 0 if we got to sending
- * the command.
- */
- static int
- ftpSendPort(ftpc)
- FtpContext *ftpc;
- {
- char cmd[64];
- struct sockaddr_in addr;
- int addrlen;
-
- DEBUG1("ftpSendPort: ftpc=0x%lx\n",ftpc);
- if ((ftpc->port=socket(AF_INET,SOCK_STREAM,0)) < 0) {
- sysError("socket(ftpSendPort)");
- DEBUG0("ftpSendPort: returning -1\n");
- return(-1);
- }
- DEBUG1("ftpSendPort: socket() returned %d\n",ftpc->port);
- addr = ftpc->saddr;
- addr.sin_port = 0;
- if (bind(ftpc->port,(struct sockaddr *)&addr,
- sizeof(struct sockaddr_in)) < 0) {
- sysError("bind(ftpSendPort)");
- DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
- close(ftpc->port);
- DEBUG0("ftpSendPort: returning -1\n");
- return(-1);
- }
- DEBUG0("ftpSendPort: bind() succeeded\n");
- if (listen(ftpc->port,1) < 0) {
- sysError("listen(ftpSendPort)");
- DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
- close(ftpc->port);
- DEBUG0("ftpSendPort: returning -1\n");
- return(-1);
- }
- DEBUG0("ftpSendPort: listen() succeeded\n");
- addrlen = sizeof(struct sockaddr_in);
- if (getsockname(ftpc->port,(struct sockaddr *)&addr,&addrlen) < 0) {
- sysError("getsockname(ftpSendPort)");
- DEBUG1("ftpSendPort: closing port %d\n",ftpc->port);
- close(ftpc->port);
- DEBUG0("ftpSendPort: returning -1\n");
- return(-1);
- }
- DEBUG2("ftpSendPort: PORT address: %s; port: %d\n",
- inet_ntoa(addr.sin_addr),addr.sin_port);
- sprintf(cmd,"PORT %d,%d,%d,%d,%d,%d",
- (int)((unsigned char *)&addr.sin_addr.s_addr)[0],
- (int)((unsigned char *)&addr.sin_addr.s_addr)[1],
- (int)((unsigned char *)&addr.sin_addr.s_addr)[2],
- (int)((unsigned char *)&addr.sin_addr.s_addr)[3],
- (int)((unsigned char *)&addr.sin_port)[0],
- (int)((unsigned char *)&addr.sin_port)[1]);
- status0(cmd);
- ftpc->state = FTPS_PORT;
- ftpSendCmd(ftpc,cmd);
- DEBUG0("ftpSendPort: returning 0\n");
- return(0);
- }
-
-
- /*
- * After PORT is ok, send GET or PUT to open the dataconn.
- */
- static void
- ftpSendGetPut(ftpc)
- FtpContext *ftpc;
- {
- char cmd[MAXPATHLEN];
-
- DEBUG2("ftpSendGetPut: ftpc=0x%x, fcmd=%d\n",ftpc,ftpc->filecmd);
- ftpc->state = FTPS_GETPUT;
- switch (ftpc->filecmd) {
- case FTP_GET:
- sprintf(cmd,"RETR %s",ftpc->files[ftpc->this_file]);
- break;
- case FTP_PUT:
- sprintf(cmd,"STOR %s",ftpc->files[ftpc->this_file]);
- break;
- }
- status0(cmd);
- ftpSendCmd(ftpc,cmd);
- DEBUG0("ftpSendGetPut: done\n");
- }
-
- /*
- * After GET/PUT, accepts the connection from the PORT and closes the
- * the PORT. Returns the dataconn fd (also in ftpc->data).
- */
- static int
- ftpAcceptDataConn(ftpc)
- FtpContext *ftpc;
- {
- int datacon,addrlen;
- struct sockaddr_in addr;
-
- DEBUG1("ftpAcceptDataConn: fcmd %d successful\n",ftpc->filecmd);
- addrlen = sizeof(struct sockaddr_in);
- if ((datacon=accept(ftpc->port,(struct sockaddr *)&addr,&addrlen)) < 0) {
- sysError("accept(ftpAcceptDataConn)");
- DEBUG0("ftpAcceptDataConn: returning -1\n");
- return(-1);
- }
- DEBUG1("ftpAcceptDataConn: closing port %d\n",ftpc->port);
- close(ftpc->port);
- ftpc->port = -1;
- DEBUG1("ftpAcceptDataConn: registering dataconn %d\n",datacon);
- ftpc->data = datacon;
- MAKE_NONBLOCKING(ftpc->data);
- RegisterFtpFd(ftpc,ftpc->data,O_RDONLY,ftpDataCallback);
- DEBUG1("ftpAcceptDataConn: returning dataconn = %d\n",datacon);
- return(datacon);
- }
-
- /*
- * Once the remote file is ready for transfer through the dataconn, this
- * function sets up the local end. Returns -1 on error, otherwise 0.
- */
- static int
- ftpSetupLocalData(ftpc)
- FtpContext *ftpc;
- {
- char filename[MAXPATHLEN];
-
- DEBUG1("ftpSetupLocalData: ftpc=0x%x\n",ftpc);
- if (ftpc->local_dir && *(ftpc->local_dir)) {
- sprintf(filename,"%s/%s",ftpc->local_dir,ftpc->files[ftpc->this_file]);
- } else {
- strcpy(filename,ftpc->files[ftpc->this_file]);
- }
- DEBUG1("ftpSetupLocalData: opening \"%s\"\n",filename);
- if (ftpc->filecmd == FTP_GET) {
- ftpc->local_fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0644);
- } else {
- ftpc->local_fd = open(filename,O_RDONLY,0);
- }
- if (ftpc->local_fd < 0) {
- sysError(filename);
- DEBUG0("ftpSetupLocalData: returning -1\n");
- return(-1);
- }
- DEBUG1("ftpSetupLocalData: got local fd = %d\n",ftpc->local_fd);
- ftpc->num_bytes = 0;
- DEBUG0("ftpSetupLocalData: returning 0\n");
- return(0);
- }
-
- /* - - - - - - - - */
- /* Data connection processing: */
- /*
- * This function is called whenever the dataconn is ready for read/write.
- */
- static void
- ftpDataCallback(ftpc)
- FtpContext *ftpc;
- {
- DEBUG2("ftpDataCallback: ftpc=0x%x, fcmd=%d\n",ftpc,ftpc->filecmd);
- switch (ftpc->filecmd) {
- case FTP_GET:
- ftpReadData(ftpc);
- break;
- case FTP_PUT:
- ftpWriteData(ftpc);
- break;
- default:
- fprintf(stderr,"ftpDataCallback: unknown cmd: %d\n",ftpc->filecmd);
- abort();
- }
- DEBUG0("ftpDataCallback: done\n");
- }
-
- /*
- * This function is called to read from the dataconn and write to the
- * local_fd. On EOF, it sets the state field to FTPS_EOF and calls
- * ftpInitReply() to continue the session. On error, it calls
- * ftpSendAbort() and then ftpInitReply(). Otherwise the transfer
- * is ongoing, so it doesn't do anything.
- */
- static void
- ftpReadData(ftpc)
- FtpContext *ftpc;
- {
- char buf[BUFSIZ*4],*s,*t,msg[256];
- int nread,i;
-
- DEBUG0("ftpReadData: reading\n");
- /* Read one chunk of stuff and write it locally (don't loop or
- * X won't get a chance to dispatch).
- */
- nread = read(ftpc->data,buf,sizeof(buf));
- if (nread > 0) {
- /* Strip CR's if necessary */
- if (ftpc->type == TYPE_A && ftpc->stripcr) {
- DEBUG0("ftpReadData: stripping <CR>\n");
- for (s=t=buf,i=nread; i--; ++s) {
- if (*s != '\r')
- *t++ = *s;
- }
- }
- /* Write locally */
- if (write(ftpc->local_fd,buf,nread) != nread) {
- /* Error when writing */
- sysError("write(ftpReadData)");
- ftpCleanupDataConn(ftpc);
- ftpSendAbort(ftpc);
- ftpInitReply(ftpc);
- } else {
- /* write was ok */
- ftpc->num_bytes += nread;
- DEBUG1("ftpReadData: total = %d\n",ftpc->num_bytes);
- if (ftpc->this_size != 0) {
- sprintf(msg,"%.200s: %d/%d",ftpc->files[ftpc->this_file],
- (char *)(ftpc->this_size),(char *)(ftpc->num_bytes));
- } else {
- sprintf(msg,"%.200s: %d bytes",ftpc->files[ftpc->this_file],
- (char *)(ftpc->num_bytes));
- }
- status0(msg);
- }
- } else if (nread < 0 && errno == ITWOULDBLOCK) {
- /* Nothing more ready now, keep waiting */
- DEBUG0("ftpReadData: dataconn would block\n");
- } else if (nread < 0) {
- /* Error when reading */
- sysError("read(ftpReadData)");
- DEBUG0("ftpReadData: error reading dataconn\n");
- ftpCleanupDataConn(ftpc);
- ftpSendAbort(ftpc);
- ftpInitReply(ftpc);
- } else if (nread == 0) {
- /* We got EOF on remote file. */
- DEBUG0("ftpReadData: EOF on dataconn\n");
- ftpCleanupDataConn(ftpc);
- if (ftpc->done1 != NULL)
- (*(ftpc->done1))(ftpc);
- ftpc->state = FTPS_EOF;
- ftpInitReply(ftpc);
- }
- }
-
- /*ARGSUSED*/
- static void
- ftpWriteData(ftpc)
- FtpContext *ftpc;
- {
- /*EMPTY*/
- }
-
- static void
- ftpCleanupDataConn(ftpc)
- FtpContext *ftpc;
- {
- /* Note: We may have already unregistered, eg, in ftpAbort(). */
- if (ftpc->state != FTPS_ABORT2)
- UnregisterFtpFd(ftpc,ftpc->data);
- if (ftpc->data != -1) {
- DEBUG1("ftpCleanupDataConn: closing data %d\n",ftpc->data);
- close(ftpc->data);
- ftpc->data = -1;
- }
- if (ftpc->local_fd != -1) {
- DEBUG1("ftpCleanupDataConn: closing local_fd %d\n",ftpc->local_fd);
- close(ftpc->local_fd);
- ftpc->local_fd = -1;
- }
- }
-
- /* - - - - - - - - */
- /* Sending commands to the server: */
- /*
- * Adds CRLF and calls to ftpStartSendingCmd().
- */
- static void
- ftpSendCmd(ftpc,cmd)
- FtpContext *ftpc;
- char *cmd;
- {
- char buf[MAXPATHLEN];
-
- if (ftpc->trace) {
- (*(ftpc->trace))(ftpc,1,cmd);
- }
- sprintf(buf,"%s\r\n",cmd);
- ftpStartSendingCmd(ftpc,buf);
- }
-
- /*
- * Handles abort processing by sending the Telnet abort sequence in the
- * out-of-band stream of the ctrl conn and the ABOR command on the regular
- * stream.
- */
- static void
- ftpSendAbort(ftpc)
- FtpContext *ftpc;
- {
- char buf[8];
- int sent;
-
- DEBUG1("ftpSendAbort: ftpc=0x%lx\n",ftpc);
- DEBUG0("ftpSendAbort: sending <IAC><IP><IAC> in OOB\n");
- sprintf(buf,"%c%c%c",IAC,IP,IAC);
- do {
- sent = send(ftpc->ctrl,buf,3,MSG_OOB);
- } while (sent == -1 && errno == EINTR);
- if (sent != 3) {
- sysError("send(ftpSendAbort)");
- ftpDone(ftpc);
- }
- DEBUG0("ftpSendAbort: sending <DM>ABOR\n");
- sprintf(buf,"%cABOR\r\n",DM);
- do {
- sent = write(ftpc->ctrl,buf,strlen(buf));
- } while (sent == -1 && errno == EINTR);
- if (sent != strlen(buf)) {
- sysError("write(ftpSendAbort)");
- ftpDone(ftpc);
- }
- ftpc->state = FTPS_ABORT;
- ftpInitReply(ftpc);
- status0("Aborting...");
- DEBUG0("ftpSendAbort: done\n");
- }
-
- /*
- * This function starts sending CMD to the server over the control
- * connection. The writes can block, hence this has to be able to finish
- * later, but since they usually don't block, we don't set up to retry
- * unless we have to. If the command is completely sent, then we call
- * ftpInitReply() otherwise we setup to finish by registering the ctrlconn
- * for writing via callback ftpWriteCallback().
- */
- static void
- ftpStartSendingCmd(ftpc,cmd)
- FtpContext *ftpc;
- char *cmd;
- {
- int cmdlen,numsent;
-
- DEBUG2("ftpStartSendingCmd: ftpc=0x%x: \"%s\"\n",ftpc,cmd);
- cmdlen = strlen(cmd);
- numsent = write(ftpc->ctrl,cmd,cmdlen);
- if (numsent < 0 && errno != ITWOULDBLOCK) {
- sysError("write(ftpStartSendingCmd)");
- DEBUG0("ftpStartSendingCmd: error writing ctrlconn\n");
- ftpDone(ftpc);
- return;
- } else if (numsent == cmdlen) {
- DEBUG0("ftpStartSendingCmd: write all done\n");
- ftpInitReply(ftpc);
- return;
- } else if (errno == ITWOULDBLOCK) {
- /* Otherwise the write would block, nothing sent */
- DEBUG0("ftpStartSendingCmd: write would block\n");
- numsent = 0;
- } else {
- /* Incomplete write */
- DEBUG1("ftpStartSendingCmd: write incomplete (%d bytes sent)\n",numsent);
- }
- /* Either way, if we get here there's something left do */
- cmd += numsent;
- if ((ftpc->cmd=malloc(strlen(cmd)+1)) == NULL) {
- sysError("malloc(ftpStartSendingCmd)");
- ftpDone(ftpc);
- return;
- }
- strcpy(ftpc->cmd,cmd);
- DEBUG1("ftpStartSendingCmd: pending: \"%s\"\n",ftpc->cmd);
- RegisterFtpFd(ftpc,ftpc->ctrl,O_WRONLY,ftpWriteCallback);
- }
-
- /*
- * This function is called when the ctrlconn is ready for writing.
- * It sends as much as possible. If that completes the cmd, then it
- * calls ftpInitReply(), otherwise updates the cmd with what's left
- * and returns.
- */
- static void
- ftpWriteCallback(ftpc)
- FtpContext *ftpc;
- {
- char *cmd;
- int cmdlen,numsent;
-
- cmd = ftpc->cmd;
- DEBUG2("ftpWriteCallback: ftpc=0x%x: \"%s\"\n",ftpc,cmd);
- cmdlen = strlen(ftpc->cmd);
- numsent = write(ftpc->ctrl,cmd,cmdlen);
- if (numsent < 0 && errno != ITWOULDBLOCK) {
- sysError("write(ftpWriteCallback)");
- DEBUG0("ftpWriteCallback: error writing ctrlconn\n");
- ftpDone(ftpc);
- return;
- } else if (numsent == cmdlen) {
- DEBUG0("ftpWriteCallback: write all done\n");
- UnregisterFtpFd(ftpc,ftpc->ctrl);
- free(ftpc->cmd);
- ftpInitReply(ftpc);
- return;
- } else if (errno == ITWOULDBLOCK) {
- /* Otherwise the write would block, nothing sent */
- DEBUG0("ftpWriteCallback: write would block\n");
- numsent = 0;
- } else {
- /* Incomplete write */
- DEBUG1("ftpWriteCallback: write incomplete (%d bytes sent)\n",numsent);
- }
- /* Either way, if we get here there's something left do */
- cmd += numsent;
- free(ftpc->cmd);
- if ((ftpc->cmd=malloc(strlen(cmd)+1)) == NULL) {
- sysError("malloc(ftpWriteCallback)");
- ftpDone(ftpc);
- return;
- }
- strcpy(ftpc->cmd,cmd);
- DEBUG1("ftpWriteCallback: pending: \"%s\"\n",ftpc->cmd);
- }
-
- /* - - - - - - - - */
- /* Printing utilities for debugging */
-
- #ifdef DEBUG
- char *
- ftpstatestr(state)
- int state;
- {
- char buf[8];
-
- switch (state) {
- /* state */
- case FTPS_OPEN: return("OPEN");
- case FTPS_CONNECT: return("CONNECT");
- case FTPS_CONNECTED: return("CONNECTED");
- case FTPS_USER: return("USER");
- case FTPS_PASS: return("PASS");
- case FTPS_CWD: return("CWD");
- case FTPS_TYPE: return("TYPE");
- case FTPS_READY: return("READY");
- case FTPS_PORT: return("PORT");
- case FTPS_GETPUT: return("GETPUT");
- case FTPS_TRANSFER: return("TRANSFER");
- case FTPS_EOF: return("EOF");
- case FTPS_QUIT: return("QUIT");
- case FTPS_ABORT: return("ABORT");
- case FTPS_ABORT2: return("ABORT2");
- /* reply_state */
- case FTPS_REPLY_CODE: return("CODE");
- case FTPS_REPLY_CONT: return("CONT");
- case FTPS_REPLY_LAST: return("LAST");
- case FTPS_REPLY_MORE: return("MORE");
- case FTPS_REPLY_CHCK: return("CHCK");
- case FTPS_REPLY_IAC1: return("IAC1");
- case FTPS_REPLY_IAC2: return("IAC2");
- case FTPS_REPLY_IAC3: return("IAC3");
- default: sprintf(buf,"?%d?",state);
- return(buf);
- }
- }
- #endif /* DEBUG */
-
-
- /* - - - - - - - - */
- /* Sample code for standalone client */
-
- #ifdef STANDALONE
-
- /* This is from Brendan... */
- #include <sys/types.h> /* this may/will define FD_SET etc */
- #ifdef u3b2
- # include <sys/inet.h> /* THIS does FD_SET etc on AT&T 3b2s. */
- #endif
-
- fd_set readfds,writefds;
- typedef void (*PF)();
- PF funcs[FD_SETSIZE];
- FtpContext *contexts[FD_SETSIZE];
-
- void
- RegisterFtpFd(ftpc,fd,flags,func)
- FtpContext *ftpc;
- int fd,flags;
- void (*func)();
- {
- switch (flags) {
- case O_RDONLY:
- fprintf(stderr,"REGISTER fd %d, flags=RO\n",fd);
- FD_SET(fd,&readfds);
- FD_CLR(fd,&writefds);
- break;
- case O_WRONLY:
- fprintf(stderr,"REGISTER fd %d, flags=WO\n",fd);
- FD_CLR(fd,&readfds);
- FD_SET(fd,&writefds);
- break;
- case O_RDWR:
- fprintf(stderr,"REGISTER fd %d, flags=RW\n",fd);
- FD_SET(fd,&readfds);
- FD_SET(fd,&writefds);
- break;
- }
- funcs[fd] = func;
- contexts[fd] = ftpc;
- }
-
- /*ARGSUSED*/
- void
- UnregisterFtpFd(ftpc,fd)
- FtpContext *ftpc;
- int fd;
- {
- fprintf(stderr,"UNREGISTER fd %d\n",fd);
- FD_CLR(fd,&readfds);
- FD_CLR(fd,&writefds);
- funcs[fd] = NULL;
- contexts[fd] = NULL;
- }
-
- void
- done(ftpc)
- FtpContext *ftpc;
- {
- fprintf(stderr,"DONE!\n");
- ftpFreeContext(ftpc);
- exit(0);
- }
-
- void
- done1(ftpc)
- FtpContext *ftpc;
- {
- fprintf(stderr,"DONE1: \"%s\"\n",ftpc->files[ftpc->this_file]);
- }
-
- /*ARGSUSED*/
- void
- trace(ftpc,who,text)
- FtpContext *ftpc;
- int who; /* 0 => recvd, non-0 => sent */
- char *text;
- {
- fprintf(stderr,"TRACE: ");
- if (who) /* non-zero == us */
- fprintf(stderr,"ftp> ");
- fprintf(stderr,"%s",text);
- if (*(text+strlen(text)-1) != '\n')
- fprintf(stderr,"\n",text);
- }
-
- char *program;
-
- main(argc,argv)
- int argc;
- char *argv[];
- {
- char *host,*cwd;
- FtpContext *ftpc;
- fd_set rfds,wfds;
- int fd;
-
- if (argc < 3) {
- fprintf(stderr,"usage: %s host cwd files...\n",argv[0]);
- exit(1);
- }
- argc -= 1;
- program = *argv++;
- argc -= 1;
- host = *argv++;
- argc -= 1;
- cwd = *argv++;
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
- ftpc = ftpNewContext(host,"anonymous","ferguson@cs.rochester.edu",
- cwd,TYPE_I,1,1,FTP_GET,argv,argc,trace,done1,done);
- printf("Calling ftpStart...\n");
- ftpStart(ftpc);
- while (1) {
- bcopy((char *)&readfds,(char *)&rfds,sizeof(fd_set));
- bcopy((char *)&writefds,(char *)&wfds,sizeof(fd_set));
- fprintf(stderr,"select(): R=");
- for (fd=0; fd < FD_SETSIZE; fd++) {
- if (FD_ISSET(fd,&rfds))
- fprintf(stderr,"%d,",fd);
- }
- fprintf(stderr," W=");
- for (fd=0; fd < FD_SETSIZE; fd++) {
- if (FD_ISSET(fd,&wfds))
- fprintf(stderr,"%d,",fd);
- }
- fprintf(stderr,"\n");
- if (select(FD_SETSIZE,&rfds,&wfds,NULL,NULL) < 0) {
- perror(program);
- exit(1);
- }
- for (fd=0; fd < FD_SETSIZE; fd++) {
- if (FD_ISSET(fd,&rfds) || FD_ISSET(fd,&wfds)) {
- fprintf(stderr,"selected fd %d...\n",fd);
- (*(funcs[fd]))(contexts[fd]);
- }
- }
- }
- }
-
- void
- sysError(s)
- char *s;
- {
- extern int errno;
- extern char *sys_errlist[];
- fprintf(stderr,"SYSERR: %s: %s\n",s,sys_errlist[errno]);
- }
-
- void
- alert0(s)
- char *s;
- {
- fprintf(stderr,"ALERT: %s\n",s);
- }
-
- void
- status0(s)
- char *s;
- {
- fprintf(stderr,"STATUS: %s\n",s);
- }
-
- int
- ftpPrompt(ftpc)
- FtpContext *ftpc;
- {
- char c;
-
- fprintf(stderr,"CONFIRM: %s %s [ynaq]",
- ftpfilecmdstr(ftpc->filecmd),ftpc->files[ftpc->this_file]);
- c = getchar();
- if (c != '\n')
- (void)getchar();
- switch (c) {
- case 'y': return(1);
- case 'n': return(0);
- case 'a': ftpc->prompt = 0;
- return(1);
- case 'q': ftpc->this_file = ftpc->num_files;
- return(0);
- default: return(1);
- }
- }
- #endif /*STANDALONE*/
-