123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /*
- * Part of Very Secure FTPd
- * Licence: GPL v2
- * Author: Chris Evans
- * ls.c
- *
- * Would you believe, code to handle directory listing.
- */
- #include "ls.h"
- #include "access.h"
- #include "defs.h"
- #include "str.h"
- #include "strlist.h"
- #include "sysstr.h"
- #include "sysutil.h"
- #include "tunables.h"
- static void build_dir_line(struct mystr* p_str,
- const struct mystr* p_filename_str,
- const struct vsf_sysutil_statbuf* p_stat,
- long curr_time);
- void
- vsf_ls_populate_dir_list(struct mystr_list* p_list,
- struct mystr_list* p_subdir_list,
- struct vsf_sysutil_dir* p_dir,
- const struct mystr* p_base_dir_str,
- const struct mystr* p_option_str,
- const struct mystr* p_filter_str,
- int is_verbose)
- {
- struct mystr dirline_str = INIT_MYSTR;
- struct mystr normalised_base_dir_str = INIT_MYSTR;
- struct str_locate_result loc_result;
- int a_option;
- int r_option;
- int t_option;
- int F_option;
- int do_stat = 0;
- long curr_time = 0;
- loc_result = str_locate_char(p_option_str, 'a');
- a_option = loc_result.found;
- loc_result = str_locate_char(p_option_str, 'r');
- r_option = loc_result.found;
- loc_result = str_locate_char(p_option_str, 't');
- t_option = loc_result.found;
- loc_result = str_locate_char(p_option_str, 'F');
- F_option = loc_result.found;
- loc_result = str_locate_char(p_option_str, 'l');
- if (loc_result.found)
- {
- is_verbose = 1;
- }
- /* Invert "reverse" arg for "-t", the time sorting */
- if (t_option)
- {
- r_option = !r_option;
- }
- if (is_verbose || t_option || F_option || p_subdir_list != 0)
- {
- do_stat = 1;
- }
- /* If the filter starts with a . then implicitly enable -a */
- if (!str_isempty(p_filter_str) && str_get_char_at(p_filter_str, 0) == '.')
- {
- a_option = 1;
- }
- /* "Normalise" the incoming base directory string by making sure it
- * ends in a '/' if it is nonempty
- */
- if (!str_equal_text(p_base_dir_str, "."))
- {
- str_copy(&normalised_base_dir_str, p_base_dir_str);
- }
- if (!str_isempty(&normalised_base_dir_str))
- {
- unsigned int len = str_getlen(&normalised_base_dir_str);
- if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/')
- {
- str_append_char(&normalised_base_dir_str, '/');
- }
- }
- /* If we're going to need to do time comparisions, cache the local time */
- if (is_verbose)
- {
- curr_time = vsf_sysutil_get_time_sec();
- }
- while (1)
- {
- static struct mystr s_next_filename_str;
- static struct mystr s_next_path_and_filename_str;
- static struct vsf_sysutil_statbuf* s_p_statbuf;
- str_next_dirent(&s_next_filename_str, p_dir);
- if (str_isempty(&s_next_filename_str))
- {
- break;
- }
- {
- unsigned int len = str_getlen(&s_next_filename_str);
- if (len > 0 && str_get_char_at(&s_next_filename_str, 0) == '.')
- {
- if (!a_option && !tunable_force_dot_files)
- {
- continue;
- }
- if (!a_option &&
- ((len == 2 && str_get_char_at(&s_next_filename_str, 1) == '.') ||
- len == 1))
- {
- continue;
- }
- }
- }
- /* Don't show hidden directory entries */
- if (!vsf_access_check_file_visible(&s_next_filename_str))
- {
- continue;
- }
- /* If we have an ls option which is a filter, apply it */
- if (!str_isempty(p_filter_str))
- {
- unsigned int iters = 0;
- if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str,
- &iters))
- {
- continue;
- }
- }
- /* Calculate the full path (relative to CWD) for lstat() and
- * output purposes
- */
- str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str);
- str_append_str(&s_next_path_and_filename_str, &s_next_filename_str);
- if (do_stat)
- {
- /* lstat() the file. Of course there's a race condition - the
- * directory entry may have gone away whilst we read it, so
- * ignore failure to stat
- */
- int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf);
- if (vsf_sysutil_retval_is_error(retval))
- {
- continue;
- }
- }
- if (is_verbose)
- {
- static struct mystr s_final_file_str;
- /* If it's a damn symlink, we need to append the target */
- str_copy(&s_final_file_str, &s_next_filename_str);
- if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
- {
- static struct mystr s_temp_str;
- int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str);
- if (retval == 0 && !str_isempty(&s_temp_str))
- {
- str_append_text(&s_final_file_str, " -> ");
- str_append_str(&s_final_file_str, &s_temp_str);
- }
- }
- if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
- {
- str_append_char(&s_final_file_str, '/');
- }
- build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf, curr_time);
- }
- else
- {
- /* Just emit the filenames - note, we prepend the directory for NLST
- * but not for LIST
- */
- str_copy(&dirline_str, &s_next_path_and_filename_str);
- if (F_option)
- {
- if (vsf_sysutil_statbuf_is_dir(s_p_statbuf))
- {
- str_append_char(&dirline_str, '/');
- }
- else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
- {
- str_append_char(&dirline_str, '@');
- }
- }
- str_append_text(&dirline_str, "\r\n");
- }
- /* Add filename into our sorted list - sorting by filename or time. Also,
- * if we are required to, maintain a distinct list of direct
- * subdirectories.
- */
- {
- static struct mystr s_temp_str;
- const struct mystr* p_sort_str = 0;
- const struct mystr* p_sort_subdir_str = 0;
- if (!t_option)
- {
- p_sort_str = &s_next_filename_str;
- }
- else
- {
- str_alloc_text(&s_temp_str,
- vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf));
- p_sort_str = &s_temp_str;
- p_sort_subdir_str = &s_temp_str;
- }
- str_list_add(p_list, &dirline_str, p_sort_str);
- if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
- {
- str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str);
- }
- }
- } /* END: while(1) */
- str_list_sort(p_list, r_option);
- if (p_subdir_list != 0)
- {
- str_list_sort(p_subdir_list, r_option);
- }
- str_free(&dirline_str);
- str_free(&normalised_base_dir_str);
- }
- int
- vsf_filename_passes_filter(const struct mystr* p_filename_str,
- const struct mystr* p_filter_str,
- unsigned int* iters)
- {
- /* A simple routine to match a filename against a pattern.
- * This routine is used instead of e.g. fnmatch(3), because we should be
- * reluctant to trust the latter. fnmatch(3) involves _lots_ of string
- * parsing and handling. There is broad potential for any given fnmatch(3)
- * implementation to be buggy.
- *
- * Currently supported pattern(s):
- * - any number of wildcards, "*" or "?"
- * - {,} syntax (not nested)
- *
- * Note that pattern matching is only supported within the last path
- * component. For example, searching for /a/b/? will work, but searching
- * for /a/?/c will not.
- */
- struct mystr filter_remain_str = INIT_MYSTR;
- struct mystr name_remain_str = INIT_MYSTR;
- struct mystr temp_str = INIT_MYSTR;
- struct mystr brace_list_str = INIT_MYSTR;
- struct mystr new_filter_str = INIT_MYSTR;
- int ret = 0;
- char last_token = 0;
- int must_match_at_current_pos = 1;
- str_copy(&filter_remain_str, p_filter_str);
- str_copy(&name_remain_str, p_filename_str);
- while (!str_isempty(&filter_remain_str) && *iters < VSFTP_MATCHITERS_MAX)
- {
- static struct mystr s_match_needed_str;
- /* Locate next special token */
- struct str_locate_result locate_result =
- str_locate_chars(&filter_remain_str, "*?{");
- (*iters)++;
- /* Isolate text leading up to token (if any) - needs to be matched */
- if (locate_result.found)
- {
- unsigned int indexx = locate_result.index;
- str_left(&filter_remain_str, &s_match_needed_str, indexx);
- str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1);
- str_copy(&filter_remain_str, &temp_str);
- last_token = locate_result.char_found;
- }
- else
- {
- /* No more tokens. Must match remaining filter string exactly. */
- str_copy(&s_match_needed_str, &filter_remain_str);
- str_empty(&filter_remain_str);
- last_token = 0;
- }
- if (!str_isempty(&s_match_needed_str))
- {
- /* Need to match something.. could be a match which has to start at
- * current position, or we could allow it to start anywhere
- */
- unsigned int indexx;
- locate_result = str_locate_str(&name_remain_str, &s_match_needed_str);
- if (!locate_result.found)
- {
- /* Fail */
- goto out;
- }
- indexx = locate_result.index;
- if (must_match_at_current_pos && indexx > 0)
- {
- goto out;
- }
- /* Chop matched string out of remainder */
- str_mid_to_end(&name_remain_str, &temp_str,
- indexx + str_getlen(&s_match_needed_str));
- str_copy(&name_remain_str, &temp_str);
- }
- if (last_token == '?')
- {
- if (str_isempty(&name_remain_str))
- {
- goto out;
- }
- str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1);
- str_copy(&name_remain_str, &temp_str);
- must_match_at_current_pos = 1;
- }
- else if (last_token == '{')
- {
- struct str_locate_result end_brace =
- str_locate_char(&filter_remain_str, '}');
- must_match_at_current_pos = 1;
- if (end_brace.found)
- {
- str_split_char(&filter_remain_str, &temp_str, '}');
- str_copy(&brace_list_str, &filter_remain_str);
- str_copy(&filter_remain_str, &temp_str);
- str_split_char(&brace_list_str, &temp_str, ',');
- while (!str_isempty(&brace_list_str))
- {
- str_copy(&new_filter_str, &brace_list_str);
- str_append_str(&new_filter_str, &filter_remain_str);
- if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str,
- iters))
- {
- ret = 1;
- goto out;
- }
- str_copy(&brace_list_str, &temp_str);
- str_split_char(&brace_list_str, &temp_str, ',');
- }
- goto out;
- }
- else if (str_isempty(&name_remain_str) ||
- str_get_char_at(&name_remain_str, 0) != '{')
- {
- goto out;
- }
- else
- {
- str_right(&name_remain_str, &temp_str,
- str_getlen(&name_remain_str) - 1);
- str_copy(&name_remain_str, &temp_str);
- }
- }
- else
- {
- must_match_at_current_pos = 0;
- }
- }
- /* Any incoming string left means no match unless we ended on the correct
- * type of wildcard.
- */
- if (str_getlen(&name_remain_str) > 0 && last_token != '*')
- {
- goto out;
- }
- /* OK, a match */
- ret = 1;
- if (*iters == VSFTP_MATCHITERS_MAX) {
- ret = 0;
- }
- out:
- str_free(&filter_remain_str);
- str_free(&name_remain_str);
- str_free(&temp_str);
- str_free(&brace_list_str);
- str_free(&new_filter_str);
- return ret;
- }
- static void
- build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str,
- const struct vsf_sysutil_statbuf* p_stat, long curr_time)
- {
- static struct mystr s_tmp_str;
- filesize_t size = vsf_sysutil_statbuf_get_size(p_stat);
- /* Permissions */
- str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat));
- str_append_char(p_str, ' ');
- /* Hard link count */
- str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat));
- str_lpad(&s_tmp_str, 4);
- str_append_str(p_str, &s_tmp_str);
- str_append_char(p_str, ' ');
- /* User */
- if (tunable_hide_ids)
- {
- str_alloc_text(&s_tmp_str, "ftp");
- }
- else
- {
- int uid = vsf_sysutil_statbuf_get_uid(p_stat);
- struct vsf_sysutil_user* p_user = 0;
- if (tunable_text_userdb_names)
- {
- p_user = vsf_sysutil_getpwuid(uid);
- }
- if (p_user == 0)
- {
- str_alloc_ulong(&s_tmp_str, (unsigned long) uid);
- }
- else
- {
- str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user));
- }
- }
- str_rpad(&s_tmp_str, 8);
- str_append_str(p_str, &s_tmp_str);
- str_append_char(p_str, ' ');
- /* Group */
- if (tunable_hide_ids)
- {
- str_alloc_text(&s_tmp_str, "ftp");
- }
- else
- {
- int gid = vsf_sysutil_statbuf_get_gid(p_stat);
- struct vsf_sysutil_group* p_group = 0;
- if (tunable_text_userdb_names)
- {
- p_group = vsf_sysutil_getgrgid(gid);
- }
- if (p_group == 0)
- {
- str_alloc_ulong(&s_tmp_str, (unsigned long) gid);
- }
- else
- {
- str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group));
- }
- }
- str_rpad(&s_tmp_str, 8);
- str_append_str(p_str, &s_tmp_str);
- str_append_char(p_str, ' ');
- /* Size in bytes */
- str_alloc_filesize_t(&s_tmp_str, size);
- str_lpad(&s_tmp_str, 8);
- str_append_str(p_str, &s_tmp_str);
- str_append_char(p_str, ' ');
- /* Date stamp */
- str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat,
- tunable_use_localtime,
- curr_time));
- str_append_char(p_str, ' ');
- /* Filename */
- str_append_str(p_str, p_filename_str);
- str_append_text(p_str, "\r\n");
- }
|