/*
** 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 <roth@feep.net>
*/
#include <internal.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#ifdef STDC_HEADERS
# include <string.h>
# include <stdarg.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#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 <allbery_b> 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);
}
syntax highlighted by Code2HTML, v. 0.9.1