/* ** 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 */ #include #include #include #include #include #include #ifdef STDC_HEADERS # include # include #endif #ifdef MAJOR_IN_MKDEV # include #else # ifdef MAJOR_IN_SYSMACROS # include # 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; }