449 lines
13 KiB
C
449 lines
13 KiB
C
/*
|
|
* 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)
|
|
{
|
|
int len;
|
|
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;
|
|
}
|
|
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");
|
|
}
|
|
|