/*
**  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