/*
** Copyright 2000-2004 University of Illinois Board of Trustees
** Copyright 2000-2004 Mark D. Roth
** All rights reserved.
**
** ftplist.c - FTP directory parsing code
**
** Mark D. Roth <roth@feep.net>
*/
#include <internal.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef STDC_HEADERS
# include <string.h>
# include <stdlib.h>
#endif
/* needed for strcasecmp() on some platforms */
#include <strings.h>
#ifdef DEBUG
static void
_print_file_info(file_info_t *fip)
{
printf("fi_filename\t\t= \"%s\"\n", fip->fi_filename);
printf("fi_linkto\t\t= \"%s\"\n", fip->fi_linkto);
printf("fs_mode\t\t\t= %04o\n", (unsigned int)fip->fi_stat.fs_mode);
printf("fs_nlink\t\t= %hu\n", (unsigned short)fip->fi_stat.fs_nlink);
printf("fs_username\t\t= \"%s\"\n", fip->fi_stat.fs_username);
printf("fs_groupname\t\t= \"%s\"\n", fip->fi_stat.fs_groupname);
printf("fs_size\t\t\t= %lu\n", (unsigned long)fip->fi_stat.fs_size);
printf("fs_mtime\t\t= %s", asctime(localtime(&fip->fi_stat.fs_mtime)));
}
#endif
/*
** _ftp_list_parse_choose() - choose a list-parsing function based on
** the first line returned by the server
** arguments:
** ftp FTP handle
** buf line to be parsed
*/
static void
_ftp_list_parse_choose(FTP *ftp, char *buf)
{
int i;
#ifdef DEBUG
printf("==> _ftp_list_parse_choose(\"%s\")\n", buf);
#endif
/*
** for MLST, we know what to use,
** so we don't even bother looking at buf
*/
if (BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_MLST)
&& BIT_ISSET(ftp->ftp_features, FTP_FEAT_MLST))
{
ftp->ftp_list_parse_function = _ftp_list_parse_mlsd;
#ifdef DEBUG
printf("<== _ftp_list_parse_choose(): using MLST parsing\n");
#endif
return;
}
/* if it starts with a '+', assume EPLF */
if (buf[0] == '+')
{
ftp->ftp_list_parse_function = _ftp_list_parse_eplf;
#ifdef DEBUG
printf("<== _ftp_list_parse_choose(): using EPLF parsing\n");
#endif
return;
}
/* if there's no whitespace, assume it's a filename-only listing */
if (strpbrk(buf, " \t") == NULL)
{
ftp->ftp_list_parse_function = _ftp_list_parse_dummy;
#ifdef DEBUG
printf("<== _ftp_list_parse_choose(): using dummy parsing\n");
#endif
return;
}
/* if it starts with a date in the form MM-DD-YY, assume NT */
if (sscanf(buf, "%2d-%2d-%2d", &i, &i, &i) == 3)
{
ftp->ftp_list_parse_function = _ftp_list_parse_nt;
#ifdef DEBUG
printf("<== _ftp_list_parse_choose(): using WinNT parsing\n");
#endif
return;
}
/*
** if we haven't identified it as any other type,
** assume UNIX-style directories by default
*/
ftp->ftp_list_parse_function = _ftp_list_parse_unix;
#ifdef DEBUG
printf("<== _ftp_list_parse_choose(): using UNIX parsing\n");
#endif
}
/*
** _ftp_list() - request, read, and parse a directory list from the server
** Returns:
** (number of entries) success
** -1 (and sets errno) failure
*/
int
_ftp_list(FTP *ftp, char *path, fget_hash_t *files_h,
enum dcent_types preferred_type, unsigned long flags)
{
char buf[FTPBUFSIZE];
int retval = 0, code = 0, numignored = 0, save_errno;
unsigned short in_desired_cwd = 0;
char *new_cwd, *cp, *cp2;
char orig_path[MAXPATHLEN] = "";
file_info_t *fip;
#ifdef DEBUG
printf("==> _ftp_list(ftp=0x%lx (%s), path=\"%s\", "
"files_h=0x%lx, preferred_type=%d, flags=%lu)\n",
ftp, ftp->ftp_host, path, files_h, preferred_type, flags);
#endif
/*
** for MLSD or MLST, we don't need to mess with CWD
*/
if (BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_MLST)
&& BIT_ISSET(ftp->ftp_features, FTP_FEAT_MLST))
{
/*
** make sure the server knows which facts we want returned
*/
if (_ftp_opts_mlsd(ftp) == -1)
return -1;
/*
** for MLSD, set up the command and skip down
** to where we open the ftp-data connection
*/
if (preferred_type == DCENT_DIR)
{
snprintf(buf, sizeof(buf), "MLSD %s", path);
goto open_data_connection;
}
/*
** MLST is handled differently, because there's no
** ftp-data connection
*/
if (_ftp_send_command(ftp, "MLST %s", path) == -1)
{
#ifdef DEBUG
printf(" _ftp_list(): _ftp_send_command(\"%s\"): "
"%s\n", buf, strerror(errno));
#endif
return -1;
}
if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
{
#ifdef DEBUG
printf(" _ftp_list(): _ftp_get_response(): %s\n",
strerror(errno));
#endif
return -1;
}
if (code != 250)
{
errno = ENOENT;
return -1;
}
/* find line starting with a space */
cp = strstr(buf, "\n ");
if (cp == NULL)
{
errno = EINVAL;
return -1;
}
/* skip to beginning of MLST data */
cp += 2;
/* find next newline */
cp2 = strchr(cp, '\n');
if (cp2 == NULL)
{
errno = EINVAL;
return -1;
}
*cp2 = '\0';
fip = (file_info_t *)calloc(1, sizeof(file_info_t));
if (fip == NULL)
return -1;
/* parse the entry */
switch (_ftp_list_parse_mlsd(ftp, cp, fip))
{
/*
** _ftp_list_parse_mlsd() never returns FLP_ERROR
*/
case FLP_IGNORE:
default:
/* ignore line */
free(fip);
errno = EINVAL;
return -1;
case FLP_VALID:
/* normal file */
fget_hash_add(files_h, fip);
}
/* return number of entries, which is always 1 */
return 1;
}
/*
** not using MLSx, so construct LIST request
*/
/*
** if the path is "/", always treat it as a DCENT_DIR request,
** since there is no parent directory to check for a DCENT_FILE
*/
if (strcmp(path, "/") == 0)
preferred_type = DCENT_DIR;
/*
** for files, change to the parent directory
** for directories, change to the directory itself
*/
if (preferred_type == DCENT_FILE)
new_cwd = dirname(path);
else
new_cwd = path;
/*
** if we're not already in the desired directory,
** save original path and send CWD request
*/
if (strcmp(ftp_getcwd(ftp), new_cwd) == 0)
in_desired_cwd = 1;
else
{
strlcpy(orig_path, ftp_getcwd(ftp), sizeof(orig_path));
if (ftp_chdir(ftp, new_cwd) == -1)
{
#ifdef DEBUG
printf(" _ftp_list(): ftp_chdir(\"%s\"): %s\n",
new_cwd, strerror(errno));
#endif
/*
** if we're looking for a directory but we can't
** CWD to the specified path, then fail
**
** FIXME: is this too much of an assumption?
** is it possible for CWD to fail for a directory,
** but still have LIST succeed?
*/
if (preferred_type == DCENT_DIR)
return -1;
/*
** also fail immediately if we got ECONNRESET
*/
if (errno == ECONNRESET)
return -1;
/*
** it could be EACCES or ENOENT
** (and if we got ENOENT, it might really be an
** ENOTDIR situation)
** either way, we'll try passing the path name to
** the LIST request
*/
/*
** clear buffer so that we know not to CWD back below
*/
orig_path[0] = '\0';
}
else
in_desired_cwd = 1;
}
/*
** construct list command
*/
strlcpy(buf, "LIST", sizeof(buf));
/*
** add appropriate ls(1) options,
** unless server doesn't grok them
*/
if (! BIT_ISSET(flags, FL_TRY_NO_LS_OPTS)
&& ! BIT_ISSET(ftp->ftp_features, FTP_FEAT_NO_LIST_LS_OPTS))
strlcat(buf,
(preferred_type == DCENT_FILE
? " -ld"
: " -la"),
sizeof(buf));
/*
** add the pathname to be listed
*/
if (preferred_type == DCENT_FILE)
{
strlcat(buf, " ", sizeof(buf));
/*
** if we're already in the parent directory, just use
** the basename; otherwise, use the full path
*/
if (in_desired_cwd)
strlcat(buf, basename(path), sizeof(buf));
else
strlcat(buf, path, sizeof(buf));
}
else /* preferred_type == DCENT_DIR */
{
/*
** if we're not already in the directory to be listed,
** use the full path; otherwise, don't use any argument
*/
if (! in_desired_cwd)
{
strlcat(buf, " ", sizeof(buf));
strlcat(buf, path, sizeof(buf));
}
}
open_data_connection:
/* for MLSD or LIST requests, use a data connection */
if (_ftp_data_connect(ftp, "%s", buf) == -1)
{
retval = -1;
goto done;
}
/*
** if there are send and receive hooks set for the control
** connection, use them for the file listing as well
*/
fget_netio_set_options(ftp->ftp_data,
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);
/* read each line */
while (fget_netio_read_line(ftp->ftp_data,
ftp->ftp_io_timeout,
buf, sizeof(buf)) > 0)
{
/* choose a list parsing function, if needed */
if (ftp->ftp_list_parse_function == NULL)
_ftp_list_parse_choose(ftp, buf);
/* allocate structure for new entry */
fip = (file_info_t *)calloc(1, sizeof(file_info_t));
if (fip == NULL)
return -1;
/*
** call the parsing function to generate
** an ftpstat structure
*/
switch ((*(ftp->ftp_list_parse_function))(ftp, buf, fip))
{
case FLP_ERROR:
/* error (sets errno) */
save_errno = errno;
free(fip);
_ftp_data_close(ftp);
errno = save_errno;
retval = -1;
goto done;
case FLP_IGNORE:
default:
/* ignore line */
free(fip);
numignored++;
break;
case FLP_VALID:
/* normal file */
fget_hash_add(files_h, fip);
}
}
if (_ftp_data_close(ftp) == -1)
{
/* if something other than 226 was returned, return ENOENT */
if (errno == EINVAL)
errno = ENOENT;
retval = -1;
goto done;
}
/* no lines returned, which means ENOENT */
if ((fget_hash_nents(files_h) + numignored) == 0)
{
errno = ENOENT;
retval = -1;
goto done;
}
/* no errors, so return number of entries found */
retval = fget_hash_nents(files_h);
done:
/*
** for MLSD, return immediately
** (no need to try again with ls(1) options
** or CWD back to where we belong)
*/
if (BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_MLST)
&& BIT_ISSET(ftp->ftp_features, FTP_FEAT_MLST))
return retval;
/*
** if we failed with ls(1) options, try again without them
*/
if (! BIT_ISSET(flags, FL_TRY_NO_LS_OPTS)
&& ! BIT_ISSET(ftp->ftp_features, FTP_FEAT_NO_LIST_LS_OPTS)
&& retval == -1
&& errno != ECONNRESET)
{
retval = _ftp_list(ftp, path, files_h,
preferred_type, flags|FL_TRY_NO_LS_OPTS);
/* if it succeeded, don't use ls(1) options from now on */
if (retval == 0)
BIT_SET(ftp->ftp_features, FTP_FEAT_NO_LIST_LS_OPTS);
}
/*
** change back to original working directory
*/
if (orig_path[0] != '\0'
&& (retval != -1 || errno != ECONNRESET)
&& ftp_chdir(ftp, orig_path) == -1)
return -1;
#ifdef DEBUG
printf("<== _ftp_list(): returning %d\n", retval);
#endif
return retval;
}
syntax highlighted by Code2HTML, v. 0.9.1