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