/* * Copyright (c) 2009-2010 * by AVM GmbH Berlin, Germany * * Licence: Free, use with no restriction. */ /* Speed on F!Box 7270 16MB: * 15000 Files on FAT32 USB Stick: 840 sec. (not cached) * 14 sec. (cached) */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include "avm_acl.h" #include "buildin_ls.h" #include "charencoding.h" #include "extern.h" #define OPT_LONG 0x01 #define OPT_ALL 0x02 #define OPT_ALL_NO_PARENT 0x04 #define OPT_RECURS 0x08 static off_t byte_count; static short type_ascii = 1; // for safari, conforming to RFC959, LIST and NLST command static int this_year; static unsigned options = 0; static char *month_name[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static int format_time(char *p, int len, time_t t) { struct tm tm = { 0, }; localtime_r(&t, &tm); if (tm.tm_year == this_year) { // without year, but with time return snprintf(p, len, "%s %2u %02u:%02u", month_name[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min); } else { // with year, but without time return snprintf(p, len, "%s %2u %u", month_name[tm.tm_mon], tm.tm_mday, tm.tm_year+1900); } } #define NENTRIES 500 static struct entry { mode_t mode; unsigned nlink; off_t size; time_t mtime; char *name; } *entries = 0; static int nentries = 0; static void out_entry(struct entry *e, FILE *out_fp) { if (options & OPT_LONG) { char line[256]; strcpy(line, "---------- "); if (S_ISDIR(e->mode)) line[0] = 'd'; if (e->mode & S_IRUSR) line[1] = 'r'; if (e->mode & S_IWUSR) line[2] = 'w'; if (e->mode & S_IXUSR) line[3] = 'x'; if (e->mode & S_IRGRP) line[4] = 'r'; if (e->mode & S_IWGRP) line[5] = 'w'; if (e->mode & S_IXGRP) line[6] = 'x'; if (e->mode & S_IROTH) line[7] = 'r'; if (e->mode & S_IWOTH) line[8] = 'w'; if (e->mode & S_IXOTH) line[9] = 'x'; int n = strlen(line); char *p = line + n; int len = sizeof(line) - n - 1; n = snprintf(p, len, "%u ", e->nlink); if (n > len) goto full; p += n; len -= n; if (len < 1) goto full; // n = snprintf(p, len, "%u %u ", sb->st_uid, sb->st_gid); n = snprintf(p, len, "ftp ftp "); if (n > len) goto full; p += n; len -= n; if (len < 1) goto full; if (sizeof(e->size) > sizeof(long)) { n = snprintf(p, len, "%qu ", e->size); } else { n = snprintf(p, len, "%u ", e->size); } if (n > len) goto full; p += n; len -= n; if (len < 1) goto full; n = format_time(p, len, e->mtime); if (n > len) goto full; p += n; len -= n; if (len < 1) goto full; snprintf(p, len, " "); full: fputs(line, out_fp); byte_count += strlen(line); } #ifndef NO_RFC2640_SUPPORT { char *s; #ifdef FULL_UTF8 if (g_utf8) { fputs(e->name, out_fp); byte_count += strlen(e->name); } else { s = strdup(e->name); if (s) { ConvertStringFromUTF8ToISO8859_1_With_Fallback(s, '.'); fputs(s, out_fp); byte_count += strlen(s); free(s); } } #else if (g_utf8) { s = ConvertStringFromISO8859_1ToUTF8_WithAlloc(e->name); } else s = 0; if (s) { fputs(s, out_fp); byte_count += strlen(s); free(s); } else { // iso8859-1 fputs(e->name, out_fp); byte_count += strlen(e->name); } #endif } #else fputs(e->name, out_fp); byte_count += strlen(e->name); #endif if (type_ascii) { (void) putc ('\r', out_fp); byte_count++; } (void) putc ('\n', out_fp); byte_count++; } static int entry_remember(struct entry *e) { if (nentries == NENTRIES) return -1; char *name = strdup(e->name); if (!name) return -1; e->name = name; memcpy(&entries[nentries], e, sizeof(struct entry)); nentries++; return 0; } static void entries_swap(int index1, int index2) { if (index1 == index2) return; struct entry e; memcpy(&e, &entries[index1], sizeof(struct entry)); memcpy(&entries[index1], &entries[index2], sizeof(struct entry)); memcpy(&entries[index2], &e, sizeof(struct entry)); } static int entries_compare(struct entry *e1, struct entry *e2) { if (options & OPT_LONG) { if (S_ISDIR(e1->mode) && !S_ISDIR(e2->mode)) return -1; if (!S_ISDIR(e1->mode) && S_ISDIR(e2->mode)) return 1; } return strcmp(e1->name, e2->name); } // alle von first_index bis inklusive last_index werden sortiert static void entries_qsort(int first_index, int last_index) { // mittleren "rausnehmen" // kleinere nach vorne // grossere nach hinten if (last_index - first_index < 1) return; // fertisch int middle_index = first_index; int bottom_index = last_index; int i = first_index+1; while(i <= bottom_index) { // assert(middle_index < i); if (entries_compare(&entries[i], &entries[middle_index]) < 0) { // swap i with middle entries_swap(i, middle_index); middle_index = i; i++; } else { // swap i with bottom_index if (i != bottom_index) { entries_swap(i, bottom_index); } bottom_index--; // i bleibt so, aber bottom_index ist eins weniger } } // assert(bottom_index == middle_index); entries_qsort(first_index, middle_index-1); entries_qsort(middle_index+1, last_index); } static void entry_output_sorted_entries(FILE *out_fp) { entries_qsort(0, nentries-1); struct entry *e = &entries[0]; int i; for (i = 0; i < nentries; i++, e++) { out_entry(e, out_fp); free(e->name); } nentries = 0; } static int realpath_acl_stat(const char *path, struct stat *buf) { struct stat sb; int write_access; int ret = stat(path, &sb); if (ret) return ret; // use original errno if (!acl_is_allowed_path(path, &write_access)) { // dont modify buf errno = EACCES; return -1; } if (0 == ret) { if (!write_access) sb.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); memcpy(buf, &sb, sizeof(struct stat)); } return ret; } static DIR *realpath_acl_opendir(const char *name) { int write_access; DIR *d = opendir(name); if (!d) return 0; // use original errno if (!acl_is_allowed_path(name, &write_access)) { closedir(d); errno = EACCES; return 0; } return d; } // we dont sort the output to not use much RAM static int list_dir(char *path, FILE *out_fp) { DIR *d = realpath_acl_opendir(path); Log(("list_dir path=%s d=%p", path, d)); if (!d) return 0; unsigned subdirs = 0; while(1) { struct dirent *de = readdir(d); if (!de) break; // complete Log(("list_dir d_name=%s", de->d_name)); // filter out files if (!(options & OPT_ALL) && !(options & OPT_ALL_NO_PARENT)) { if (de->d_name[0] == '.') continue; } else if (options & OPT_ALL_NO_PARENT) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; } char *path2 = NULL; if (!strcmp(path, "/")) { asprintf(&path2, "/%s", de->d_name); } else { asprintf(&path2, "%s/%s", path, de->d_name); } if (!path2) continue; struct stat sb; #if 0 // SPEED TEST if (path2[0] == '/') { char *real_path = (char *)malloc(strlen("/var/media/ftp") + strlen(path2) + 1); if (!real_path) continue; strcpy(real_path, "/var/media/ftp"); strcat(real_path, &path2[1]); if (stat(real_path, &sb)) { Log(("test abs path stat(%s) failed", real_path)); free(path2); free(real_path); continue; } free(real_path); } else { // very virt rel path is also a real path if (stat(path2, &sb)) { Log(("test rel path stat(%s) failed", path2)); free(path2); continue; } } #else if (realpath_acl_stat(path2, &sb)) { Log(("list_dir stat path2=%s FAILED", path2)); free(path2); continue; } #endif Log(("list_dir stat path2=%s ok mode=0x%x", path2, sb.st_mode)); free(path2); if (!S_ISREG(sb.st_mode) && !S_ISDIR(sb.st_mode)) continue; if (options & OPT_RECURS) { if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) subdirs++; } struct entry e; e.mode = sb.st_mode; e.nlink = sb.st_nlink; e.size = sb.st_size; e.mtime = sb.st_mtime; e.name = de->d_name; if (entry_remember(&e)) { entry_output_sorted_entries(out_fp); if (entry_remember(&e)) { out_entry(&e, out_fp); } } } entry_output_sorted_entries(out_fp); closedir(d); if ((options & OPT_RECURS) && subdirs) { d = realpath_acl_opendir(path); if (d) { while(1) { struct dirent *de = readdir(d); if (!de) break; // complete if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; // filter out files if (!(options & OPT_ALL)) { if (de->d_name[0] == '.') continue; } char *path2 = NULL; if (!strcmp(path, "/")) { asprintf(&path2, "/%s", de->d_name); } else { asprintf(&path2, "%s/%s", path, de->d_name); } if (!path2) continue; struct stat sb; if (0 == realpath_acl_stat(path2, &sb)) { if (S_ISDIR(sb.st_mode)) { if (type_ascii) { (void) putc ('\r', out_fp); byte_count++; } (void) putc ('\n', out_fp); byte_count++; fputs(path2, out_fp); byte_count += strlen(path2); putc (':', out_fp); byte_count++; if (type_ascii) { (void) putc ('\r', out_fp); byte_count++; } (void) putc ('\n', out_fp); byte_count++; list_dir(path2, out_fp); } } free(path2); } closedir(d); } } return 0; } int buildin_ls(int ac, char *av[]) { int i; char *path = 0; byte_count = 0; { #ifdef FTPD_DEBUG char buf[256]; Log(("%s enter getcwd=%s", __FUNCTION__, getcwd(buf,sizeof(buf)))); #endif } // we always output directly to stdout, even if using SSL, // cause we are running in a seperate forked process FILE *out_fp = stdout; for (i = 1; i < ac; i++) { if (av[i][0] == '-') { char *p = &av[i][1]; while(*p) { switch(*p) { case 'l': options |= OPT_LONG; break; case 'a': options |= OPT_ALL; break; case 'A': options |= OPT_ALL_NO_PARENT; break; case 'R': options |= OPT_RECURS; break; } p++; } } else { path = av[i]; } } if (!path || path[0] == '\0') path = "."; struct stat sb; short done = 0; if (0 == realpath_acl_stat(path, &sb)) { Log(("%s acl_stat path=%s ok mode=0x%x sizeof(sb)=%u", __FUNCTION__, path, sb.st_mode, sizeof(sb))); if (S_ISDIR(sb.st_mode)) { Log(("%s acl_stat S_ISDIR", __FUNCTION__)); entries = (struct entry *)calloc(NENTRIES, sizeof(struct entry)); if (!entries) return -1; struct tm tm2 = { 0, }; time_t now = time(0); localtime_r(&now, &tm2); this_year = tm2.tm_year; if (options & OPT_RECURS) { fputs(path, out_fp); byte_count += strlen(path); putc (':', out_fp); byte_count++; if (type_ascii) { (void) putc ('\r', out_fp); byte_count++; } (void) putc ('\n', out_fp); byte_count++; } list_dir(path, out_fp); done = 1; free(entries); entries = 0; } else if (S_ISREG(sb.st_mode)) { Log(("%s acl_stat S_ISREG", __FUNCTION__)); done = 1; struct entry e; e.mode = sb.st_mode; e.nlink = sb.st_nlink; e.size = sb.st_size; e.mtime = sb.st_mtime; e.name = path; out_entry(&e, out_fp); } } else { Log(("%s acl_stat of %s failed", __FUNCTION__, path)); } if (!done) { // err no such file or directory char *s = "No such file or directory"; fputs(s, out_fp); byte_count += strlen(s); if (type_ascii) { (void) putc ('\r', out_fp); byte_count++; } (void) putc ('\n', out_fp); byte_count++; } // *pbyte_count += byte_count; Log(("%s leave", __FUNCTION__)); return 0; }