/*
** Copyright 2000-2004 University of Illinois Board of Trustees
** Copyright 2000-2004 Mark D. Roth
** All rights reserved.
**
** list_parse_unix.c - Unix-style 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
#ifdef MAJOR_IN_MKDEV
# include <sys/mkdev.h>
#else
# ifdef MAJOR_IN_SYSMACROS
# include <sys/sysmacros.h>
# endif
#endif
/* FIXME: use strftime() instead ??? */
static const char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
/* does the opposite of strmode() */
static int
_modestr(char *bp, mode_t *mode)
{
*mode = 0;
if (strlen(bp) < 10)
return -1;
/* file type */
switch (bp[0])
{
case 'd':
*mode |= S_IFDIR;
break;
case 'l':
*mode |= S_IFLNK;
break;
case 's':
*mode |= S_IFSOCK;
break;
case 'p':
*mode |= S_IFIFO;
break;
case 'c':
*mode |= S_IFCHR;
break;
case 'b':
*mode |= S_IFBLK;
break;
case '-':
*mode |= S_IFREG;
break;
default:
return -1;
}
/* user bits */
switch (bp[1])
{
case 'r':
*mode |= S_IRUSR;
case '-':
break;
default:
return -1;
}
switch (bp[2])
{
case 'w':
*mode |= S_IWUSR;
case '-':
break;
default:
return -1;
}
switch (bp[3])
{
case 's':
*mode |= S_ISUID;
/* intentional fall-through */
case 'x':
*mode |= S_IXUSR;
break;
case 'S':
*mode |= S_ISUID;
case '-':
break;
default:
return -1;
}
/* group bits */
switch (bp[4])
{
case 'r':
*mode |= S_IRGRP;
case '-':
break;
default:
return -1;
}
switch (bp[5])
{
case 'w':
*mode |= S_IWGRP;
case '-':
break;
default:
return -1;
}
switch (bp[6])
{
case 's':
*mode |= S_ISGID;
/* intentional fall-through */
case 'x':
*mode |= S_IXGRP;
break;
case 'S':
*mode |= S_ISGID;
case '-':
break;
default:
return -1;
}
/* other bits */
switch (bp[7])
{
case 'r':
*mode |= S_IROTH;
case '-':
break;
default:
return -1;
}
switch (bp[8])
{
case 'w':
*mode |= S_IWOTH;
case '-':
break;
default:
return -1;
}
switch (bp[9])
{
case 't':
*mode |= S_ISVTX;
/* intentional fall-through */
case 'x':
*mode |= S_IXOTH;
break;
case 'T':
*mode |= S_ISVTX;
case '-':
break;
default:
return -1;
}
return 0;
}
/* parse Unix directories */
int
_ftp_list_parse_unix(FTP *ftp, char *buf, file_info_t *fip)
{
char *linep = buf, *fieldp, *cp, *save_fieldp = NULL;
time_t now;
struct tm tt, *tn;
int i;
unsigned long ul;
major_t maj;
minor_t min;
char modestring[11];
size_t sz;
#ifdef HAVE_LOCALTIME_R
struct tm tmbuf;
#endif
#ifdef DEBUG
printf("==> _ftp_list_parse_unix(buf=\"%s\", fip=0x%lx)\n", buf, fip);
#endif
/*
** for "/reuquested/path unreadable" messages, fail with EPERM
*/
if (buf[0] == '/'
&& strcmp(buf + strlen(buf) - 11, " unreadable") == 0)
{
errno = EPERM;
return FLP_ERROR;
}
/*
** for error messages from ls or from the ftp server,
** fail with an appropriate errno value
*/
if (strncmp(buf, "ls: ", 4) == 0
|| strncmp(buf, "in.ftpd: ", 9) == 0
|| strncmp(buf, "ftpd: ", 6) == 0)
{
/*
** this is a fairly ugly hack...
** it could be made a little bit better by using
** sys_errlist[] to check for any value of errno
*/
cp = strrchr(buf, ':');
if (cp != NULL
&& strcmp(cp + 2, "No such file or directory") == 0)
errno = ENOENT;
else
errno = EPERM;
return FLP_ERROR;
}
/*
** if the first field on the line is all numeric, it's
** some funky unparsable thing.
** (the Heimdal FTP server is known to do this sometimes)
*/
if (strspn(buf, "0123456789") == strcspn(buf, " \t"))
{
errno = EINVAL;
return FLP_ERROR;
}
/* skip the "total #" line */
if (strncmp(buf, "total ", 6) == 0)
return FLP_IGNORE;
/* initialize time stuff */
memset(&tt, 0, sizeof(tt));
time(&now);
#ifdef HAVE_LOCALTIME_R
tn = localtime_r(&now, &tmbuf);
#else
tn = localtime(&now);
#endif
if (tn->tm_isdst)
tt.tm_isdst = 1;
/* mode */
strlcpy(modestring, linep, sizeof(modestring));
linep += sizeof(modestring) - 1;
if (_modestr(modestring, &(fip->fi_stat.fs_mode)) == -1)
{
errno = EINVAL;
return FLP_ERROR;
}
/*
** special processing for MACOS servers:
** - no nlink field
** - says "folder" instead of user and group for subdirectories
*/
if (strcmp(ftp_systype(ftp), "MACOS") == 0)
{
fip->fi_stat.fs_nlink = 1;
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
if (strcmp(fieldp, "folder") == 0)
{
strlcpy(fip->fi_stat.fs_username, "-1",
sizeof(fip->fi_stat.fs_username));
strlcpy(fip->fi_stat.fs_groupname, "-1",
sizeof(fip->fi_stat.fs_groupname));
}
else
{
/* username */
strlcpy(fip->fi_stat.fs_username, fieldp,
sizeof(fip->fi_stat.fs_username));
/* groupname (or maybe size/device - see next field) */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
strlcpy(fip->fi_stat.fs_groupname, fieldp,
sizeof(fip->fi_stat.fs_groupname));
}
}
else
{
/* nlink */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
fip->fi_stat.fs_nlink = atoi(fieldp);
/* username */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
strlcpy(fip->fi_stat.fs_username, fieldp,
sizeof(fip->fi_stat.fs_username));
/* groupname (or maybe size/device - see next field) */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
strlcpy(fip->fi_stat.fs_groupname, fieldp,
sizeof(fip->fi_stat.fs_groupname));
/*
** save pointer to this field, just in case it's
** actually the size field
** this avoids truncation in the case where the size has
** more digits than the length of the fs_groupname field
** (see below)
*/
save_fieldp = fieldp;
}
/* size/device (or maybe month) */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
if (S_ISCHR(fip->fi_stat.fs_mode)
|| S_ISBLK(fip->fi_stat.fs_mode))
{
/* previous field is actually major device number */
sz = strlen(fip->fi_stat.fs_groupname) - 1;
if (fip->fi_stat.fs_groupname[sz] == ',')
{
sscanf(fip->fi_stat.fs_groupname, "%lu", &ul);
maj = (major_t)ul;
strlcpy(fip->fi_stat.fs_groupname, "-1",
sizeof(fip->fi_stat.fs_groupname));
}
else
{
sscanf(fieldp, "%lu", &ul);
maj = (major_t)ul;
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
}
sscanf(fieldp, "%lu", &ul);
min = (minor_t)ul;
fip->fi_stat.fs_rdev = makedev(maj, min);
/* read month into fieldp */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
}
else
{
for (i = 0; i < 12; i++)
{
if (strcmp(months[i], fieldp) == 0)
{
/*
** yup, it's a month - that means the previous
** field was the size, and the group wasn't
** listed
*/
sscanf(save_fieldp, "%lu", &ul);
fip->fi_stat.fs_size = (off_t)ul;
strlcpy(fip->fi_stat.fs_groupname, "-1",
sizeof(fip->fi_stat.fs_groupname));
break;
}
}
/* didn't find the month, so this must be the size */
if (i == 12)
{
sscanf(fieldp, "%lu", &ul);
fip->fi_stat.fs_size = (off_t)ul;
/* read month into fieldp */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
}
}
/* month should be in fieldp now */
for (i = 0; i < 12; i++)
{
if (strcmp(months[i], fieldp) == 0)
{
tt.tm_mon = i;
break;
}
}
/* if not a valid month, fail with EINVAL */
if (i == 12)
{
errno = EINVAL;
return FLP_ERROR;
}
/* day of month */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
tt.tm_mday = atoi(fieldp);
/* hour/minute or year */
do
fieldp = strsep(&linep, " \n");
while (fieldp != NULL && *fieldp == '\0');
if ((cp = strchr(fieldp, ':')) != NULL)
{
*cp++ = '\0';
tt.tm_hour = atoi(fieldp);
tt.tm_min = atoi(cp);
/* either this year or last... */
tt.tm_year = tn->tm_year;
if (tn->tm_mon < tt.tm_mon)
tt.tm_year--;
}
else
tt.tm_year = atoi(fieldp) - 1900;
/* save date */
fip->fi_stat.fs_mtime = mktime(&tt);
#ifdef DEBUG2
printf(" _ftp_list_parse_unix(): fip->fi_stat.fs_mtime = %s",
asctime(localtime(&fip->fi_stat.fs_mtime)));
#endif
/* skip any extra spaces */
linep += strspn(linep, " ");
/* check if it's a link */
if ((cp = strstr(linep, " -> ")) != NULL)
{
*cp = '\0';
cp += 4;
strlcpy(fip->fi_linkto, cp, sizeof(fip->fi_linkto));
}
/* the rest is the filename */
strlcpy(fip->fi_filename, linep, sizeof(fip->fi_filename));
#ifdef DEBUG
printf("<== _ftp_list_parse_unix(): returning entry for \"%s\"\n",
fip->fi_filename);
#endif
return FLP_VALID;
}
syntax highlighted by Code2HTML, v. 0.9.1