/* * part of owftpd By Paul H Alfille * The whole is GPLv2 licenced though the ftp code was more liberally licenced when first used. */ #include "owftpd.h" #include #include #include /* AIX requires this to be the first thing in the file. */ #ifndef __GNUC__ # if HAVE_ALLOCA_H # include # else # ifdef _AIX #pragma alloca # else # ifndef alloca /* predefined by HP cc +Olibcalls */ char *alloca(); # endif # endif # endif #endif static void fdprintf(FILE_DESCRIPTOR_OR_ERROR file_descriptor, const char *fmt, ...); static void List_show(struct file_parse_s *fps, const struct parsedname *pn); static void WildLexParse(struct file_parse_s *fps, ASCII * match); static char *skip_ls_options(char *filespec); /* if no localtime_r() is available, provide one */ #ifndef HAVE_LOCALTIME_R #include struct tm *localtime_r(const time_t * timep, struct tm *timeptr) { static pthread_mutex_t time_lock = PTHREAD_MUTEX_INITIALIZER; _MUTEX_LOCK(time_lock); *timeptr = *(localtime(timep)); _MUTEX_UNLOCK(time_lock); return timeptr; } #endif /* HAVE_LOCALTIME_R */ static void List_show(struct file_parse_s *fps, const struct parsedname *pn) { struct stat stbuf; time_t now; struct tm tm_now; double age; char date_buf[13]; ASCII *perms[] = { "---------", "--x--x--x", "-w--w--w-", "-wx-wx-wx", "r--r--r--", "r-xr-xr-x", "rw-rw-rw-", "rwxrwxrwx", }; LEVEL_DEBUG("List_show %s", pn->path); switch (fps->fle) { case file_list_list: FS_fstat_postparse(&stbuf, pn); fdprintf(fps->out, "%s%s", stbuf.st_mode & S_IFDIR ? "d" : "-", perms[stbuf.st_mode & 0x07]); /* fps->output link & ownership information */ fdprintf(fps->out, " %3d %-8d %-8d %8lu ", stbuf.st_nlink, stbuf.st_uid, stbuf.st_gid, (unsigned long) stbuf.st_size); /* fps->output date */ time(&now); localtime_r(&stbuf.st_mtime, &tm_now); age = difftime(now, stbuf.st_mtime); if ((age > 60 * 60 * 24 * 30 * 6) || (age < -(60 * 60 * 24 * 30 * 6))) { strftime(date_buf, sizeof(date_buf), "%b %e %Y", &tm_now); } else { strftime(date_buf, sizeof(date_buf), "%b %e %H:%M", &tm_now); } fdprintf(fps->out, "%s ", date_buf); /* Fall Through */ case file_list_nlst: /* fps->output filename */ fdprintf(fps->out, "%s\r\n", &pn->path[fps->start]); } } void FileLexParse(struct file_parse_s *fps) { struct parsedname pn; while (1) { switch (fps->pse) { case parse_status_init: LEVEL_DEBUG("FTP parse_status_init Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ fps->ret = 0; fps->start = strlen(fps->buffer); if (fps->start > 1) { ++fps->start; } fps->rest = skip_ls_options(fps->rest); if (fps->rest == NULL || fps->rest[0] == '\0') { fps->pse = parse_status_tame; } else { if (fps->rest[0] == '/') { // root specification fps->buffer[1] = '\0'; fps->start = 0; ++fps->rest; } fps->pse = parse_status_init2; } break; case parse_status_init2: LEVEL_DEBUG("FTP parse_status_init2 Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ if ((fps->rest[0] == '.' && fps->rest[1] == '.') || strpbrk(fps->rest, "*[?")) { fps->pse = parse_status_back; } else { fps->pse = parse_status_tame; } break; case parse_status_back: LEVEL_DEBUG("FTP parse_status_back Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ if (fps->rest[0] == '.' && fps->rest[1] == '.') { // Move back ASCII *back = strrchr(fps->buffer, '/'); back[1] = '\0'; fps->start = strlen(fps->buffer); // look for next file part if (fps->rest[2] == '\0') { fps->pse = parse_status_last; fps->rest = NULL; } else if (fps->rest[2] == '/') { fps->pse = parse_status_next; fps->rest = &fps->rest[3]; } else { fps->ret = -ENOENT; return; } } else { fps->pse = parse_status_next; // off the double dot trail } break; case parse_status_next: LEVEL_DEBUG("FTP parse_status_next Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ if (fps->rest == NULL || fps->rest[0] == '\0') { fps->pse = parse_status_last; } else { ASCII *oldrest = strsep(&fps->rest, "/"); if (strpbrk(oldrest, "*[?")) { WildLexParse(fps, oldrest); return; } else { if (strlen(fps->buffer) + strlen(oldrest) + 4 > PATH_MAX) { fps->ret = -ENAMETOOLONG; return; } if (fps->buffer[1]) { strcat(fps->buffer, "/"); } strcat(fps->buffer, oldrest); fps->pse = parse_status_next; } } break; case parse_status_tame: LEVEL_DEBUG("FTP parse_status_tame Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ if ( fps-> rest != NULL ) { if ( strlen(fps->buffer) + strlen(fps->rest) + 4 > PATH_MAX ) { fps->ret = -ENAMETOOLONG; return; } if ( fps->buffer[1] != 0 ) { strcat(fps->buffer, "/"); } strcat(fps->buffer, fps->rest); } if (FS_ParsedName(fps->buffer, &pn) == 0) { if (IsDir(&pn)) { fps->start = strlen(fps->buffer) + 1; if (fps->start == 2) { fps->start = 1; } else if (fps->buffer[fps->start - 2] == '/') { --fps->start; fps->buffer[fps->start - 1] = '\0'; } fps->rest = NULL; WildLexParse(fps, "*"); } else { List_show(fps, &pn); } FS_ParsedName_destroy(&pn); } else { fps->ret = -ENOENT; } return; case parse_status_last: LEVEL_DEBUG("FTP parse_status_last Path<%s> File <%s>", fps->buffer, fps->rest); /* fps->buffer is absolute */ /* trailing / only at root */ if (fps->rest && (strlen(fps->buffer) + strlen(fps->rest) + 4 > PATH_MAX)) { fps->ret = -ENAMETOOLONG; return; } if (FS_ParsedNamePlus(fps->buffer, fps->rest, &pn) == 0) { List_show(fps, &pn); FS_ParsedName_destroy(&pn); } return; } } } struct wildlexparse { ASCII *end; ASCII *match; struct file_parse_s *fps; }; /* Called for each directory element, and operates recursively */ /* uses C library fnmatch for file wildcard comparisons */ static void WildLexParseCallback(void *v, const struct parsedname *const pn_entry) { struct wildlexparse *wlp = v; struct file_parse_s fps; // duplicate for recursive call /* get real name from parsedname struct */ strcpy(&wlp->end[1], FS_DirName(pn_entry)); LEVEL_DEBUG("WildLexParseCallback: Try %s vs %s", &wlp->end[1], wlp->match); if (fnmatch(wlp->match, &wlp->end[1], FNM_PATHNAME)) { return; } /* Matched! So set up for recursive call on nect elements in path name */ memcpy(&fps, wlp->fps, sizeof(fps)); fps.pse = parse_status_next; FileLexParse(&fps); } /* Called for each directory, calls WildLEexParseCallback on each element to see if matches wildcards (even used for normal listings with * wildcard) */ static void WildLexParse(struct file_parse_s *fps, ASCII * match) { struct parsedname pn; /* Embedded callback function */ LEVEL_DEBUG("FTP Wildcard pattern matching: Path=%s, Pattern=%s, File=%s", SAFESTRING(fps->buffer), SAFESTRING(match), SAFESTRING(fps->rest)); /* Check potential length */ if (strlen(fps->buffer) + OW_FULLNAME_MAX + 2 > PATH_MAX) { fps->ret = -ENAMETOOLONG; return; } /* Can we understand the current path? */ if (FS_ParsedName(fps->buffer, &pn) != 0 ) { fps->ret = -ENOENT; return; } if (pn.selected_filetype) { fps->ret = -ENOTDIR; } else { struct wildlexparse wlp = { NULL, match, fps, }; int root = (fps->buffer[1] == '\0'); wlp.end = &fps->buffer[strlen(fps->buffer)]; if (root) { --wlp.end; } wlp.end[0] = '/'; FS_dir(WildLexParseCallback, &wlp, &pn); if (root) { ++wlp.end; } wlp.end[0] = '\0'; // restore fps->buffer } FS_ParsedName_destroy(&pn); } /* write with care for max length and incomplete outout */ static void fdprintf(FILE_DESCRIPTOR_OR_ERROR file_descriptor, const char *fmt, ...) { char buf[PATH_MAX + 1]; ssize_t buflen; va_list ap; int amt_written; int write_ret; daemon_assert(FILE_DESCRIPTOR_VALID(file_descriptor)); daemon_assert(fmt != NULL); va_start(ap, fmt); buflen = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (buflen <= 0) { return; } if ((size_t) buflen >= sizeof(buf)) { buflen = sizeof(buf) - 1; } amt_written = 0; while (amt_written < buflen) { write_ret = write(file_descriptor, buf + amt_written, buflen - amt_written); if (write_ret <= 0) { return; } amt_written += write_ret; } } /* hack workaround clients like Midnight Commander that send: LIST -al /dirname */ static char *skip_ls_options(char *filespec) { daemon_assert(filespec != NULL); for (;;) { /* end when we've passed all options */ if (*filespec != '-') { break; } filespec++; /* if we find "--", eat it and any following whitespace then return */ if ((filespec[0] == '-') && (filespec[1] == ' ')) { filespec += 2; while (isspace(*filespec)) { filespec++; } break; } /* otherwise, skip this option */ while ((*filespec != '\0') && !isspace(*filespec)) { filespec++; } /* and skip any whitespace */ while (isspace(*filespec)) { filespec++; } } daemon_assert(filespec != NULL); return filespec; }