/* ** Copyright 2000-2004 University of Illinois Board of Trustees ** Copyright 2000-2004 Mark D. Roth ** All rights reserved. ** ** ftpdata.c - FTP data connection code ** ** Mark D. Roth */ #include #include #include #include #include #include #include #include #ifdef STDC_HEADERS # include # include #endif #ifdef HAVE_UNISTD_H # include #endif /* initialize an ftp-data connection */ int _ftp_data_init(FTP *ftp) { struct sockaddr_in addr; socklen_t addrlen; int code; char buf[FTPBUFSIZE]; char *cp; short ta[6]; unsigned long tmpaddr; unsigned short port; /* ** if the data connection handle is already initialized, ** that means there must already be an established data ** connection, so return EAGAIN */ if (ftp->ftp_data != NULL || ftp->ftp_data_listen != NULL) { errno = EAGAIN; return -1; } if (BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_PASV)) { /* PASV mode */ if (_ftp_send_command(ftp, "PASV") == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code != 227) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } cp = strpbrk(buf, "0123456789"); if (cp == NULL) { errno = EINVAL; return -1; } if (sscanf(cp, "%hd,%hd,%hd,%hd,%hd,%hd", &ta[0], &ta[1], &ta[2], &ta[3], &ta[4], &ta[5]) != 6) { errno = EINVAL; return -1; } snprintf(buf, sizeof(buf), "%d.%d.%d.%d:%d", ta[0], ta[1], ta[2], ta[3], ((ta[4] * 256) + ta[5])); if (fget_netio_new(&(ftp->ftp_data), FTPBUFSIZE, buf, ftp->ftp_io_timeout, 0, 0) == -1) return -1; } else { /* PORT mode */ /* ** get local IP address from control connection, ** since data connection should listen on the same ** network interface */ addrlen = sizeof(addr); if (fget_netio_local_addr(ftp->ftp_control, (struct sockaddr *)&addr, &addrlen, NULL, 0) == -1) return -1; /* ** bind to port and start listening for connection ** note: we don't specify a port, since we're ** letting the kernel assign us a random local port */ if (fget_netio_listen(&(ftp->ftp_data_listen), inet_ntoa(addr.sin_addr), 1) == -1) return -1; /* ** find out what port the kernel assigned us */ addrlen = sizeof(addr); if (fget_netio_local_addr(ftp->ftp_data_listen, (struct sockaddr *)&addr, &addrlen, NULL, 0) == -1) goto data_init_error; /* ** send PORT command */ tmpaddr = addr.sin_addr.s_addr; port = addr.sin_port; if (_ftp_send_command(ftp, "PORT %d,%d,%d,%d,%d,%d", ((unsigned char *)&tmpaddr)[0], ((unsigned char *)&tmpaddr)[1], ((unsigned char *)&tmpaddr)[2], ((unsigned char *)&tmpaddr)[3], ((unsigned char *)&port)[0], ((unsigned char *)&port)[1]) == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) goto data_init_error; if (code != 200) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; goto data_init_error; } } return 0; data_init_error: if (ftp->ftp_data_listen != NULL) { fget_netio_free(ftp->ftp_data_listen); ftp->ftp_data_listen = NULL; } return -1; } /* establish an ftp-data connection */ int _vftp_data_start(FTP *ftp, char *fmt, va_list args) { char buf[FTPBUFSIZE]; int code; /* ** Note: We use fget_netio_vwrite_line() directly here, since ** the data connection is already open if we're in PASV mode, ** and _vftp_send_command() won't work while the data connection ** is open. */ if (fget_netio_vwrite_line(ftp->ftp_control, ftp->ftp_io_timeout, fmt, args) == -1 || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) goto data_start_error; if (code != 150 && code != 125) { if (code == 421) errno = ECONNRESET; else if (code == 450) errno = ETXTBSY; else if (code == 451) errno = EAGAIN; else if (code == 452) errno = ENOSPC; else if (code == 550 || code == 530) errno = EACCES; else errno = EINVAL; goto data_start_error; } if (! BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_PASV)) { if (fget_netio_accept(ftp->ftp_data_listen, FTPBUFSIZE, ftp->ftp_io_timeout, &(ftp->ftp_data)) == -1) goto data_start_error; fget_netio_free(ftp->ftp_data_listen); ftp->ftp_data_listen = NULL; } return 0; data_start_error: if (ftp->ftp_data_listen != NULL) { fget_netio_free(ftp->ftp_data_listen); ftp->ftp_data_listen = NULL; } if (ftp->ftp_data != NULL) { fget_netio_free(ftp->ftp_data); ftp->ftp_data = NULL; } return -1; } /* stdarg front-end for _vftp_data_start() */ int _ftp_data_start(FTP *ftp, char *fmt, ...) { va_list args; int retval; va_start(args, fmt); retval = _vftp_data_start(ftp, fmt, args); va_end(args); return retval; } /* establish an ftp-data connection */ int _vftp_data_connect(FTP *ftp, char *fmt, va_list args) { if (_ftp_data_init(ftp) == -1) return -1; if (_vftp_data_start(ftp, fmt, args) == -1) return -1; return 0; } /* stdarg front-end for _vftp_data_connect() */ int _ftp_data_connect(FTP *ftp, char *fmt, ...) { va_list args; int retval; va_start(args, fmt); retval = _vftp_data_connect(ftp, fmt, args); va_end(args); return retval; } /* TELNET protocol escape sequences - used by _ftp_data_abort() */ static const unsigned char telnet_ip[] = { IAC, IP }; static const unsigned char telnet_synch[] = { IAC, SYNCH }; /* close data connection */ static int _ftp_data_abort(FTP *ftp) { int code; char buf[FTPBUFSIZE]; #ifdef DEBUG printf("==> _ftp_data_abort()\n"); #endif /* ** send ABOR command */ if (fget_netio_write(ftp->ftp_control, ftp->ftp_io_timeout, (char *)telnet_ip, sizeof(telnet_ip), 0) == -1 || fget_netio_write(ftp->ftp_control, ftp->ftp_io_timeout, (char *)telnet_synch, sizeof(telnet_synch), NETIO_WRITE_OOB) == -1 || fget_netio_write_line(ftp->ftp_control, ftp->ftp_io_timeout, "ABOR") == -1) return -1; /* close ftp-data socket */ if (ftp->ftp_data_listen != NULL) { fget_netio_free(ftp->ftp_data_listen); ftp->ftp_data_listen = NULL; } if (ftp->ftp_data != NULL) { fget_netio_free(ftp->ftp_data); ftp->ftp_data = NULL; } /* ** CASE 1: ** if we were in the middle of a service request, ** the server will give the following responses: ** ** 426 service request terminated abnormally ** 226 ABOR request was successful ** ** CASE 2: ** if the server thinks that the service request already ** finished (e.g., it already sent us all the data, but we ** haven't read it all yet), then it will give the following ** responses: ** ** 226 normal termination of the service request ** 225 ABOR request successful */ if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code != 426 && code != 226) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code != 226 && code != 225) { if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } return 0; } /* close data connection */ int _ftp_data_close(FTP *ftp) { int code, eof, retval = 0; char buf[FTPBUFSIZE]; #ifdef DEBUG printf("==> _ftp_data_close()\n"); #endif eof = fget_netio_eof(ftp->ftp_data); if (! eof && BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_ABOR)) return _ftp_data_abort(ftp); /* ** if FTP_OPT_USE_ABOR is not set, we'll try to abort by ** simply closing the data connection and waiting for the ** server to send an error response. ** ** NOTE: there is some dissention about whether this will work ** properly with all servers. Brandon Allbery notes: ** ** 20:47 coll: 426? always? I've seen them ** (a) return random shit (b) hang (c) appear to recover ** but fail on subsequent commands */ if (ftp->ftp_data_listen != NULL) { fget_netio_free(ftp->ftp_data_listen); ftp->ftp_data_listen = NULL; } if (ftp->ftp_data != NULL) { retval = fget_netio_free(ftp->ftp_data); ftp->ftp_data = NULL; } /* ** CASE 1: ** the server thinks that the service request already finished ** (whether because it really did, or just because it already sent ** us all the data, even if we haven't actually read it all yet), ** so it will respond with: ** ** 226 normal termination of the service request ** ** CASE 2: ** we were in the middle of a service request, so the server ** responds with: ** ** 426 service request terminated abnormally */ if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1) return -1; if (code == 226) return retval; if (! eof && code == 426) return retval; if (code == 421) errno = ECONNRESET; else errno = EINVAL; return -1; } /* read from data connection */ ssize_t _ftp_data_read(FTP *ftp, char *buf, size_t bufsize) { if (ftp->ftp_data == NULL) { errno = EINVAL; return -1; } return fget_netio_read(ftp->ftp_data, ftp->ftp_io_timeout, buf, bufsize); } /* write to data connection */ ssize_t _ftp_data_write(FTP *ftp, char *buf, size_t bufsize) { if (ftp->ftp_data == NULL) { errno = EINVAL; return -1; } return fget_netio_write(ftp->ftp_data, ftp->ftp_io_timeout, buf, bufsize, 0); }