/*
** Copyright 2000-2004 University of Illinois Board of Trustees
** Copyright 2000-2004 Mark D. Roth
** All rights reserved.
**
** handle.c - code to initialize an FTP handle for libfget
**
** Mark D. Roth <roth@feep.net>
*/
#include <internal.h>
#include <stdio.h>
#include <errno.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <string.h>
# include <stdarg.h>
#endif
#include <strings.h>
const char libfget_version[] = PACKAGE_VERSION;
const int libfget_is_thread_safe =
#if defined(HAVE_GETHOSTBYNAME_R) && defined(HAVE_GETSERVBYNAME_R) && defined(HAVE_LOCALTIME_R)
1;
#else
0;
#endif
/*
** ftp_connect() - open a connection to hostname
*/
int
ftp_connect(FTP **ftp, char *hostname, char *banner, size_t bannersize,
unsigned short flags, ...)
{
va_list args;
int code;
#ifdef DEBUG
printf("==> ftp_connect(ftp=0x%lx, hostname=\"%s\", banner=0x%lx, "
"bannersize=%lu, flags=%hu, ...)\n",
ftp, hostname, banner, (unsigned long)bannersize, flags);
#endif
/*
** allocate and initialize FTP handle
*/
*ftp = (FTP *)calloc(1, sizeof(FTP));
if (*ftp == NULL)
return -1;
_ftp_dircache_init(*ftp);
_ftp_init_options(*ftp);
/* process stdarg options */
va_start(args, flags);
_vftp_set_options(*ftp, args);
va_end(args);
/*
** establish control connection
*/
if (fget_netio_new(&((*ftp)->ftp_control), FTPBUFSIZE, hostname,
(*ftp)->ftp_io_timeout,
(BIT_ISSET(flags, FTP_CONNECT_DNS_RR)
? NETIO_CONNECT_DNS_RR
: 0),
NETIO_OPT_DEFAULT_SERVICE, "ftp",
NETIO_OPT_DEFAULT_PORT, (unsigned short)21,
NETIO_OPT_SEND_HOOK, (*ftp)->ftp_send_hook,
NETIO_OPT_RECV_HOOK, (*ftp)->ftp_recv_hook,
NETIO_OPT_HOOK_HANDLE, *ftp,
NETIO_OPT_HOOK_DATA, (*ftp)->ftp_hook_data,
0) == -1)
goto connect_error;
/*
** read banner from server
*/
do
{
if (_ftp_get_response(*ftp, &code, banner, bannersize) == -1)
goto connect_error;
}
while (code == 120);
if (code != 220)
{
if (code == 421)
errno = ECONNRESET;
else
errno = EINVAL;
goto connect_error;
}
return 0;
connect_error:
if (*ftp != NULL)
{
if ((*ftp)->ftp_control != NULL)
fget_netio_free((*ftp)->ftp_control);
free(*ftp);
*ftp = NULL;
}
return -1;
}
/* check for server features */
static int
_ftp_feat(FTP *ftp)
{
char buf[FTPBUFSIZE];
char *linep, *nextp = buf;
int code;
/* check if the server supports the FEAT command */
if (_ftp_send_command(ftp, "FEAT") == -1
|| _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
return -1;
if (code == 211)
{
BIT_SET(ftp->ftp_features, FTP_FEAT_FEAT);
while ((linep = strsep(&nextp, "\n")) != NULL)
{
if (*linep != ' ')
continue;
#ifdef DEBUG
printf(" _ftp_feat(): linep=\"%s\"\n", linep);
#endif
/* skip leading space */
linep++;
if (strncasecmp(linep, "SIZE", 4) == 0)
BIT_SET(ftp->ftp_features, FTP_FEAT_SIZE);
else if (strncasecmp(linep, "MDTM", 4) == 0)
BIT_SET(ftp->ftp_features, FTP_FEAT_MDTM);
else if (strncasecmp(linep, "REST STREAM", 11) == 0)
BIT_SET(ftp->ftp_features,
FTP_FEAT_REST_STREAM);
else if (strncasecmp(linep, "TVFS", 4) == 0)
BIT_SET(ftp->ftp_features, FTP_FEAT_TVFS);
else if (strncasecmp(linep, "MLST", 4) == 0)
BIT_SET(ftp->ftp_features, FTP_FEAT_MLST);
}
}
else if (code == 421)
{
errno = ECONNRESET;
return -1;
}
return 0;
}
/* option negotiation */
int
_ftp_opts(FTP *ftp, char *command, char *options)
{
char buf[FTPBUFSIZE];
int code;
if (!BIT_ISSET(ftp->ftp_features, FTP_FEAT_FEAT))
{
errno = EINVAL;
return -1;
}
if (_ftp_send_command(ftp, "OPTS %s%s%s", command,
(options ? " " : ""),
(options ? options : "")) == -1
|| _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
return -1;
if (code != 200)
{
if (code == 421)
errno = ECONNRESET;
else if (code == 451)
errno = EAGAIN;
else if (code == 500 || code == 502)
/*
** if we get 500 or 502, that means
** that the server doesn't support the OPTS
** command. this should never happen, because
** RFC-2389 requires that if you support FEAT,
** you also support OPTS. however, Pure-FTPd
** is known to violate this requirement.
*/
errno = ENOSYS;
else
errno = EINVAL;
return -1;
}
return 0;
}
/*
** _ftp_init_session() - initialize session parameters
**
** This initialization must be done after authentication, because the
** server responses may be different (or may fail) for non-authenticated
** connections. Thus, this function is called from the end of ftp_login().
**
** Note: In the future, new functions to support RFC-2228-style
** authentication mechansims may be added as alternatives to
** ftp_login(). Any such functions will have to call
** _ftp_init_session() after a successful authentication, just
** as ftp_login() does.
*/
static int
_ftp_init_session(FTP *ftp)
{
/* check for features */
if (_ftp_feat(ftp) == -1)
return -1;
/*
** get remote system type.
** Note: old SunOS 4.x FTP servers don't grok SYST,
** so don't bitch if this fails.
*/
ftp_systype(ftp);
/* get current working directory */
if (ftp_getcwd(ftp) == NULL)
return -1;
/* set TYPE to binary by default */
if (ftp_type(ftp, FTP_TYPE_BINARY) == -1)
return -1;
return 0;
}
/*
** ftp_login() - login to the FTP server
**
** Note: This function is seperated from ftp_connect() for two main reasons:
**
** 1. Applications that interactively prompt the user for a password
** may need to retry if the user mistyped their password on the first
** attempt, and it is useful to be able to do that without trying to
** establish a second connection.
**
** 2. In the future, new functions to support RFC-2228-style
** authentication mechansims may be added as alternatives to
** ftp_login(). Keeping this seperate from ftp_connect() will
** allow those new functions to be added without changing the
** existing API.
*/
int
ftp_login(FTP *ftp, char *login, char *pass)
{
int code;
char buf[FTPBUFSIZE];
if (ftp->ftp_login[0] != '\0')
{
if (_ftp_send_command(ftp, "REIN") == -1)
return -1;
do
{
if (_ftp_get_response(ftp, &code, buf,
sizeof(buf)) == -1)
return -1;
}
while (code == 120);
if (code != 220)
{
if (code == 421)
errno = ECONNRESET;
else
errno = EINVAL;
return -1;
}
ftp->ftp_login[0] = '\0';
}
if (_ftp_send_command(ftp, "USER %s", login) == -1
|| _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
return -1;
#ifdef DEBUG
printf(" ftp_login(): got response from USER command (code == %d)\n",
code);
#endif
if (code != 230)
{
if (code != 331)
{
if (code == 421)
errno = ECONNRESET;
else
errno = EINVAL;
return -1;
}
if (_ftp_send_command(ftp, "PASS %s", pass) == -1
|| _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
return -1;
if (code != 230)
{
if (code == 530)
errno = EACCES;
else if (code == 421)
errno = ECONNRESET;
else
errno = EINVAL;
return -1;
}
}
strlcpy(ftp->ftp_login, login, sizeof(ftp->ftp_login));
return _ftp_init_session(ftp);
}
/* close ftp connection */
int
ftp_quit(FTP *ftp, unsigned short flags)
{
char buf[FTPBUFSIZE];
int code, retval = 0;
if (ftp->ftp_data_listen != NULL)
fget_netio_free(ftp->ftp_data_listen);
if (ftp->ftp_data != NULL)
{
if (BIT_ISSET(flags, FTP_QUIT_FAST))
fget_netio_free(ftp->ftp_data);
else
_ftp_data_close(ftp);
}
if (! BIT_ISSET(flags, FTP_QUIT_FAST))
{
if (_ftp_send_command(ftp, "QUIT") == -1)
return -1;
_ftp_get_response(ftp, &code, buf, sizeof(buf));
}
retval = fget_netio_free(ftp->ftp_control);
_ftp_dircache_free(ftp);
free(ftp);
return retval;
}
/* return file descriptor for FTP control connection */
int
ftp_fd(FTP *ftp)
{
return fget_netio_fd(ftp->ftp_control);
}
/* return file descriptor for FTP data connection */
int
ftp_data_fd(FTP *ftp)
{
return (ftp->ftp_data == NULL
? -1
: fget_netio_fd(ftp->ftp_data));
}
/* return hostname of FTP server */
char *
ftp_get_host(FTP *ftp)
{
return fget_netio_get_host(ftp->ftp_control);
}
/* return login used to authenticate to FTP server */
char *
ftp_whoami(FTP *ftp)
{
return ftp->ftp_login;
}
syntax highlighted by Code2HTML, v. 0.9.1