ls.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. /*
  2. * Part of Very Secure FTPd
  3. * Licence: GPL v2
  4. * Author: Chris Evans
  5. * ls.c
  6. *
  7. * Would you believe, code to handle directory listing.
  8. */
  9. #include "ls.h"
  10. #include "access.h"
  11. #include "defs.h"
  12. #include "str.h"
  13. #include "strlist.h"
  14. #include "sysstr.h"
  15. #include "sysutil.h"
  16. #include "tunables.h"
  17. static void build_dir_line(struct mystr* p_str,
  18. const struct mystr* p_filename_str,
  19. const struct vsf_sysutil_statbuf* p_stat,
  20. long curr_time);
  21. void
  22. vsf_ls_populate_dir_list(struct mystr_list* p_list,
  23. struct mystr_list* p_subdir_list,
  24. struct vsf_sysutil_dir* p_dir,
  25. const struct mystr* p_base_dir_str,
  26. const struct mystr* p_option_str,
  27. const struct mystr* p_filter_str,
  28. int is_verbose)
  29. {
  30. struct mystr dirline_str = INIT_MYSTR;
  31. struct mystr normalised_base_dir_str = INIT_MYSTR;
  32. struct str_locate_result loc_result;
  33. int a_option;
  34. int r_option;
  35. int t_option;
  36. int F_option;
  37. int do_stat = 0;
  38. long curr_time = 0;
  39. loc_result = str_locate_char(p_option_str, 'a');
  40. a_option = loc_result.found;
  41. loc_result = str_locate_char(p_option_str, 'r');
  42. r_option = loc_result.found;
  43. loc_result = str_locate_char(p_option_str, 't');
  44. t_option = loc_result.found;
  45. loc_result = str_locate_char(p_option_str, 'F');
  46. F_option = loc_result.found;
  47. loc_result = str_locate_char(p_option_str, 'l');
  48. if (loc_result.found)
  49. {
  50. is_verbose = 1;
  51. }
  52. /* Invert "reverse" arg for "-t", the time sorting */
  53. if (t_option)
  54. {
  55. r_option = !r_option;
  56. }
  57. if (is_verbose || t_option || F_option || p_subdir_list != 0)
  58. {
  59. do_stat = 1;
  60. }
  61. /* If the filter starts with a . then implicitly enable -a */
  62. if (!str_isempty(p_filter_str) && str_get_char_at(p_filter_str, 0) == '.')
  63. {
  64. a_option = 1;
  65. }
  66. /* "Normalise" the incoming base directory string by making sure it
  67. * ends in a '/' if it is nonempty
  68. */
  69. if (!str_equal_text(p_base_dir_str, "."))
  70. {
  71. str_copy(&normalised_base_dir_str, p_base_dir_str);
  72. }
  73. if (!str_isempty(&normalised_base_dir_str))
  74. {
  75. unsigned int len = str_getlen(&normalised_base_dir_str);
  76. if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/')
  77. {
  78. str_append_char(&normalised_base_dir_str, '/');
  79. }
  80. }
  81. /* If we're going to need to do time comparisions, cache the local time */
  82. if (is_verbose)
  83. {
  84. curr_time = vsf_sysutil_get_time_sec();
  85. }
  86. while (1)
  87. {
  88. static struct mystr s_next_filename_str;
  89. static struct mystr s_next_path_and_filename_str;
  90. static struct vsf_sysutil_statbuf* s_p_statbuf;
  91. str_next_dirent(&s_next_filename_str, p_dir);
  92. if (str_isempty(&s_next_filename_str))
  93. {
  94. break;
  95. }
  96. {
  97. unsigned int len = str_getlen(&s_next_filename_str);
  98. if (len > 0 && str_get_char_at(&s_next_filename_str, 0) == '.')
  99. {
  100. if (!a_option && !tunable_force_dot_files)
  101. {
  102. continue;
  103. }
  104. if (!a_option &&
  105. ((len == 2 && str_get_char_at(&s_next_filename_str, 1) == '.') ||
  106. len == 1))
  107. {
  108. continue;
  109. }
  110. }
  111. }
  112. /* Don't show hidden directory entries */
  113. if (!vsf_access_check_file_visible(&s_next_filename_str))
  114. {
  115. continue;
  116. }
  117. /* If we have an ls option which is a filter, apply it */
  118. if (!str_isempty(p_filter_str))
  119. {
  120. unsigned int iters = 0;
  121. if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str,
  122. &iters))
  123. {
  124. continue;
  125. }
  126. }
  127. /* Calculate the full path (relative to CWD) for lstat() and
  128. * output purposes
  129. */
  130. str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str);
  131. str_append_str(&s_next_path_and_filename_str, &s_next_filename_str);
  132. if (do_stat)
  133. {
  134. /* lstat() the file. Of course there's a race condition - the
  135. * directory entry may have gone away whilst we read it, so
  136. * ignore failure to stat
  137. */
  138. int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf);
  139. if (vsf_sysutil_retval_is_error(retval))
  140. {
  141. continue;
  142. }
  143. }
  144. if (is_verbose)
  145. {
  146. static struct mystr s_final_file_str;
  147. /* If it's a damn symlink, we need to append the target */
  148. str_copy(&s_final_file_str, &s_next_filename_str);
  149. if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
  150. {
  151. static struct mystr s_temp_str;
  152. int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str);
  153. if (retval == 0 && !str_isempty(&s_temp_str))
  154. {
  155. str_append_text(&s_final_file_str, " -> ");
  156. str_append_str(&s_final_file_str, &s_temp_str);
  157. }
  158. }
  159. if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
  160. {
  161. str_append_char(&s_final_file_str, '/');
  162. }
  163. build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf, curr_time);
  164. }
  165. else
  166. {
  167. /* Just emit the filenames - note, we prepend the directory for NLST
  168. * but not for LIST
  169. */
  170. str_copy(&dirline_str, &s_next_path_and_filename_str);
  171. if (F_option)
  172. {
  173. if (vsf_sysutil_statbuf_is_dir(s_p_statbuf))
  174. {
  175. str_append_char(&dirline_str, '/');
  176. }
  177. else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf))
  178. {
  179. str_append_char(&dirline_str, '@');
  180. }
  181. }
  182. str_append_text(&dirline_str, "\r\n");
  183. }
  184. /* Add filename into our sorted list - sorting by filename or time. Also,
  185. * if we are required to, maintain a distinct list of direct
  186. * subdirectories.
  187. */
  188. {
  189. static struct mystr s_temp_str;
  190. const struct mystr* p_sort_str = 0;
  191. const struct mystr* p_sort_subdir_str = 0;
  192. if (!t_option)
  193. {
  194. p_sort_str = &s_next_filename_str;
  195. }
  196. else
  197. {
  198. str_alloc_text(&s_temp_str,
  199. vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf));
  200. p_sort_str = &s_temp_str;
  201. p_sort_subdir_str = &s_temp_str;
  202. }
  203. str_list_add(p_list, &dirline_str, p_sort_str);
  204. if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf))
  205. {
  206. str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str);
  207. }
  208. }
  209. } /* END: while(1) */
  210. str_list_sort(p_list, r_option);
  211. if (p_subdir_list != 0)
  212. {
  213. str_list_sort(p_subdir_list, r_option);
  214. }
  215. str_free(&dirline_str);
  216. str_free(&normalised_base_dir_str);
  217. }
  218. int
  219. vsf_filename_passes_filter(const struct mystr* p_filename_str,
  220. const struct mystr* p_filter_str,
  221. unsigned int* iters)
  222. {
  223. /* A simple routine to match a filename against a pattern.
  224. * This routine is used instead of e.g. fnmatch(3), because we should be
  225. * reluctant to trust the latter. fnmatch(3) involves _lots_ of string
  226. * parsing and handling. There is broad potential for any given fnmatch(3)
  227. * implementation to be buggy.
  228. *
  229. * Currently supported pattern(s):
  230. * - any number of wildcards, "*" or "?"
  231. * - {,} syntax (not nested)
  232. *
  233. * Note that pattern matching is only supported within the last path
  234. * component. For example, searching for /a/b/? will work, but searching
  235. * for /a/?/c will not.
  236. */
  237. struct mystr filter_remain_str = INIT_MYSTR;
  238. struct mystr name_remain_str = INIT_MYSTR;
  239. struct mystr temp_str = INIT_MYSTR;
  240. struct mystr brace_list_str = INIT_MYSTR;
  241. struct mystr new_filter_str = INIT_MYSTR;
  242. int ret = 0;
  243. char last_token = 0;
  244. int must_match_at_current_pos = 1;
  245. str_copy(&filter_remain_str, p_filter_str);
  246. str_copy(&name_remain_str, p_filename_str);
  247. while (!str_isempty(&filter_remain_str) && *iters < VSFTP_MATCHITERS_MAX)
  248. {
  249. static struct mystr s_match_needed_str;
  250. /* Locate next special token */
  251. struct str_locate_result locate_result =
  252. str_locate_chars(&filter_remain_str, "*?{");
  253. (*iters)++;
  254. /* Isolate text leading up to token (if any) - needs to be matched */
  255. if (locate_result.found)
  256. {
  257. unsigned int indexx = locate_result.index;
  258. str_left(&filter_remain_str, &s_match_needed_str, indexx);
  259. str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1);
  260. str_copy(&filter_remain_str, &temp_str);
  261. last_token = locate_result.char_found;
  262. }
  263. else
  264. {
  265. /* No more tokens. Must match remaining filter string exactly. */
  266. str_copy(&s_match_needed_str, &filter_remain_str);
  267. str_empty(&filter_remain_str);
  268. last_token = 0;
  269. }
  270. if (!str_isempty(&s_match_needed_str))
  271. {
  272. /* Need to match something.. could be a match which has to start at
  273. * current position, or we could allow it to start anywhere
  274. */
  275. unsigned int indexx;
  276. locate_result = str_locate_str(&name_remain_str, &s_match_needed_str);
  277. if (!locate_result.found)
  278. {
  279. /* Fail */
  280. goto out;
  281. }
  282. indexx = locate_result.index;
  283. if (must_match_at_current_pos && indexx > 0)
  284. {
  285. goto out;
  286. }
  287. /* Chop matched string out of remainder */
  288. str_mid_to_end(&name_remain_str, &temp_str,
  289. indexx + str_getlen(&s_match_needed_str));
  290. str_copy(&name_remain_str, &temp_str);
  291. }
  292. if (last_token == '?')
  293. {
  294. if (str_isempty(&name_remain_str))
  295. {
  296. goto out;
  297. }
  298. str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1);
  299. str_copy(&name_remain_str, &temp_str);
  300. must_match_at_current_pos = 1;
  301. }
  302. else if (last_token == '{')
  303. {
  304. struct str_locate_result end_brace =
  305. str_locate_char(&filter_remain_str, '}');
  306. must_match_at_current_pos = 1;
  307. if (end_brace.found)
  308. {
  309. str_split_char(&filter_remain_str, &temp_str, '}');
  310. str_copy(&brace_list_str, &filter_remain_str);
  311. str_copy(&filter_remain_str, &temp_str);
  312. str_split_char(&brace_list_str, &temp_str, ',');
  313. while (!str_isempty(&brace_list_str))
  314. {
  315. str_copy(&new_filter_str, &brace_list_str);
  316. str_append_str(&new_filter_str, &filter_remain_str);
  317. if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str,
  318. iters))
  319. {
  320. ret = 1;
  321. goto out;
  322. }
  323. str_copy(&brace_list_str, &temp_str);
  324. str_split_char(&brace_list_str, &temp_str, ',');
  325. }
  326. goto out;
  327. }
  328. else if (str_isempty(&name_remain_str) ||
  329. str_get_char_at(&name_remain_str, 0) != '{')
  330. {
  331. goto out;
  332. }
  333. else
  334. {
  335. str_right(&name_remain_str, &temp_str,
  336. str_getlen(&name_remain_str) - 1);
  337. str_copy(&name_remain_str, &temp_str);
  338. }
  339. }
  340. else
  341. {
  342. must_match_at_current_pos = 0;
  343. }
  344. }
  345. /* Any incoming string left means no match unless we ended on the correct
  346. * type of wildcard.
  347. */
  348. if (str_getlen(&name_remain_str) > 0 && last_token != '*')
  349. {
  350. goto out;
  351. }
  352. /* OK, a match */
  353. ret = 1;
  354. if (*iters == VSFTP_MATCHITERS_MAX) {
  355. ret = 0;
  356. }
  357. out:
  358. str_free(&filter_remain_str);
  359. str_free(&name_remain_str);
  360. str_free(&temp_str);
  361. str_free(&brace_list_str);
  362. str_free(&new_filter_str);
  363. return ret;
  364. }
  365. static void
  366. build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str,
  367. const struct vsf_sysutil_statbuf* p_stat, long curr_time)
  368. {
  369. static struct mystr s_tmp_str;
  370. filesize_t size = vsf_sysutil_statbuf_get_size(p_stat);
  371. /* Permissions */
  372. str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat));
  373. str_append_char(p_str, ' ');
  374. /* Hard link count */
  375. str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat));
  376. str_lpad(&s_tmp_str, 4);
  377. str_append_str(p_str, &s_tmp_str);
  378. str_append_char(p_str, ' ');
  379. /* User */
  380. if (tunable_hide_ids)
  381. {
  382. str_alloc_text(&s_tmp_str, "ftp");
  383. }
  384. else
  385. {
  386. int uid = vsf_sysutil_statbuf_get_uid(p_stat);
  387. struct vsf_sysutil_user* p_user = 0;
  388. if (tunable_text_userdb_names)
  389. {
  390. p_user = vsf_sysutil_getpwuid(uid);
  391. }
  392. if (p_user == 0)
  393. {
  394. str_alloc_ulong(&s_tmp_str, (unsigned long) uid);
  395. }
  396. else
  397. {
  398. str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user));
  399. }
  400. }
  401. str_rpad(&s_tmp_str, 8);
  402. str_append_str(p_str, &s_tmp_str);
  403. str_append_char(p_str, ' ');
  404. /* Group */
  405. if (tunable_hide_ids)
  406. {
  407. str_alloc_text(&s_tmp_str, "ftp");
  408. }
  409. else
  410. {
  411. int gid = vsf_sysutil_statbuf_get_gid(p_stat);
  412. struct vsf_sysutil_group* p_group = 0;
  413. if (tunable_text_userdb_names)
  414. {
  415. p_group = vsf_sysutil_getgrgid(gid);
  416. }
  417. if (p_group == 0)
  418. {
  419. str_alloc_ulong(&s_tmp_str, (unsigned long) gid);
  420. }
  421. else
  422. {
  423. str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group));
  424. }
  425. }
  426. str_rpad(&s_tmp_str, 8);
  427. str_append_str(p_str, &s_tmp_str);
  428. str_append_char(p_str, ' ');
  429. /* Size in bytes */
  430. str_alloc_filesize_t(&s_tmp_str, size);
  431. str_lpad(&s_tmp_str, 8);
  432. str_append_str(p_str, &s_tmp_str);
  433. str_append_char(p_str, ' ');
  434. /* Date stamp */
  435. str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat,
  436. tunable_use_localtime,
  437. curr_time));
  438. str_append_char(p_str, ' ');
  439. /* Filename */
  440. str_append_str(p_str, p_filename_str);
  441. str_append_text(p_str, "\r\n");
  442. }