1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 1997, 1998 Public Flood Software
4 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5 * Copyright (c) 2001-2020 The ProFTPD Project
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20 *
21 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22 * and other respective copyright holders give permission to link this program
23 * with OpenSSL, and distribute the resulting executable, without including
24 * the source code for OpenSSL in the source distribution.
25 */
26
27 /* Directory listing module for ProFTPD. */
28
29 #include "conf.h"
30
31 #ifndef GLOB_ABORTED
32 #define GLOB_ABORTED GLOB_ABEND
33 #endif
34
35 #define MAP_UID(x) \
36 (fakeuser ? fakeuser : pr_auth_uid2name(cmd->tmp_pool, (x)))
37
38 #define MAP_GID(x) \
39 (fakegroup ? fakegroup : pr_auth_gid2name(cmd->tmp_pool, (x)))
40
41 static void addfile(cmd_rec *, const char *, const char *, time_t, off_t);
42 static int outputfiles(cmd_rec *);
43
44 static int listfile(cmd_rec *, pool *, const char *, const char *);
45 static int listdir(cmd_rec *, pool *, const char *, const char *);
46
47 static int sendline(int flags, char *fmt, ...)
48 #ifdef __GNUC__
49 __attribute__ ((format (printf, 2, 3)));
50 #else
51 ;
52 #endif
53 #define LS_SENDLINE_FL_FLUSH 0x0001
54
55 #define LS_FL_NO_ERROR_IF_ABSENT 0x0001
56 #define LS_FL_LIST_ONLY 0x0002
57 #define LS_FL_NLST_ONLY 0x0004
58 #define LS_FL_ADJUSTED_SYMLINKS 0x0008
59 #define LS_FL_SORTED_NLST 0x0010
60 static unsigned long list_flags = 0UL;
61
62 /* Maximum size of the "dsize" directory block we'll allocate for all of the
63 * entries in a directory (Bug#4247).
64 */
65 #define LS_MAX_DSIZE (1024 * 1024 * 8)
66
67 static unsigned char list_strict_opts = FALSE;
68 static char *list_options = NULL;
69 static unsigned char list_show_symlinks = TRUE, list_times_gmt = TRUE;
70 static unsigned char show_symlinks_hold;
71 static const char *fakeuser = NULL, *fakegroup = NULL;
72 static mode_t fakemode;
73 static unsigned char have_fake_mode = FALSE;
74 static int ls_errno = 0;
75 static time_t ls_curtime = 0;
76
77 static unsigned char use_globbing = TRUE;
78
79 /* Directory listing limits */
80 struct list_limit_rec {
81 unsigned int curr, max;
82 unsigned char logged;
83 };
84
85 static struct list_limit_rec list_ndepth;
86 static struct list_limit_rec list_ndirs;
87 static struct list_limit_rec list_nfiles;
88
89 /* ls options */
90 static int
91 opt_1 = 0,
92 opt_a = 0,
93 opt_A = 0,
94 opt_B = 0,
95 opt_C = 0,
96 opt_c = 0,
97 opt_d = 0,
98 opt_F = 0,
99 opt_h = 0,
100 opt_l = 0,
101 opt_L = 0,
102 opt_n = 0,
103 opt_R = 0,
104 opt_r = 0,
105 opt_S = 0,
106 opt_t = 0,
107 opt_U = 0,
108 opt_u = 0,
109 opt_STAT = 0;
110
111 /* Determines which struct st timestamp is used for sorting, if any. */
112 static int ls_sort_by = 0;
113 #define LS_SORT_BY_MTIME 100
114 #define LS_SORT_BY_CTIME 101
115 #define LS_SORT_BY_ATIME 102
116
117 static char cwd[PR_TUNABLE_PATH_MAX+1] = "";
118
119 /* Find a <Limit> block that limits the given command (which will probably
120 * be LIST). This code borrowed for src/dirtree.c's dir_check_limit().
121 * Note that this function is targeted specifically for ls commands (eg
122 * LIST, NLST, DIRS, and ALL) that might be <Limit>'ed.
123 */
find_ls_limit(char * cmd_name)124 static config_rec *find_ls_limit(char *cmd_name) {
125 config_rec *c = NULL, *limit_c = NULL;
126
127 if (!cmd_name)
128 return NULL;
129
130 if (!session.dir_config)
131 return NULL;
132
133 /* Determine whether this command is <Limit>'ed. */
134 for (c = session.dir_config; c; c = c->parent) {
135 pr_signals_handle();
136
137 if (c->subset) {
138 for (limit_c = (config_rec *) (c->subset->xas_list); limit_c;
139 limit_c = limit_c->next) {
140
141 if (limit_c->config_type == CONF_LIMIT) {
142 register unsigned int i = 0;
143
144 for (i = 0; i < limit_c->argc; i++) {
145
146 /* match any of the appropriate <Limit> arguments
147 */
148 if (strcasecmp(cmd_name, (char *) (limit_c->argv[i])) == 0 ||
149 strcasecmp("DIRS", (char *) (limit_c->argv[i])) == 0 ||
150 strcasecmp("ALL", (char *) (limit_c->argv[i])) == 0) {
151 break;
152 }
153 }
154
155 if (i == limit_c->argc)
156 continue;
157
158 /* Found a <Limit> directive associated with the current command. */
159 return limit_c;
160 }
161 }
162 }
163 }
164
165 return NULL;
166 }
167
is_safe_symlink(pool * p,const char * path,size_t pathlen)168 static int is_safe_symlink(pool *p, const char *path, size_t pathlen) {
169
170 /* First, check the most common cases: '.', './', '..', and '../'. */
171 if ((pathlen == 1 && path[0] == '.') ||
172 (pathlen == 2 && path[0] == '.' && (path[1] == '.' || path[1] == '/')) ||
173 (pathlen == 3 && path[0] == '.' && path[1] == '.' && path[2] == '/')) {
174 return FALSE;
175 }
176
177 /* Next, paranoidly check for uncommon occurrences, e.g. './///', '../////',
178 * etc.
179 */
180 if (pathlen >= 2 &&
181 path[0] == '.' &&
182 (path[pathlen-1] == '/' || path[pathlen-1] == '.')) {
183 char buf[PR_TUNABLE_PATH_MAX + 1], *full_path;
184 size_t buflen;
185
186 full_path = pdircat(p, pr_fs_getcwd(), path, NULL);
187
188 buf[sizeof(buf)-1] = '\0';
189 pr_fs_clean_path(full_path, buf, sizeof(buf)-1);
190 buflen = strlen(buf);
191
192 /* If the cleaned path appears in the current working directory, we
193 * have an "unsafe" symlink pointing to the current directory (or higher
194 * up the path).
195 */
196 if (strncmp(pr_fs_getcwd(), buf, buflen) == 0) {
197 return FALSE;
198 }
199 }
200
201 return TRUE;
202 }
203
push_cwd(char * _cwd,unsigned char * symhold)204 static void push_cwd(char *_cwd, unsigned char *symhold) {
205 if (!_cwd)
206 _cwd = cwd;
207
208 sstrncpy(_cwd, pr_fs_getcwd(), PR_TUNABLE_PATH_MAX + 1);
209 *symhold = show_symlinks_hold = list_show_symlinks;
210 }
211
pop_cwd(char * _cwd,unsigned char * symhold)212 static void pop_cwd(char *_cwd, unsigned char *symhold) {
213 if (!_cwd)
214 _cwd = cwd;
215
216 *symhold = show_symlinks_hold;
217 pr_fsio_chdir(_cwd, *symhold);
218 list_show_symlinks = *symhold;
219 }
220
ls_perms_full(pool * p,cmd_rec * cmd,const char * path,int * hidden)221 static int ls_perms_full(pool *p, cmd_rec *cmd, const char *path, int *hidden) {
222 int res, use_canon = FALSE;
223 char *fullpath;
224 mode_t *fake_mode = NULL;
225
226 fullpath = dir_realpath(p, path);
227 if (fullpath == NULL) {
228 fullpath = dir_canonical_path(p, path);
229 use_canon = TRUE;
230 }
231
232 if (fullpath == NULL) {
233 fullpath = pstrdup(p, path);
234 }
235
236 if (use_canon) {
237 res = dir_check_canon(p, cmd, cmd->group, fullpath, hidden);
238
239 } else {
240 res = dir_check(p, cmd, cmd->group, fullpath, hidden);
241 }
242
243 if (session.dir_config) {
244 unsigned char *tmp = get_param_ptr(session.dir_config->subset,
245 "ShowSymlinks", FALSE);
246
247 if (tmp)
248 list_show_symlinks = *tmp;
249 }
250
251 fake_mode = get_param_ptr(CURRENT_CONF, "DirFakeMode", FALSE);
252 if (fake_mode) {
253 fakemode = *fake_mode;
254 have_fake_mode = TRUE;
255
256 } else {
257 have_fake_mode = FALSE;
258 }
259
260 return res;
261 }
262
ls_perms(pool * p,cmd_rec * cmd,const char * path,int * hidden)263 static int ls_perms(pool *p, cmd_rec *cmd, const char *path, int *hidden) {
264 int res = 0;
265 char fullpath[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
266 mode_t *fake_mode = NULL;
267
268 /* No need to process dotdirs. */
269 if (is_dotdir(path)) {
270 return 1;
271 }
272
273 if (*path == '~') {
274 return ls_perms_full(p, cmd, path, hidden);
275 }
276
277 if (*path != '/') {
278 pr_fs_clean_path(pdircat(p, pr_fs_getcwd(), path, NULL), fullpath,
279 PR_TUNABLE_PATH_MAX);
280
281 } else {
282 pr_fs_clean_path(path, fullpath, PR_TUNABLE_PATH_MAX);
283 }
284
285 res = dir_check(p, cmd, cmd->group, fullpath, hidden);
286
287 if (session.dir_config) {
288 unsigned char *tmp = get_param_ptr(session.dir_config->subset,
289 "ShowSymlinks", FALSE);
290
291 if (tmp)
292 list_show_symlinks = *tmp;
293 }
294
295 fake_mode = get_param_ptr(CURRENT_CONF, "DirFakeMode", FALSE);
296 if (fake_mode) {
297 fakemode = *fake_mode;
298 have_fake_mode = TRUE;
299
300 } else {
301 have_fake_mode = FALSE;
302 }
303
304 return res;
305 }
306
307 /* sendline() now has an internal buffer, to help speed up LIST output.
308 * This buffer is allocated once, the first time sendline() is called.
309 * By using a runtime allocation, we can use pr_config_get_server_xfer_bufsz()
310 * to get the optimal buffer size for network transfers.
311 */
312 static char *listbuf = NULL, *listbuf_ptr = NULL;
313 static size_t listbufsz = 0;
314
sendline(int flags,char * fmt,...)315 static int sendline(int flags, char *fmt, ...) {
316 va_list msg;
317 char buf[PR_TUNABLE_BUFFER_SIZE+1];
318 int res = 0;
319 size_t buflen, listbuflen;
320
321 memset(buf, '\0', sizeof(buf));
322
323 if (listbuf == NULL) {
324 listbufsz = pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR);
325 listbuf = listbuf_ptr = pcalloc(session.pool, listbufsz);
326 pr_trace_msg("data", 8, "allocated list buffer of %lu bytes",
327 (unsigned long) listbufsz);
328 }
329
330 if (flags & LS_SENDLINE_FL_FLUSH) {
331 listbuflen = (listbuf_ptr - listbuf) + strlen(listbuf_ptr);
332
333 if (listbuflen > 0) {
334 int using_ascii = FALSE;
335
336 /* Make sure the ASCII flags are cleared from the session flags,
337 * so that the pr_data_xfer() function does not try to perform
338 * ASCII translation on this data.
339 */
340 if (session.sf_flags & SF_ASCII) {
341 using_ascii = TRUE;
342 }
343
344 session.sf_flags &= ~SF_ASCII;
345 session.sf_flags &= ~SF_ASCII_OVERRIDE;
346
347 res = pr_data_xfer(listbuf, listbuflen);
348 if (res < 0 &&
349 errno != 0) {
350 int xerrno = errno;
351
352 if (session.d != NULL) {
353 xerrno = PR_NETIO_ERRNO(session.d->outstrm);
354 }
355
356 pr_log_debug(DEBUG3, "pr_data_xfer returned %d, error = %s", res,
357 strerror(xerrno));
358 }
359
360 if (using_ascii) {
361 session.sf_flags |= SF_ASCII;
362 }
363 session.sf_flags |= SF_ASCII_OVERRIDE;
364
365 memset(listbuf, '\0', listbufsz);
366 listbuf_ptr = listbuf;
367 pr_trace_msg("data", 8, "flushed %lu bytes of list buffer",
368 (unsigned long) listbuflen);
369 listbuflen = 0;
370 }
371
372 return res;
373 }
374
375 va_start(msg, fmt);
376 pr_vsnprintf(buf, sizeof(buf), fmt, msg);
377 va_end(msg);
378
379 buf[sizeof(buf)-1] = '\0';
380
381 /* If buf won't fit completely into listbuf, flush listbuf */
382 listbuflen = (listbuf_ptr - listbuf) + strlen(listbuf_ptr);
383
384 buflen = strlen(buf);
385 if (buflen >= (listbufsz - listbuflen)) {
386 /* Make sure the ASCII flags are cleared from the session flags,
387 * so that the pr_data_xfer() function does not try to perform
388 * ASCII translation on this data.
389 */
390 session.sf_flags &= ~SF_ASCII_OVERRIDE;
391
392 res = pr_data_xfer(listbuf, listbuflen);
393 if (res < 0 &&
394 errno != 0) {
395 int xerrno = errno;
396
397 if (session.d != NULL &&
398 session.d->outstrm) {
399 xerrno = PR_NETIO_ERRNO(session.d->outstrm);
400 }
401
402 pr_log_debug(DEBUG3, "pr_data_xfer returned %d, error = %s", res,
403 strerror(xerrno));
404 }
405
406 session.sf_flags |= SF_ASCII_OVERRIDE;
407
408 memset(listbuf, '\0', listbufsz);
409 listbuf_ptr = listbuf;
410 pr_trace_msg("data", 8, "flushed %lu bytes of list buffer",
411 (unsigned long) listbuflen);
412 listbuflen = 0;
413 }
414
415 sstrcat(listbuf_ptr, buf, listbufsz - listbuflen);
416 listbuf_ptr += buflen;
417
418 return res;
419 }
420
ls_done(cmd_rec * cmd)421 static void ls_done(cmd_rec *cmd) {
422 pr_data_close2();
423
424 if (!(session.sf_flags & SF_ABORT)) {
425 /* If the transfer was not aborted, consider it a success. */
426 pr_response_add(R_226, _("Transfer complete"));
427 }
428 }
429
430 static char units[6][2] =
431 { "", "k", "M", "G", "T", "P" };
432
ls_fmt_filesize(char * buf,size_t buflen,off_t sz)433 static void ls_fmt_filesize(char *buf, size_t buflen, off_t sz) {
434 if (!opt_h || sz < 1000) {
435 pr_snprintf(buf, buflen, "%8" PR_LU, (pr_off_t) sz);
436
437 } else {
438 register unsigned int i = 0;
439 float size = sz;
440
441 /* Determine the appropriate units label to use. */
442 while (size >= 1024.0) {
443 size /= 1024.0;
444 i++;
445 }
446
447 pr_snprintf(buf, buflen, "%7.1f%s", size, units[i]);
448 }
449 }
450
451 static char months[12][4] =
452 { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
453 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
454
listfile(cmd_rec * cmd,pool * p,const char * resp_code,const char * name)455 static int listfile(cmd_rec *cmd, pool *p, const char *resp_code,
456 const char *name) {
457 register unsigned int i;
458 int rval = 0, len;
459 time_t sort_time;
460 char m[PR_TUNABLE_PATH_MAX+1] = {'\0'}, l[PR_TUNABLE_PATH_MAX+1] = {'\0'}, s[16] = {'\0'};
461 struct stat st;
462 struct tm *t = NULL;
463 char suffix[2];
464 int hidden = 0;
465 char *filename, *ptr;
466 size_t namelen;
467
468 /* Note that listfile() expects to be given the file name, NOT the path.
469 * So strip off any path elements, watching out for any trailing slashes
470 * (Bug#4259).
471 */
472 namelen = strlen(name);
473 for (i = namelen-1; i > 0; i--) {
474 if (name[i] != '/') {
475 break;
476 }
477
478 namelen--;
479 }
480
481 filename = pstrndup(p, name, namelen);
482
483 ptr = strrchr(filename, '/');
484 if (ptr != NULL) {
485 /* Advance past that path separator to get just the filename. */
486 filename = ptr + 1;
487 }
488
489 if (list_nfiles.curr && list_nfiles.max &&
490 list_nfiles.curr >= list_nfiles.max) {
491
492 if (!list_nfiles.logged) {
493 pr_log_debug(DEBUG8, "ListOptions maxfiles (%u) reached",
494 list_nfiles.max);
495 list_nfiles.logged = TRUE;
496 }
497
498 return 2;
499 }
500 list_nfiles.curr++;
501
502 if (p == NULL) {
503 p = cmd->tmp_pool;
504 }
505
506 pr_fs_clear_cache2(name);
507 if (pr_fsio_lstat(name, &st) == 0) {
508 char *display_name = NULL;
509
510 suffix[0] = suffix[1] = '\0';
511
512 display_name = pstrdup(p, name);
513
514 #ifndef PR_USE_NLS
515 if (opt_B) {
516 register unsigned int j;
517 size_t display_namelen, printable_namelen;
518 char *printable_name = NULL;
519
520 display_namelen = strlen(display_name);
521
522 /* Allocate 4 times as much space as necessary, in case every single
523 * character is non-printable.
524 */
525 printable_namelen = (display_namelen * 4);
526 printable_name = pcalloc(p, printable_namelen + 1);
527
528 /* Check for any non-printable characters, and replace them with the
529 * octal escape sequence equivalent.
530 */
531 for (i = 0, j = 0; i < display_namelen && j < printable_namelen; i++) {
532 if (!PR_ISPRINT(display_name[i])) {
533 register int k;
534 int replace_len = 0;
535 char replace[32];
536
537 memset(replace, '\0', sizeof(replace));
538 replace_len = pr_snprintf(replace, sizeof(replace)-1, "\\%03o",
539 display_name[i]);
540
541 for (k = 0; k < replace_len; k++) {
542 printable_name[j++] = replace[k];
543 }
544
545 } else {
546 printable_name[j++] = display_name[i];
547 }
548 }
549
550 display_name = pstrdup(p, printable_name);
551 }
552 #endif /* PR_USE_NLS */
553
554 if (S_ISLNK(st.st_mode) &&
555 (opt_L || !list_show_symlinks)) {
556 /* Attempt to fully dereference symlink */
557 struct stat l_st;
558
559 if (pr_fsio_stat(name, &l_st) != -1) {
560 memcpy(&st, &l_st, sizeof(struct stat));
561
562 /* First see if the symlink itself is hidden e.g. by HideFiles
563 * (see Bug#3924).
564 */
565 if (!ls_perms_full(p, cmd, name, &hidden)) {
566 return 0;
567 }
568
569 if (hidden) {
570 return 0;
571 }
572
573 if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
574 len = dir_readlink(p, name, m, sizeof(m) - 1,
575 PR_DIR_READLINK_FL_HANDLE_REL_PATH);
576
577 } else {
578 len = pr_fsio_readlink(name, m, sizeof(m) - 1);
579 }
580
581 if (len < 0) {
582 return 0;
583 }
584
585 if ((size_t) len >= sizeof(m)) {
586 return 0;
587 }
588
589 m[len] = '\0';
590
591 /* If the symlink points to either '.' or '..', skip it (Bug#3719). */
592 if (is_safe_symlink(p, m, len) == FALSE) {
593 return 0;
594 }
595
596 if (!ls_perms_full(p, cmd, m, NULL)) {
597 return 0;
598 }
599
600 } else {
601 return 0;
602 }
603
604 } else if (S_ISLNK(st.st_mode)) {
605 /* First see if the symlink itself is hidden e.g. by HideFiles
606 * (see Bug#3924).
607 */
608 if (!ls_perms(p, cmd, name, &hidden)) {
609 return 0;
610 }
611
612 if (hidden) {
613 return 0;
614 }
615
616 if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
617 len = dir_readlink(p, name, l, sizeof(l) - 1,
618 PR_DIR_READLINK_FL_HANDLE_REL_PATH);
619
620 } else {
621 len = pr_fsio_readlink(name, l, sizeof(l) - 1);
622 }
623
624 if (len < 0) {
625 return 0;
626 }
627
628 if ((size_t) len >= sizeof(l)) {
629 return 0;
630 }
631
632 l[len] = '\0';
633
634 /* If the symlink points to either '.' or '..', skip it (Bug#3719). */
635 if (is_safe_symlink(p, l, len) == FALSE) {
636 return 0;
637 }
638
639 if (!ls_perms_full(p, cmd, l, &hidden)) {
640 return 0;
641 }
642
643 } else if (!ls_perms(p, cmd, name, &hidden)) {
644 return 0;
645 }
646
647 /* Skip dotfiles, unless requested not to via -a or -A. */
648 if (*filename == '.' &&
649 (!opt_a && (!opt_A || is_dotdir(filename)))) {
650 pr_log_debug(DEBUG10,
651 "skipping listing of hidden file '%s' (no -A/-a options in effect)",
652 filename);
653 return 0;
654 }
655
656 if (hidden) {
657 return 0;
658 }
659
660 switch (ls_sort_by) {
661 case LS_SORT_BY_MTIME:
662 sort_time = st.st_mtime;
663 break;
664
665 case LS_SORT_BY_CTIME:
666 sort_time = st.st_ctime;
667 break;
668
669 case LS_SORT_BY_ATIME:
670 sort_time = st.st_atime;
671 break;
672
673 default:
674 sort_time = st.st_mtime;
675 break;
676 }
677
678 if (list_times_gmt) {
679 t = pr_gmtime(p, (const time_t *) &sort_time);
680
681 } else {
682 t = pr_localtime(p, (const time_t *) &sort_time);
683 }
684
685 if (opt_F) {
686 if (S_ISLNK(st.st_mode)) {
687 suffix[0] = '@';
688
689 } else if (S_ISDIR(st.st_mode)) {
690 suffix[0] = '/';
691 rval = 1;
692
693 } else if (st.st_mode & 0111) {
694 suffix[0] = '*';
695 }
696 }
697
698 if (opt_l) {
699 sstrncpy(m, " ---------", sizeof(m));
700 switch (st.st_mode & S_IFMT) {
701 case S_IFREG:
702 m[0] = '-';
703 break;
704
705 case S_IFLNK:
706 m[0] = 'l';
707 break;
708
709 #ifdef S_IFSOCK
710 case S_IFSOCK:
711 m[0] = 's';
712 break;
713 #endif /* S_IFSOCK */
714
715 case S_IFBLK:
716 m[0] = 'b';
717 break;
718
719 case S_IFCHR:
720 m[0] = 'c';
721 break;
722
723 case S_IFIFO:
724 m[0] = 'p';
725 break;
726
727 case S_IFDIR:
728 m[0] = 'd';
729 rval = 1;
730 break;
731 }
732
733 if (m[0] != ' ') {
734 char nameline[(PR_TUNABLE_PATH_MAX * 2) + 128] = {'\0'};
735 char timeline[6] = {'\0'};
736 mode_t mode = st.st_mode;
737
738 if (have_fake_mode) {
739 mode = fakemode;
740
741 if (S_ISDIR(st.st_mode)) {
742 if (mode & S_IROTH) mode |= S_IXOTH;
743 if (mode & S_IRGRP) mode |= S_IXGRP;
744 if (mode & S_IRUSR) mode |= S_IXUSR;
745 }
746 }
747
748 m[9] = (mode & S_IXOTH)
749 ? ((mode & S_ISVTX) ? 't' : 'x')
750 : ((mode & S_ISVTX) ? 'T' : '-');
751 m[8] = (mode & S_IWOTH) ? 'w' : '-';
752 m[7] = (mode & S_IROTH) ? 'r' : '-';
753 m[6] = (mode & S_IXGRP)
754 ? ((mode & S_ISGID) ? 's' : 'x')
755 : ((mode & S_ISGID) ? 'S' : '-');
756 m[5] = (mode & S_IWGRP) ? 'w' : '-';
757 m[4] = (mode & S_IRGRP) ? 'r' : '-';
758 m[3] = (mode & S_IXUSR) ? ((mode & S_ISUID)
759 ? 's' : 'x')
760 : ((mode & S_ISUID) ? 'S' : '-');
761 m[2] = (mode & S_IWUSR) ? 'w' : '-';
762 m[1] = (mode & S_IRUSR) ? 'r' : '-';
763
764 if (ls_curtime - sort_time > 180 * 24 * 60 * 60) {
765 pr_snprintf(timeline, sizeof(timeline), "%5d", t->tm_year+1900);
766
767 } else {
768 pr_snprintf(timeline, sizeof(timeline), "%02d:%02d", t->tm_hour,
769 t->tm_min);
770 }
771
772 ls_fmt_filesize(s, sizeof(s), st.st_size);
773
774 if (opt_1) {
775 /* One file per line, with no info other than the file name. Easy. */
776 pr_snprintf(nameline, sizeof(nameline)-1, "%s",
777 pr_fs_encode_path(cmd->tmp_pool, display_name));
778
779 } else {
780 if (!opt_n) {
781 /* Format nameline using user/group names. */
782 pr_snprintf(nameline, sizeof(nameline)-1,
783 "%s %3d %-8s %-8s %s %s %2d %s %s", m, (int) st.st_nlink,
784 MAP_UID(st.st_uid), MAP_GID(st.st_gid), s,
785 months[t->tm_mon], t->tm_mday, timeline,
786 pr_fs_encode_path(cmd->tmp_pool, display_name));
787
788 } else {
789 /* Format nameline using user/group IDs. */
790 pr_snprintf(nameline, sizeof(nameline)-1,
791 "%s %3d %-8u %-8u %s %s %2d %s %s", m, (int) st.st_nlink,
792 (unsigned) st.st_uid, (unsigned) st.st_gid, s,
793 months[t->tm_mon], t->tm_mday, timeline,
794 pr_fs_encode_path(cmd->tmp_pool, name));
795 }
796 }
797
798 nameline[sizeof(nameline)-1] = '\0';
799
800 if (S_ISLNK(st.st_mode)) {
801 char *buf = nameline + strlen(nameline);
802
803 suffix[0] = '\0';
804 if (opt_F) {
805 if (pr_fsio_stat(name, &st) == 0) {
806 if (S_ISLNK(st.st_mode)) {
807 suffix[0] = '@';
808
809 } else if (S_ISDIR(st.st_mode)) {
810 suffix[0] = '/';
811
812 } else if (st.st_mode & 0111) {
813 suffix[0] = '*';
814 }
815 }
816 }
817
818 if (!opt_L && list_show_symlinks) {
819 if (sizeof(nameline) - strlen(nameline) > 4) {
820 pr_snprintf(buf, sizeof(nameline) - strlen(nameline) - 4,
821 " -> %s", l);
822 } else {
823 pr_log_pri(PR_LOG_NOTICE, "notice: symlink '%s' yields an "
824 "excessive string, ignoring", name);
825 }
826 }
827
828 nameline[sizeof(nameline)-1] = '\0';
829 }
830
831 if (opt_STAT) {
832 pr_response_add(resp_code, "%s%s", nameline, suffix);
833
834 } else {
835 addfile(cmd, nameline, suffix, sort_time, st.st_size);
836 }
837 }
838
839 } else {
840 if (S_ISREG(st.st_mode) ||
841 S_ISDIR(st.st_mode) ||
842 S_ISLNK(st.st_mode)) {
843 addfile(cmd, pr_fs_encode_path(cmd->tmp_pool, name), suffix, sort_time,
844 st.st_size);
845 }
846 }
847 }
848
849 return rval;
850 }
851
852 static size_t colwidth = 0;
853 static unsigned int filenames = 0;
854
855 struct filename {
856 struct filename *down;
857 struct filename *right;
858 char *line;
859 int top;
860 };
861
862 struct sort_filename {
863 time_t sort_time;
864 off_t size;
865 char *name;
866 char *suffix;
867 };
868
869 static struct filename *head = NULL;
870 static struct filename *tail = NULL;
871 static array_header *sort_arr = NULL;
872 static pool *fpool = NULL;
873
addfile(cmd_rec * cmd,const char * name,const char * suffix,time_t sort_time,off_t size)874 static void addfile(cmd_rec *cmd, const char *name, const char *suffix,
875 time_t sort_time, off_t size) {
876 struct filename *p;
877 size_t l;
878
879 if (name == NULL ||
880 suffix == NULL) {
881 return;
882 }
883
884 /* If we are not sorting (-U is in effect), then we have no need to buffer
885 * up the line, and can send it immediately. This can provide quite a bit
886 * of memory/CPU savings, especially for LIST commands on wide/deep
887 * directories (Bug#4060).
888 */
889 if (opt_U == 1) {
890 (void) sendline(0, "%s%s\r\n", name, suffix);
891 return;
892 }
893
894 if (fpool == NULL) {
895 fpool = make_sub_pool(cmd->tmp_pool);
896 pr_pool_tag(fpool, "mod_ls addfile pool");
897 }
898
899 if (opt_S || opt_t) {
900 struct sort_filename *s;
901
902 if (sort_arr == NULL) {
903 sort_arr = make_array(fpool, 50, sizeof(struct sort_filename));
904 }
905
906 s = (struct sort_filename *) push_array(sort_arr);
907 s->sort_time = sort_time;
908 s->size = size;
909 s->name = pstrdup(fpool, name);
910 s->suffix = pstrdup(fpool, suffix);
911
912 return;
913 }
914
915 l = strlen(name) + strlen(suffix);
916 if (l > colwidth) {
917 colwidth = l;
918 }
919
920 p = (struct filename *) pcalloc(fpool, sizeof(struct filename));
921 p->line = pcalloc(fpool, l + 2);
922 pr_snprintf(p->line, l + 1, "%s%s", name, suffix);
923
924 if (tail) {
925 tail->down = p;
926
927 } else {
928 head = p;
929 }
930
931 tail = p;
932 filenames++;
933 }
934
file_time_cmp(const struct sort_filename * f1,const struct sort_filename * f2)935 static int file_time_cmp(const struct sort_filename *f1,
936 const struct sort_filename *f2) {
937
938 if (f1->sort_time > f2->sort_time)
939 return -1;
940
941 else if (f1->sort_time < f2->sort_time)
942 return 1;
943
944 return 0;
945 }
946
file_time_reverse_cmp(const struct sort_filename * f1,const struct sort_filename * f2)947 static int file_time_reverse_cmp(const struct sort_filename *f1,
948 const struct sort_filename *f2) {
949 return -file_time_cmp(f1, f2);
950 }
951
file_size_cmp(const struct sort_filename * f1,const struct sort_filename * f2)952 static int file_size_cmp(const struct sort_filename *f1,
953 const struct sort_filename *f2) {
954
955 if (f1->size > f2->size)
956 return -1;
957
958 else if (f1->size < f2->size)
959 return 1;
960
961 return 0;
962 }
963
file_size_reverse_cmp(const struct sort_filename * f1,const struct sort_filename * f2)964 static int file_size_reverse_cmp(const struct sort_filename *f1,
965 const struct sort_filename *f2) {
966 return -file_size_cmp(f1, f2);
967 }
968
sortfiles(cmd_rec * cmd)969 static void sortfiles(cmd_rec *cmd) {
970
971 if (sort_arr) {
972
973 /* Sort by time? */
974 if (opt_t) {
975 register unsigned int i = 0;
976 int setting = opt_S;
977 struct sort_filename *elts = sort_arr->elts;
978
979 qsort(sort_arr->elts, sort_arr->nelts, sizeof(struct sort_filename),
980 (int (*)(const void *, const void *))
981 (opt_r ? file_time_reverse_cmp : file_time_cmp));
982
983 opt_S = opt_t = 0;
984
985 for (i = 0; i < sort_arr->nelts; i++) {
986 addfile(cmd, elts[i].name, elts[i].suffix, elts[i].sort_time,
987 elts[i].size);
988 }
989
990 opt_S = setting;
991 opt_t = 1;
992
993 /* Sort by file size? */
994 } else if (opt_S) {
995 register unsigned int i = 0;
996 int setting = opt_t;
997 struct sort_filename *elts = sort_arr->elts;
998
999 qsort(sort_arr->elts, sort_arr->nelts, sizeof(struct sort_filename),
1000 (int (*)(const void *, const void *))
1001 (opt_r ? file_size_reverse_cmp : file_size_cmp));
1002
1003 opt_S = opt_t = 0;
1004
1005 for (i = 0; i < sort_arr->nelts; i++) {
1006 addfile(cmd, elts[i].name, elts[i].suffix, elts[i].sort_time,
1007 elts[i].size);
1008 }
1009
1010 opt_S = 1;
1011 opt_t = setting;
1012 }
1013 }
1014
1015 sort_arr = NULL;
1016 }
1017
outputfiles(cmd_rec * cmd)1018 static int outputfiles(cmd_rec *cmd) {
1019 int n, res = 0;
1020 struct filename *p = NULL, *q = NULL;
1021
1022 if (opt_S || opt_t) {
1023 sortfiles(cmd);
1024 }
1025
1026 if (head == NULL) {
1027 /* Nothing to display. */
1028 if (sendline(LS_SENDLINE_FL_FLUSH, " ") < 0) {
1029 res = -1;
1030 }
1031
1032 destroy_pool(fpool);
1033 fpool = NULL;
1034 sort_arr = NULL;
1035 head = tail = NULL;
1036 colwidth = 0;
1037 filenames = 0;
1038
1039 return res;
1040 }
1041
1042 tail->down = NULL;
1043 tail = NULL;
1044 colwidth = (colwidth | 7) + 1;
1045 if (opt_l || !opt_C) {
1046 colwidth = 75;
1047 }
1048
1049 /* avoid division by 0 if colwidth > 75 */
1050 if (colwidth > 75) {
1051 colwidth = 75;
1052 }
1053
1054 if (opt_C) {
1055 p = head;
1056 p->top = 1;
1057 n = (filenames + (75 / colwidth)-1) / (75 / colwidth);
1058
1059 while (n && p) {
1060 pr_signals_handle();
1061
1062 p = p->down;
1063 if (p) {
1064 p->top = 0;
1065 }
1066 n--;
1067 }
1068
1069 q = head;
1070 while (p) {
1071 pr_signals_handle();
1072
1073 p->top = q->top;
1074 q->right = p;
1075 q = q->down;
1076 p = p->down;
1077 }
1078
1079 while (q) {
1080 pr_signals_handle();
1081
1082 q->right = NULL;
1083 q = q->down;
1084 }
1085
1086 p = head;
1087 while (p && p->down && !p->down->top) {
1088 pr_signals_handle();
1089 p = p->down;
1090 }
1091
1092 if (p && p->down) {
1093 p->down = NULL;
1094 }
1095 }
1096
1097 p = head;
1098 while (p) {
1099 pr_signals_handle();
1100
1101 q = p;
1102 p = p->down;
1103 while (q) {
1104 char pad[6] = {'\0'};
1105
1106 pr_signals_handle();
1107
1108 if (!q->right) {
1109 sstrncpy(pad, "\r\n", sizeof(pad));
1110
1111 } else {
1112 unsigned int idx = 0;
1113
1114 sstrncpy(pad, "\t\t\t\t\t", sizeof(pad));
1115
1116 idx = (colwidth + 7 - strlen(q->line)) / 8;
1117 if (idx >= sizeof(pad)) {
1118 idx = sizeof(pad)-1;
1119 }
1120
1121 pad[idx] = '\0';
1122 }
1123
1124 if (sendline(0, "%s%s", q->line, pad) < 0) {
1125 return -1;
1126 }
1127
1128 q = q->right;
1129 }
1130 }
1131
1132 if (sendline(LS_SENDLINE_FL_FLUSH, " ") < 0) {
1133 res = -1;
1134 }
1135
1136 destroy_pool(fpool);
1137 fpool = NULL;
1138 sort_arr = NULL;
1139 head = tail = NULL;
1140 colwidth = 0;
1141 filenames = 0;
1142
1143 return res;
1144 }
1145
discard_output(void)1146 static void discard_output(void) {
1147 if (fpool) {
1148 destroy_pool(fpool);
1149 }
1150 fpool = NULL;
1151
1152 head = tail = NULL;
1153 colwidth = 0;
1154 filenames = 0;
1155 }
1156
dircmp(const void * a,const void * b)1157 static int dircmp(const void *a, const void *b) {
1158 #if defined(PR_USE_NLS) && defined(HAVE_STRCOLL)
1159 return strcoll(*(const char **)a, *(const char **)b);
1160 #else
1161 return strcmp(*(const char **)a, *(const char **)b);
1162 #endif /* !PR_USE_NLS or !HAVE_STRCOLL */
1163 }
1164
sreaddir(const char * dirname,const int sort)1165 static char **sreaddir(const char *dirname, const int sort) {
1166 DIR *d;
1167 struct dirent *de;
1168 struct stat st;
1169 int i, dir_fd;
1170 char **p;
1171 long ssize;
1172 size_t dsize;
1173
1174 pr_fs_clear_cache2(dirname);
1175 if (pr_fsio_stat(dirname, &st) < 0) {
1176 return NULL;
1177 }
1178
1179 if (!S_ISDIR(st.st_mode)) {
1180 errno = ENOTDIR;
1181 return NULL;
1182 }
1183
1184 d = pr_fsio_opendir(dirname);
1185 if (d == NULL) {
1186 return NULL;
1187 }
1188
1189 /* It doesn't matter if the following guesses are wrong, but it slows
1190 * the system a bit and wastes some memory if they are wrong, so
1191 * don't guess *too* naively!
1192 *
1193 * 'dsize' must be greater than zero or we loop forever.
1194 * 'ssize' must be at least big enough to hold a maximum-length name.
1195 */
1196
1197 /* Guess the number of entries in the directory. */
1198 dsize = (((size_t) st.st_size) / 4) + 10;
1199 if (dsize > LS_MAX_DSIZE) {
1200 dsize = LS_MAX_DSIZE;
1201 }
1202
1203 /* The directory has been opened already, but portably accessing the file
1204 * descriptor inside the DIR struct isn't easy. Some systems use "dd_fd" or
1205 * "__dd_fd" rather than "d_fd". Still others work really hard at opacity.
1206 */
1207 #if defined(HAVE_DIRFD)
1208 dir_fd = dirfd(d);
1209 #elif defined(HAVE_STRUCT_DIR_D_FD)
1210 dir_fd = d->d_fd;
1211 #elif defined(HAVE_STRUCT_DIR_DD_FD)
1212 dir_fd = d->dd_fd;
1213 #elif defined(HAVE_STRUCT_DIR___DD_FD)
1214 dir_fd = d->__dd_fd;
1215 #else
1216 dir_fd = 0;
1217 #endif
1218
1219 ssize = get_name_max((char *) dirname, dir_fd);
1220 if (ssize < 1) {
1221 pr_log_debug(DEBUG1, "get_name_max(%s, %d) = %lu, using %d", dirname,
1222 dir_fd, (unsigned long) ssize, NAME_MAX_GUESS);
1223 ssize = NAME_MAX_GUESS;
1224 }
1225
1226 ssize *= ((dsize / 4) + 1);
1227
1228 /* Allocate first block for holding filenames. Yes, we are explicitly using
1229 * malloc (and realloc, and calloc, later) rather than the memory pools.
1230 * Recursive directory listings would eat up a lot of pool memory that is
1231 * only freed when the _entire_ directory structure has been parsed. Also,
1232 * this helps to keep the memory footprint a little smaller.
1233 */
1234 pr_trace_msg("data", 8, "allocating readdir buffer of %lu bytes",
1235 (unsigned long) (dsize * sizeof(char *)));
1236
1237 p = malloc(dsize * sizeof(char *));
1238 if (p == NULL) {
1239 pr_log_pri(PR_LOG_ALERT, "Out of memory!");
1240 exit(1);
1241 }
1242
1243 i = 0;
1244
1245 while ((de = pr_fsio_readdir(d)) != NULL) {
1246 pr_signals_handle();
1247
1248 if ((size_t) i >= dsize - 1) {
1249 char **newp;
1250
1251 /* The test above goes off one item early in case this is the last item
1252 * in the directory and thus next time we will want to NULL-terminate
1253 * the array.
1254 */
1255 pr_log_debug(DEBUG0, "Reallocating sreaddir buffer from %lu entries to "
1256 "%lu entries", (unsigned long) dsize, (unsigned long) dsize * 2);
1257
1258 /* Allocate bigger array for pointers to filenames */
1259 pr_trace_msg("data", 8, "allocating readdir buffer of %lu bytes",
1260 (unsigned long) (2 * dsize * sizeof(char *)));
1261
1262 newp = (char **) realloc(p, 2 * dsize * sizeof(char *));
1263 if (newp == NULL) {
1264 pr_log_pri(PR_LOG_ALERT, "Out of memory!");
1265 exit(1);
1266 }
1267 p = newp;
1268 dsize *= 2;
1269 }
1270
1271 /* Append the filename to the block. */
1272 p[i] = (char *) calloc(strlen(de->d_name) + 1, sizeof(char));
1273 if (p[i] == NULL) {
1274 pr_log_pri(PR_LOG_ALERT, "Out of memory!");
1275 exit(1);
1276 }
1277 sstrncpy(p[i++], de->d_name, strlen(de->d_name) + 1);
1278 }
1279
1280 pr_fsio_closedir(d);
1281
1282 /* This is correct, since the above is off by one element.
1283 */
1284 p[i] = NULL;
1285
1286 if (sort) {
1287 PR_DEVEL_CLOCK(qsort(p, i, sizeof(char *), dircmp));
1288 }
1289
1290 return p;
1291 }
1292
1293 /* This listdir() requires a chdir() first. */
listdir(cmd_rec * cmd,pool * workp,const char * resp_code,const char * name)1294 static int listdir(cmd_rec *cmd, pool *workp, const char *resp_code,
1295 const char *name) {
1296 char **dir;
1297 int dest_workp = 0;
1298 register unsigned int i = 0;
1299
1300 if (list_ndepth.curr && list_ndepth.max &&
1301 list_ndepth.curr >= list_ndepth.max) {
1302
1303 if (!list_ndepth.logged) {
1304 /* Don't forget to take away the one we add to maxdepth internally. */
1305 pr_log_debug(DEBUG8, "ListOptions maxdepth (%u) reached",
1306 list_ndepth.max - 1);
1307 list_ndepth.logged = TRUE;
1308 }
1309
1310 return 1;
1311 }
1312
1313 if (list_ndirs.curr && list_ndirs.max &&
1314 list_ndirs.curr >= list_ndirs.max) {
1315
1316 if (!list_ndirs.logged) {
1317 pr_log_debug(DEBUG8, "ListOptions maxdirs (%u) reached", list_ndirs.max);
1318 list_ndirs.logged = TRUE;
1319 }
1320
1321 return 1;
1322 }
1323 list_ndirs.curr++;
1324
1325 if (XFER_ABORTED) {
1326 return -1;
1327 }
1328
1329 if (workp == NULL) {
1330 workp = make_sub_pool(cmd->tmp_pool);
1331 pr_pool_tag(workp, "mod_ls: listdir(): workp (from cmd->tmp_pool)");
1332 dest_workp++;
1333
1334 } else {
1335 workp = make_sub_pool(workp);
1336 pr_pool_tag(workp, "mod_ls: listdir(): workp (from workp)");
1337 dest_workp++;
1338 }
1339
1340 PR_DEVEL_CLOCK(dir = sreaddir(".", opt_U ? FALSE : TRUE));
1341 if (dir) {
1342 char **s;
1343 char **r;
1344
1345 int d = 0;
1346
1347 s = dir;
1348 while (*s) {
1349 if (**s == '.') {
1350 if (!opt_a && (!opt_A || is_dotdir(*s))) {
1351 d = 0;
1352
1353 } else {
1354 d = listfile(cmd, workp, resp_code, *s);
1355 }
1356
1357 } else {
1358 d = listfile(cmd, workp, resp_code, *s);
1359 }
1360
1361 if (opt_R && d == 0) {
1362
1363 /* This is a nasty hack. If listfile() returns a zero, and we
1364 * will be recursing (-R option), make sure we don't try to list
1365 * this file again by changing the first character of the path
1366 * to ".". Such files are skipped later.
1367 */
1368 **s = '.';
1369 *(*s + 1) = '\0';
1370
1371 } else if (d == 2) {
1372 break;
1373 }
1374
1375 s++;
1376 }
1377
1378 if (outputfiles(cmd) < 0) {
1379 if (dest_workp) {
1380 destroy_pool(workp);
1381 }
1382
1383 /* Explicitly free the memory allocated for containing the list of
1384 * filenames.
1385 */
1386 i = 0;
1387 while (dir[i] != NULL) {
1388 free(dir[i++]);
1389 }
1390 free(dir);
1391
1392 return -1;
1393 }
1394
1395 r = dir;
1396 while (opt_R && r != s) {
1397 char cwd_buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
1398 unsigned char symhold;
1399
1400 if (*r && (strcmp(*r, ".") == 0 || strcmp(*r, "..") == 0)) {
1401 r++;
1402 continue;
1403 }
1404
1405 /* Add some signal processing to this while loop, as it can
1406 * potentially recurse deeply.
1407 */
1408 pr_signals_handle();
1409
1410 if (list_ndirs.curr && list_ndirs.max &&
1411 list_ndirs.curr >= list_ndirs.max) {
1412
1413 if (!list_ndirs.logged) {
1414 pr_log_debug(DEBUG8, "ListOptions maxdirs (%u) reached",
1415 list_ndirs.max);
1416 list_ndirs.logged = TRUE;
1417 }
1418
1419 break;
1420 }
1421
1422 if (list_nfiles.curr && list_nfiles.max &&
1423 list_nfiles.curr >= list_nfiles.max) {
1424
1425 if (!list_nfiles.logged) {
1426 pr_log_debug(DEBUG8, "ListOptions maxfiles (%u) reached",
1427 list_nfiles.max);
1428 list_nfiles.logged = TRUE;
1429 }
1430
1431 break;
1432 }
1433
1434 push_cwd(cwd_buf, &symhold);
1435
1436 if (*r && ls_perms_full(workp, cmd, (char *) *r, NULL) &&
1437 !pr_fsio_chdir_canon(*r, !opt_L && list_show_symlinks)) {
1438 char *subdir;
1439 int res = 0;
1440
1441 if (strcmp(name, ".") == 0) {
1442 subdir = *r;
1443
1444 } else {
1445 subdir = pdircat(workp, name, *r, NULL);
1446 }
1447
1448 if (opt_STAT) {
1449 pr_response_add(resp_code, "%s", "");
1450 pr_response_add(resp_code, "%s:",
1451 pr_fs_encode_path(cmd->tmp_pool, subdir));
1452
1453 } else if (sendline(0, "\r\n%s:\r\n",
1454 pr_fs_encode_path(cmd->tmp_pool, subdir)) < 0 ||
1455 sendline(LS_SENDLINE_FL_FLUSH, " ") < 0) {
1456 pop_cwd(cwd_buf, &symhold);
1457
1458 if (dest_workp) {
1459 destroy_pool(workp);
1460 }
1461
1462 /* Explicitly free the memory allocated for containing the list of
1463 * filenames.
1464 */
1465 i = 0;
1466 while (dir[i] != NULL) {
1467 free(dir[i++]);
1468 }
1469 free(dir);
1470
1471 return -1;
1472 }
1473
1474 list_ndepth.curr++;
1475 res = listdir(cmd, workp, resp_code, subdir);
1476 list_ndepth.curr--;
1477 pop_cwd(cwd_buf, &symhold);
1478
1479 if (res > 0) {
1480 break;
1481
1482 } else if (res < 0) {
1483 if (dest_workp) {
1484 destroy_pool(workp);
1485 }
1486
1487 /* Explicitly free the memory allocated for containing the list of
1488 * filenames.
1489 */
1490 i = 0;
1491 while (dir[i] != NULL) {
1492 free(dir[i++]);
1493 }
1494 free(dir);
1495
1496 return -1;
1497 }
1498 }
1499 r++;
1500 }
1501
1502 } else {
1503 pr_trace_msg("fsio", 9,
1504 "sreaddir() error on '.': %s", strerror(errno));
1505 }
1506
1507 if (dest_workp) {
1508 destroy_pool(workp);
1509 }
1510
1511 /* Explicitly free the memory allocated for containing the list of
1512 * filenames.
1513 */
1514 if (dir) {
1515 i = 0;
1516 while (dir[i] != NULL) {
1517 free(dir[i++]);
1518 }
1519 free(dir);
1520 }
1521
1522 return 0;
1523 }
1524
ls_terminate(void)1525 static void ls_terminate(void) {
1526 if (!opt_STAT) {
1527 discard_output();
1528
1529 if (!XFER_ABORTED) {
1530 /* An error has occurred, other than client ABOR */
1531 if (ls_errno) {
1532 pr_data_abort(ls_errno,FALSE);
1533
1534 } else {
1535 pr_data_abort((session.d && session.d->outstrm ?
1536 PR_NETIO_ERRNO(session.d->outstrm) : errno), FALSE);
1537 }
1538 }
1539 ls_errno = 0;
1540
1541 } else if (ls_errno) {
1542 pr_response_add(R_211, _("ERROR: %s"), strerror(ls_errno));
1543 ls_errno = 0;
1544 }
1545 }
1546
parse_list_opts(char ** opt,int * glob_flags,int handle_plus_opts)1547 static void parse_list_opts(char **opt, int *glob_flags, int handle_plus_opts) {
1548 char *ptr;
1549
1550 /* First, scan for options. Any leading whitespace before options can
1551 * be skipped, as long as there ARE options.
1552 */
1553 ptr = *opt;
1554
1555 while (PR_ISSPACE(*ptr)) {
1556 pr_signals_handle();
1557 ptr++;
1558 }
1559
1560 if (*ptr == '-') {
1561 /* Options are found; skip past the leading whitespace. */
1562 *opt = ptr;
1563 }
1564
1565 /* Check for standard /bin/ls options */
1566 while (*opt && **opt == '-') {
1567 pr_signals_handle();
1568
1569 while ((*opt)++ && PR_ISALNUM(**opt)) {
1570 switch (**opt) {
1571 case '1':
1572 if (session.curr_cmd_id != PR_CMD_STAT_ID) {
1573 opt_1 = 1;
1574 opt_l = opt_C = 0;
1575 }
1576 break;
1577
1578 case 'A':
1579 opt_A = 1;
1580 break;
1581
1582 case 'a':
1583 opt_a = 1;
1584 break;
1585
1586 case 'B':
1587 opt_B = 1;
1588 break;
1589
1590 case 'C':
1591 if (session.curr_cmd_id != PR_CMD_NLST_ID) {
1592 opt_l = 0;
1593 opt_C = 1;
1594 }
1595 break;
1596
1597 case 'c':
1598 opt_c = 1;
1599 ls_sort_by = LS_SORT_BY_CTIME;
1600 break;
1601
1602 case 'd':
1603 opt_d = 1;
1604 break;
1605
1606 case 'F':
1607 if (session.curr_cmd_id != PR_CMD_NLST_ID) {
1608 opt_F = 1;
1609 }
1610 break;
1611
1612 case 'h':
1613 if (session.curr_cmd_id != PR_CMD_NLST_ID) {
1614 opt_h = 1;
1615 }
1616 break;
1617
1618 case 'L':
1619 opt_L = 1;
1620 break;
1621
1622 case 'l':
1623 if (session.curr_cmd_id != PR_CMD_NLST_ID) {
1624 opt_l = 1;
1625 opt_C = 0;
1626 opt_1 = 0;
1627 }
1628 break;
1629
1630 case 'n':
1631 if (session.curr_cmd_id != PR_CMD_NLST_ID) {
1632 opt_n = 1;
1633 }
1634 break;
1635
1636 case 'R':
1637 opt_R = 1;
1638 break;
1639
1640 case 'r':
1641 opt_r = 1;
1642 break;
1643
1644 case 'S':
1645 opt_S = 1;
1646 break;
1647
1648 case 't':
1649 opt_t = 1;
1650 if (glob_flags) {
1651 *glob_flags |= GLOB_NOSORT;
1652 }
1653 break;
1654
1655 case 'U':
1656 opt_U = 1;
1657 opt_c = opt_S = opt_t = 0;
1658 break;
1659
1660 case 'u':
1661 opt_u = 1;
1662 ls_sort_by = LS_SORT_BY_ATIME;
1663 break;
1664 }
1665 }
1666
1667 ptr = *opt;
1668
1669 while (*ptr &&
1670 PR_ISSPACE(*ptr)) {
1671 pr_signals_handle();
1672 ptr++;
1673 }
1674
1675 if (*ptr == '-') {
1676 /* Options are found; skip past the leading whitespace. */
1677 *opt = ptr;
1678
1679 } else if (**opt && *(*opt + 1) == ' ') {
1680 /* If the next character is a blank space, advance just one character. */
1681 (*opt)++;
1682 break;
1683
1684 } else {
1685 *opt = ptr;
1686 break;
1687 }
1688 }
1689
1690 if (!handle_plus_opts) {
1691 return;
1692 }
1693
1694 /* Check for non-standard options */
1695 while (*opt && **opt == '+') {
1696 pr_signals_handle();
1697
1698 while ((*opt)++ && PR_ISALNUM(**opt)) {
1699 switch (**opt) {
1700 case '1':
1701 opt_1 = opt_l = opt_C = 0;
1702 break;
1703
1704 case 'A':
1705 opt_A = 0;
1706 break;
1707
1708 case 'a':
1709 opt_a = 0;
1710 break;
1711
1712 case 'B':
1713 opt_B = 0;
1714 break;
1715
1716 case 'C':
1717 opt_l = opt_C = 0;
1718 break;
1719
1720 case 'c':
1721 opt_c = 0;
1722
1723 /* -u is still in effect, sort by that, otherwise use the default. */
1724 ls_sort_by = opt_u ? LS_SORT_BY_ATIME : LS_SORT_BY_MTIME;
1725 break;
1726
1727 case 'd':
1728 opt_d = 0;
1729 break;
1730
1731 case 'F':
1732 opt_F = 0;
1733 break;
1734
1735 case 'h':
1736 opt_h = 0;
1737 break;
1738
1739 case 'L':
1740 opt_L = 0;
1741 break;
1742
1743 case 'l':
1744 opt_l = opt_C = 0;
1745 break;
1746
1747 case 'n':
1748 opt_n = 0;
1749 break;
1750
1751 case 'R':
1752 opt_R = 0;
1753 break;
1754
1755 case 'r':
1756 opt_r = 0;
1757 break;
1758
1759 case 'S':
1760 opt_S = 0;
1761 break;
1762
1763 case 't':
1764 opt_t = 0;
1765 if (glob_flags)
1766 *glob_flags &= GLOB_NOSORT;
1767 break;
1768
1769 case 'U':
1770 opt_U = 0;
1771 break;
1772
1773 case 'u':
1774 opt_u = 0;
1775
1776 /* -c is still in effect, sort by that, otherwise use the default. */
1777 ls_sort_by = opt_c ? LS_SORT_BY_CTIME : LS_SORT_BY_MTIME;
1778 break;
1779 }
1780 }
1781
1782 ptr = *opt;
1783
1784 while (*ptr &&
1785 PR_ISSPACE(*ptr)) {
1786 pr_signals_handle();
1787 ptr++;
1788 }
1789
1790 if (*ptr == '+') {
1791 /* Options are found; skip past the leading whitespace. */
1792 *opt = ptr;
1793
1794 } else if (**opt && *(*opt + 1) == ' ') {
1795 /* If the next character is a blank space, advance just one character. */
1796 (*opt)++;
1797 break;
1798
1799 } else {
1800 *opt = ptr;
1801 break;
1802 }
1803 }
1804 }
1805
1806 /* Only look for and parse options if there are more than two arguments.
1807 * This will avoid trying to handle the file/path in a command like:
1808 *
1809 * LIST -filename
1810 *
1811 * as if it were options.
1812 *
1813 * Returns TRUE if the given command has options that should be parsed,
1814 * FALSE otherwise.
1815 */
have_options(cmd_rec * cmd,const char * arg)1816 static int have_options(cmd_rec *cmd, const char *arg) {
1817 struct stat st;
1818 int res;
1819
1820 /* If we have more than 2 arguments, then we definitely have parseable
1821 * options.
1822 */
1823
1824 if (cmd->argc > 2) {
1825 return TRUE;
1826 }
1827
1828 /* Now we need to determine if the given string (arg) should be handled
1829 * as options (as when the target path is implied, e.g. "LIST -al") or
1830 * as a real path. We'll simply do a stat on the string; if it exists,
1831 * then it's a path.
1832 */
1833
1834 pr_fs_clear_cache2(arg);
1835 res = pr_fsio_stat(arg, &st);
1836
1837 if (res == 0) {
1838 return FALSE;
1839 }
1840
1841 return TRUE;
1842 }
1843
1844 /* The main work for LIST and STAT (not NLST). Returns -1 on error, 0 if
1845 * successful.
1846 */
dolist(cmd_rec * cmd,const char * opt,const char * resp_code,int clear_flags)1847 static int dolist(cmd_rec *cmd, const char *opt, const char *resp_code,
1848 int clear_flags) {
1849 int skiparg = 0;
1850 int glob_flags = 0;
1851
1852 char *arg = (char*) opt;
1853
1854 ls_curtime = time(NULL);
1855
1856 if (clear_flags) {
1857 opt_1 = opt_A = opt_a = opt_B = opt_C = opt_d = opt_F = opt_h = opt_n =
1858 opt_r = opt_R = opt_S = opt_t = opt_STAT = opt_L = 0;
1859 }
1860
1861 if (have_options(cmd, arg)) {
1862 if (!list_strict_opts) {
1863 parse_list_opts(&arg, &glob_flags, FALSE);
1864
1865 } else {
1866 char *ptr;
1867
1868 /* Even if the user-given options are ignored, they still need to
1869 * "processed" (i.e. skip past options) in order to get to the paths.
1870 *
1871 * First, scan for options. Any leading whitespace before options can
1872 * be skipped, as long as there ARE options.
1873 */
1874 ptr = arg;
1875
1876 while (PR_ISSPACE(*ptr)) {
1877 pr_signals_handle();
1878 ptr++;
1879 }
1880
1881 if (*ptr == '-') {
1882 /* Options are found; skip past the leading whitespace. */
1883 arg = ptr;
1884 }
1885
1886 while (arg && *arg == '-') {
1887 /* Advance to the next whitespace */
1888 while (*arg != '\0' && !PR_ISSPACE(*arg)) {
1889 arg++;
1890 }
1891
1892 ptr = arg;
1893
1894 while (*ptr &&
1895 PR_ISSPACE(*ptr)) {
1896 pr_signals_handle();
1897 ptr++;
1898 }
1899
1900 if (*ptr == '-') {
1901 /* Options are found; skip past the leading whitespace. */
1902 arg = ptr;
1903
1904 } else if (*(arg + 1) == ' ') {
1905 /* If the next character is a blank space, advance just one
1906 * character.
1907 */
1908 arg++;
1909 break;
1910
1911 } else {
1912 arg = ptr;
1913 break;
1914 }
1915 }
1916 }
1917 }
1918
1919 if (list_options) {
1920 parse_list_opts(&list_options, &glob_flags, TRUE);
1921 }
1922
1923 if (arg && *arg) {
1924 int justone = 1;
1925 glob_t g;
1926 int globbed = FALSE;
1927 int a;
1928 char pbuffer[PR_TUNABLE_PATH_MAX + 1] = "";
1929 char *target;
1930
1931 /* Make sure the glob_t is initialized. */
1932 memset(&g, '\0', sizeof(g));
1933
1934 if (*arg == '~') {
1935 struct passwd *pw;
1936 int i;
1937 const char *p;
1938
1939 for (i = 0, p = arg + 1;
1940 ((size_t) i < sizeof(pbuffer) - 1) && p && *p && *p != '/';
1941 pbuffer[i++] = *p++);
1942
1943 pbuffer[i] = '\0';
1944
1945 pw = pr_auth_getpwnam(cmd->tmp_pool, i ? pbuffer : session.user);
1946 if (pw) {
1947 pr_snprintf(pbuffer, sizeof(pbuffer), "%s%s", pw->pw_dir, p);
1948
1949 } else {
1950 pbuffer[0] = '\0';
1951 }
1952 }
1953
1954 target = *pbuffer ? pbuffer : arg;
1955
1956 /* Open data connection */
1957 if (!opt_STAT) {
1958 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
1959 int xerrno = errno;
1960
1961 pr_cmd_set_errno(cmd, xerrno);
1962 errno = xerrno;
1963 return -1;
1964 }
1965
1966 session.sf_flags |= SF_ASCII_OVERRIDE;
1967 }
1968
1969 /* If there are no globbing characters in the given target,
1970 * we can check to see if it even exists.
1971 */
1972 if (pr_str_is_fnmatch(target) == FALSE) {
1973 struct stat st;
1974
1975 pr_fs_clear_cache2(target);
1976 if (pr_fsio_stat(target, &st) < 0) {
1977 int xerrno = errno;
1978
1979 if (xerrno == ENOENT &&
1980 (list_flags & LS_FL_NO_ERROR_IF_ABSENT)) {
1981 return 0;
1982 }
1983
1984 pr_response_add_err(R_450, "%s: %s",
1985 pr_fs_encode_path(cmd->tmp_pool, target), strerror(xerrno));
1986
1987 errno = xerrno;
1988 return -1;
1989 }
1990 }
1991
1992 /* Check perms on the directory/file we are about to scan. */
1993 if (!ls_perms_full(cmd->tmp_pool, cmd, target, NULL)) {
1994 a = -1;
1995 skiparg = TRUE;
1996
1997 } else {
1998 skiparg = FALSE;
1999
2000 if (use_globbing &&
2001 pr_str_is_fnmatch(target)) {
2002 a = pr_fs_glob(target, glob_flags, NULL, &g);
2003 if (a == 0) {
2004 pr_log_debug(DEBUG8, "LIST: glob(3) returned %lu %s",
2005 (unsigned long) g.gl_pathc, g.gl_pathc != 1 ? "paths" : "path");
2006 globbed = TRUE;
2007
2008 } else {
2009 if (a == GLOB_NOMATCH) {
2010 pr_log_debug(DEBUG10, "LIST: glob(3) returned GLOB_NOMATCH "
2011 "for '%s', handling as literal path", target);
2012
2013 /* Trick the following code into using the non-glob() processed
2014 * path.
2015 */
2016 a = 0;
2017 g.gl_pathv = (char **) pcalloc(cmd->tmp_pool, 2 * sizeof(char *));
2018 g.gl_pathv[0] = (char *) pstrdup(cmd->tmp_pool, target);
2019 g.gl_pathv[1] = NULL;
2020 }
2021 }
2022
2023 } else {
2024 /* Trick the following code into using the non-glob() processed path */
2025 a = 0;
2026 g.gl_pathv = (char **) pcalloc(cmd->tmp_pool, 2 * sizeof(char *));
2027 g.gl_pathv[0] = (char *) pstrdup(cmd->tmp_pool, target);
2028 g.gl_pathv[1] = NULL;
2029 }
2030 }
2031
2032 if (!a) {
2033 char **path;
2034
2035 path = g.gl_pathv;
2036
2037 if (path && path[0] && path[1]) {
2038 justone = 0;
2039 }
2040
2041 while (path &&
2042 *path) {
2043 struct stat st;
2044
2045 pr_signals_handle();
2046
2047 pr_fs_clear_cache2(*path);
2048 if (pr_fsio_lstat(*path, &st) == 0) {
2049 mode_t target_mode, lmode;
2050 target_mode = st.st_mode;
2051
2052 if (S_ISLNK(st.st_mode) &&
2053 (lmode = symlink_mode2(cmd->tmp_pool, (char *) *path)) != 0) {
2054 if (opt_L || !list_show_symlinks) {
2055 st.st_mode = lmode;
2056 }
2057
2058 if (lmode != 0) {
2059 target_mode = lmode;
2060 }
2061 }
2062
2063 /* If the -d option is used or the file is not a directory, OR
2064 * if the -R option is NOT used AND the file IS a directory AND
2065 * the file is NOT the target/given parameter, then list the file
2066 * as is.
2067 */
2068 if (opt_d ||
2069 !(S_ISDIR(target_mode)) ||
2070 (!opt_R && S_ISDIR(target_mode) && strcmp(*path, target) != 0)) {
2071
2072 if (listfile(cmd, cmd->tmp_pool, resp_code, *path) < 0) {
2073 ls_terminate();
2074 if (use_globbing && globbed) {
2075 pr_fs_globfree(&g);
2076 }
2077 return -1;
2078 }
2079
2080 **path = '\0';
2081 }
2082
2083 } else {
2084 **path = '\0';
2085 }
2086
2087 path++;
2088 }
2089
2090 if (outputfiles(cmd) < 0) {
2091 ls_terminate();
2092 if (use_globbing && globbed) {
2093 pr_fs_globfree(&g);
2094 }
2095 return -1;
2096 }
2097
2098 /* At this point, the only paths left in g.gl_pathv should be
2099 * directories; anything else should have been listed/handled
2100 * above.
2101 */
2102
2103 path = g.gl_pathv;
2104 while (path &&
2105 *path) {
2106 pr_signals_handle();
2107
2108 if (**path &&
2109 ls_perms_full(cmd->tmp_pool, cmd, *path, NULL)) {
2110 char cwd_buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
2111 unsigned char symhold;
2112
2113 if (!justone) {
2114 if (opt_STAT) {
2115 pr_response_add(resp_code, "%s", "");
2116 pr_response_add(resp_code, "%s:",
2117 pr_fs_encode_path(cmd->tmp_pool, *path));
2118
2119 } else {
2120 sendline(0, "\r\n%s:\r\n",
2121 pr_fs_encode_path(cmd->tmp_pool, *path));
2122 sendline(LS_SENDLINE_FL_FLUSH, " ");
2123 }
2124 }
2125
2126 /* Recurse into the directory. */
2127 push_cwd(cwd_buf, &symhold);
2128
2129 if (pr_fsio_chdir_canon(*path, !opt_L && list_show_symlinks) == 0) {
2130 int res = 0;
2131
2132 list_ndepth.curr++;
2133 res = listdir(cmd, cmd->tmp_pool, resp_code, *path);
2134 list_ndepth.curr--;
2135
2136 pop_cwd(cwd_buf, &symhold);
2137
2138 if (res > 0) {
2139 break;
2140
2141 } else if (res < 0) {
2142 ls_terminate();
2143 if (use_globbing && globbed) {
2144 pr_fs_globfree(&g);
2145 }
2146 return -1;
2147 }
2148
2149 } else {
2150 pop_cwd(cwd_buf, &symhold);
2151 }
2152 }
2153
2154 if (XFER_ABORTED) {
2155 discard_output();
2156 if (use_globbing && globbed) {
2157 pr_fs_globfree(&g);
2158 }
2159 return -1;
2160 }
2161
2162 path++;
2163 }
2164
2165 if (outputfiles(cmd) < 0) {
2166 ls_terminate();
2167 if (use_globbing && globbed) {
2168 pr_fs_globfree(&g);
2169 }
2170 return -1;
2171 }
2172
2173 } else if (!skiparg) {
2174 if (a == GLOB_NOSPACE) {
2175 pr_response_add(R_226, _("Out of memory during globbing of %s"),
2176 pr_fs_encode_path(cmd->tmp_pool, arg));
2177
2178 } else if (a == GLOB_ABORTED) {
2179 pr_response_add(R_226, _("Read error during globbing of %s"),
2180 pr_fs_encode_path(cmd->tmp_pool, arg));
2181
2182 } else if (a != GLOB_NOMATCH) {
2183 pr_response_add(R_226, _("Unknown error during globbing of %s"),
2184 pr_fs_encode_path(cmd->tmp_pool, arg));
2185 }
2186 }
2187
2188 if (!skiparg && use_globbing && globbed) {
2189 pr_fs_globfree(&g);
2190 }
2191
2192 if (XFER_ABORTED) {
2193 discard_output();
2194 return -1;
2195 }
2196
2197 } else {
2198
2199 /* Open data connection */
2200 if (!opt_STAT) {
2201 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
2202 int xerrno = errno;
2203
2204 pr_cmd_set_errno(cmd, xerrno);
2205 errno = xerrno;
2206 return -1;
2207 }
2208
2209 session.sf_flags |= SF_ASCII_OVERRIDE;
2210 }
2211
2212 if (ls_perms_full(cmd->tmp_pool, cmd, ".", NULL)) {
2213
2214 if (opt_d) {
2215 if (listfile(cmd, NULL, resp_code, ".") < 0) {
2216 ls_terminate();
2217 return -1;
2218 }
2219
2220 } else {
2221 list_ndepth.curr++;
2222 if (listdir(cmd, NULL, resp_code, ".") < 0) {
2223 ls_terminate();
2224 return -1;
2225 }
2226
2227 list_ndepth.curr--;
2228 }
2229 }
2230
2231 if (outputfiles(cmd) < 0) {
2232 ls_terminate();
2233 return -1;
2234 }
2235 }
2236
2237 return 0;
2238 }
2239
2240 /* Display listing of a single file, no permission checking is done.
2241 * An error is only returned if the data connection cannot be opened or is
2242 * aborted.
2243 */
nlstfile(cmd_rec * cmd,const char * file)2244 static int nlstfile(cmd_rec *cmd, const char *file) {
2245 int res = 0;
2246 char *display_name;
2247
2248 /* If the data connection isn't open, return an error */
2249 if ((session.sf_flags & SF_XFER) == 0) {
2250 errno = EPERM;
2251 return -1;
2252 }
2253
2254 /* XXX Note that "NLST <glob>" was sent, we might be receiving paths
2255 * here, not just file names. And that is not what dir_hide_file() is
2256 * expecting.
2257 */
2258 if (dir_hide_file(file))
2259 return 1;
2260
2261 display_name = pstrdup(cmd->tmp_pool, file);
2262
2263 #ifndef PR_USE_NLS
2264 if (opt_B) {
2265 register unsigned int i, j;
2266 size_t display_namelen, printable_namelen;
2267 char *printable_name = NULL;
2268
2269 display_namelen = strlen(display_name);
2270
2271 /* Allocate 4 times as much space as necessary, in case every single
2272 * character is non-printable.
2273 */
2274 printable_namelen = (display_namelen * 4);
2275 printable_name = pcalloc(cmd->tmp_pool, printable_namelen + 1);
2276
2277 /* Check for any non-printable characters, and replace them with the octal
2278 * escape sequence equivalent.
2279 */
2280 for (i = 0, j = 0; i < display_namelen && j < printable_namelen; i++) {
2281 if (!PR_ISPRINT(display_name[i])) {
2282 register int k;
2283 int replace_len = 0;
2284 char replace[32];
2285
2286 memset(replace, '\0', sizeof(replace));
2287 replace_len = pr_snprintf(replace, sizeof(replace)-1, "\\%03o",
2288 display_name[i]);
2289
2290 for (k = 0; k < replace_len; k++) {
2291 printable_name[j++] = replace[k];
2292 }
2293
2294 } else {
2295 printable_name[j++] = display_name[i];
2296 }
2297 }
2298
2299 display_name = pstrdup(cmd->tmp_pool, printable_name);
2300 }
2301 #endif /* PR_USE_NLS */
2302
2303 if (opt_1) {
2304 char *ptr;
2305
2306 /* If the -1 option is configured, we want to make sure that we only
2307 * display a file, not a path. And it's possible that we given a path
2308 * here.
2309 */
2310 ptr = strrchr(display_name, '/');
2311 if (ptr != NULL) {
2312 size_t display_namelen;
2313
2314 display_namelen = strlen(display_name);
2315 if (display_namelen > 1) {
2316 /* Make sure that we handle a possible display_name of '/' properly. */
2317 display_name = ptr + 1;
2318 }
2319 }
2320 }
2321
2322 /* Be sure to flush the output */
2323 res = sendline(0, "%s\r\n", pr_fs_encode_path(cmd->tmp_pool, display_name));
2324 if (res < 0)
2325 return res;
2326
2327 return 1;
2328 }
2329
2330 /* Display listing of a directory, ACL checks performed on each entry,
2331 * sent in NLST fashion. Files which are inaccessible via ACL are skipped,
2332 * error returned if data conn cannot be opened or is aborted.
2333 */
nlstdir(cmd_rec * cmd,const char * dir)2334 static int nlstdir(cmd_rec *cmd, const char *dir) {
2335 char **list, *p, *f,
2336 file[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
2337 char cwd_buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
2338 pool *workp;
2339 unsigned char symhold;
2340 int curdir = FALSE, i, j, count = 0, hidden = 0, use_sorting = FALSE;
2341 mode_t mode;
2342 config_rec *c = NULL;
2343 unsigned char ignore_hidden = FALSE;
2344
2345 if (list_ndepth.curr && list_ndepth.max &&
2346 list_ndepth.curr >= list_ndepth.max) {
2347
2348 if (!list_ndepth.logged) {
2349 /* Don't forget to take away the one we add to maxdepth internally. */
2350 pr_log_debug(DEBUG8, "ListOptions maxdepth (%u) reached",
2351 list_ndepth.max - 1);
2352 list_ndepth.logged = TRUE;
2353 }
2354
2355 return 0;
2356 }
2357
2358 if (list_ndirs.curr && list_ndirs.max &&
2359 list_ndirs.curr >= list_ndirs.max) {
2360
2361 if (!list_ndirs.logged) {
2362 pr_log_debug(DEBUG8, "ListOptions maxdirs (%u) reached", list_ndirs.max);
2363 list_ndirs.logged = TRUE;
2364 }
2365
2366 return 0;
2367 }
2368 list_ndirs.curr++;
2369
2370 workp = make_sub_pool(cmd->tmp_pool);
2371 pr_pool_tag(workp, "mod_ls: nlstdir(): workp (from cmd->tmp_pool)");
2372
2373 if (!*dir || (*dir == '.' && !dir[1]) || strcmp(dir, "./") == 0) {
2374 curdir = TRUE;
2375 dir = "";
2376
2377 } else {
2378
2379 /* If dir is not '.', then we need to change directories. Hence we
2380 * push our current working directory onto the stack, do the chdir,
2381 * and pop back, afterwards.
2382 */
2383 push_cwd(cwd_buf, &symhold);
2384
2385 if (pr_fsio_chdir_canon(dir, !opt_L && list_show_symlinks) < 0) {
2386 pop_cwd(cwd_buf, &symhold);
2387
2388 destroy_pool(workp);
2389 return 0;
2390 }
2391 }
2392
2393 if (list_flags & LS_FL_SORTED_NLST) {
2394 use_sorting = TRUE;
2395 }
2396
2397 PR_DEVEL_CLOCK(list = sreaddir(".", use_sorting));
2398 if (list == NULL) {
2399 pr_trace_msg("fsio", 9,
2400 "sreaddir() error on '.': %s", strerror(errno));
2401
2402 if (!curdir) {
2403 pop_cwd(cwd_buf, &symhold);
2404 }
2405
2406 destroy_pool(workp);
2407 return 0;
2408 }
2409
2410 /* Search for relevant <Limit>'s to this NLST command. If found,
2411 * check to see whether hidden files should be ignored.
2412 */
2413 c = find_ls_limit(cmd->argv[0]);
2414 if (c != NULL) {
2415 unsigned char *ignore = get_param_ptr(c->subset, "IgnoreHidden", FALSE);
2416
2417 if (ignore &&
2418 *ignore == TRUE) {
2419 ignore_hidden = TRUE;
2420 }
2421 }
2422
2423 j = 0;
2424 while (list[j] && count >= 0) {
2425 p = list[j++];
2426
2427 pr_signals_handle();
2428
2429 if (*p == '.') {
2430 if (!opt_a && (!opt_A || is_dotdir(p))) {
2431 continue;
2432
2433 /* Make sure IgnoreHidden is properly honored. */
2434 } else if (ignore_hidden) {
2435 continue;
2436 }
2437 }
2438
2439 if (list_flags & LS_FL_ADJUSTED_SYMLINKS) {
2440 i = dir_readlink(cmd->tmp_pool, p, file, sizeof(file) - 1,
2441 PR_DIR_READLINK_FL_HANDLE_REL_PATH);
2442
2443 } else {
2444 i = pr_fsio_readlink(p, file, sizeof(file) - 1);
2445 }
2446
2447 if (i > 0) {
2448 if ((size_t) i >= sizeof(file)) {
2449 continue;
2450 }
2451
2452 file[i] = '\0';
2453 f = file;
2454
2455 } else {
2456 f = p;
2457 }
2458
2459 if (ls_perms(workp, cmd, dir_best_path(cmd->tmp_pool, f), &hidden)) {
2460 if (hidden) {
2461 continue;
2462 }
2463
2464 mode = file_mode2(cmd->tmp_pool, f);
2465 if (mode == 0) {
2466 continue;
2467 }
2468
2469 if (!curdir) {
2470 char *str = NULL;
2471
2472 if (opt_1) {
2473 /* Send just the file name, not the path. */
2474 str = pr_fs_encode_path(cmd->tmp_pool, p);
2475
2476 } else {
2477 str = pr_fs_encode_path(cmd->tmp_pool,
2478 pdircat(cmd->tmp_pool, dir, p, NULL));
2479 }
2480
2481 if (sendline(0, "%s\r\n", str) < 0) {
2482 count = -1;
2483
2484 } else {
2485 count++;
2486
2487 if (list_nfiles.curr > 0 &&
2488 list_nfiles.max > 0 &&
2489 list_nfiles.curr >= list_nfiles.max) {
2490
2491 if (!list_nfiles.logged) {
2492 pr_log_debug(DEBUG8, "ListOptions maxfiles (%u) reached",
2493 list_nfiles.max);
2494 list_nfiles.logged = TRUE;
2495 }
2496
2497 break;
2498 }
2499 list_nfiles.curr++;
2500 }
2501
2502 } else {
2503 if (sendline(0, "%s\r\n", pr_fs_encode_path(cmd->tmp_pool, p)) < 0) {
2504 count = -1;
2505
2506 } else {
2507 count++;
2508
2509 if (list_nfiles.curr > 0 &&
2510 list_nfiles.max > 0 &&
2511 list_nfiles.curr >= list_nfiles.max) {
2512
2513 if (!list_nfiles.logged) {
2514 pr_log_debug(DEBUG8, "ListOptions maxfiles (%u) reached",
2515 list_nfiles.max);
2516 list_nfiles.logged = TRUE;
2517 }
2518
2519 break;
2520 }
2521 list_nfiles.curr++;
2522 }
2523 }
2524 }
2525 }
2526
2527 sendline(LS_SENDLINE_FL_FLUSH, " ");
2528
2529 if (!curdir) {
2530 pop_cwd(cwd_buf, &symhold);
2531 }
2532 destroy_pool(workp);
2533
2534 /* Explicitly free the memory allocated for containing the list of
2535 * filenames.
2536 */
2537 i = 0;
2538 while (list[i] != NULL) {
2539 free(list[i++]);
2540 }
2541 free(list);
2542
2543 return count;
2544 }
2545
2546 /* The LIST command. */
genericlist(cmd_rec * cmd)2547 MODRET genericlist(cmd_rec *cmd) {
2548 int res = 0;
2549 char *decoded_path = NULL;
2550 unsigned char *tmp = NULL;
2551 mode_t *fake_mode = NULL;
2552 config_rec *c = NULL;
2553
2554 tmp = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
2555 if (tmp != NULL) {
2556 list_show_symlinks = *tmp;
2557 }
2558
2559 list_strict_opts = FALSE;
2560 list_nfiles.max = list_ndirs.max = list_ndepth.max = 0;
2561
2562 c = find_config(CURRENT_CONF, CONF_PARAM, "ListOptions", FALSE);
2563 while (c != NULL) {
2564 unsigned long flags;
2565
2566 pr_signals_handle();
2567
2568 flags = *((unsigned long *) c->argv[5]);
2569
2570 /* Make sure that this ListOptions can be applied to the LIST command.
2571 * If not, keep looking for other applicable ListOptions.
2572 */
2573 if (flags & LS_FL_NLST_ONLY) {
2574 pr_log_debug(DEBUG10, "%s: skipping NLSTOnly ListOptions",
2575 (char *) cmd->argv[0]);
2576 c = find_config_next(c, c->next, CONF_PARAM, "ListOptions", FALSE);
2577 continue;
2578 }
2579
2580 list_options = c->argv[0];
2581 list_strict_opts = *((unsigned char *) c->argv[1]);
2582 list_ndepth.max = *((unsigned int *) c->argv[2]);
2583
2584 /* We add one to the configured maxdepth in order to allow it to
2585 * function properly: if one configures a maxdepth of 2, one should
2586 * allowed to list the current directory, and all subdirectories one
2587 * layer deeper. For the checks to work, the maxdepth of 2 needs to
2588 * handled internally as a maxdepth of 3.
2589 */
2590 if (list_ndepth.max) {
2591 list_ndepth.max += 1;
2592 }
2593
2594 list_nfiles.max = *((unsigned int *) c->argv[3]);
2595 list_ndirs.max = *((unsigned int *) c->argv[4]);
2596 list_flags = *((unsigned long *) c->argv[5]);
2597
2598 break;
2599 }
2600
2601 fakeuser = get_param_ptr(CURRENT_CONF, "DirFakeUser", FALSE);
2602
2603 /* Check for a configured "logged in user" DirFakeUser. */
2604 if (fakeuser != NULL &&
2605 strncmp(fakeuser, "~", 2) == 0) {
2606 fakeuser = session.user;
2607 }
2608
2609 fakegroup = get_param_ptr(CURRENT_CONF, "DirFakeGroup", FALSE);
2610
2611 /* Check for a configured "logged in user" DirFakeGroup. */
2612 if (fakegroup != NULL &&
2613 strncmp(fakegroup, "~", 2) == 0) {
2614 fakegroup = session.group;
2615 }
2616
2617 fake_mode = get_param_ptr(CURRENT_CONF, "DirFakeMode", FALSE);
2618 if (fake_mode) {
2619 fakemode = *fake_mode;
2620 have_fake_mode = TRUE;
2621
2622 } else {
2623 have_fake_mode = FALSE;
2624 }
2625
2626 tmp = get_param_ptr(TOPLEVEL_CONF, "TimesGMT", FALSE);
2627 if (tmp != NULL) {
2628 list_times_gmt = *tmp;
2629 }
2630
2631 decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
2632 FSIO_DECODE_FL_TELL_ERRORS);
2633 if (decoded_path == NULL) {
2634 int xerrno = errno;
2635
2636 pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
2637 strerror(xerrno));
2638 pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
2639 cmd->arg);
2640
2641 pr_cmd_set_errno(cmd, xerrno);
2642 errno = xerrno;
2643 return PR_ERROR(cmd);
2644 }
2645
2646 res = dolist(cmd, decoded_path, R_211, TRUE);
2647
2648 if (XFER_ABORTED) {
2649 pr_data_abort(0, 0);
2650 res = -1;
2651
2652 } else if (session.sf_flags & SF_XFER) {
2653 ls_done(cmd);
2654 }
2655
2656 opt_l = 0;
2657
2658 return (res == -1 ? PR_ERROR(cmd) : PR_HANDLED(cmd));
2659 }
2660
ls_log_nlst(cmd_rec * cmd)2661 MODRET ls_log_nlst(cmd_rec *cmd) {
2662 pr_data_cleanup();
2663 return PR_DECLINED(cmd);
2664 }
2665
ls_err_nlst(cmd_rec * cmd)2666 MODRET ls_err_nlst(cmd_rec *cmd) {
2667 pr_data_cleanup();
2668 return PR_DECLINED(cmd);
2669 }
2670
ls_stat(cmd_rec * cmd)2671 MODRET ls_stat(cmd_rec *cmd) {
2672 struct stat st;
2673 int res;
2674 char *arg = cmd->arg, *decoded_path, *path, *resp_code = NULL;
2675 unsigned char *ptr = NULL;
2676 mode_t *fake_mode = NULL;
2677 config_rec *c = NULL;
2678
2679 if (cmd->argc == 1) {
2680 /* In this case, the client is requesting the current session status. */
2681
2682 if (!dir_check(cmd->tmp_pool, cmd, cmd->group, session.cwd, NULL)) {
2683 int xerrno = EPERM;
2684
2685 pr_response_add_err(R_500, "%s: %s", (char *) cmd->argv[0],
2686 strerror(xerrno));
2687
2688 pr_cmd_set_errno(cmd, xerrno);
2689 errno = xerrno;
2690 return PR_ERROR(cmd);
2691 }
2692
2693 pr_response_add(R_211, _("Status of '%s'"), main_server->ServerName);
2694 pr_response_add(R_DUP, _("Connected from %s (%s)"), session.c->remote_name,
2695 pr_netaddr_get_ipstr(session.c->remote_addr));
2696 pr_response_add(R_DUP, _("Logged in as %s"), session.user);
2697 pr_response_add(R_DUP, _("TYPE: %s, STRUcture: File, Mode: Stream"),
2698 (session.sf_flags & SF_ASCII) ? "ASCII" : "BINARY");
2699
2700 if (session.total_bytes) {
2701 pr_response_add(R_DUP, _("Total bytes transferred for session: %" PR_LU),
2702 (pr_off_t) session.total_bytes);
2703 }
2704
2705 if (session.sf_flags & SF_XFER) {
2706 /* Report on the data transfer attributes. */
2707
2708 pr_response_add(R_DUP, _("%s from %s port %u"),
2709 (session.sf_flags & SF_PASSIVE) ?
2710 _("Passive data transfer from") : _("Active data transfer to"),
2711 pr_netaddr_get_ipstr(session.d->remote_addr), session.d->remote_port);
2712
2713 if (session.xfer.file_size) {
2714 pr_response_add(R_DUP, "%s %s (%" PR_LU "/%" PR_LU ")",
2715 session.xfer.direction == PR_NETIO_IO_RD ? C_STOR : C_RETR,
2716 session.xfer.path, (pr_off_t) session.xfer.file_size,
2717 (pr_off_t) session.xfer.total_bytes);
2718
2719 } else {
2720 pr_response_add(R_DUP, "%s %s (%" PR_LU ")",
2721 session.xfer.direction == PR_NETIO_IO_RD ? C_STOR : C_RETR,
2722 session.xfer.path, (pr_off_t) session.xfer.total_bytes);
2723 }
2724
2725 } else {
2726 pr_response_add(R_DUP, _("No data connection"));
2727 }
2728
2729 pr_response_add(R_DUP, _("End of status"));
2730 return PR_HANDLED(cmd);
2731 }
2732
2733 list_nfiles.curr = list_ndirs.curr = list_ndepth.curr = 0;
2734 list_nfiles.logged = list_ndirs.logged = list_ndepth.logged = FALSE;
2735
2736 decoded_path = pr_fs_decode_path2(cmd->tmp_pool, arg,
2737 FSIO_DECODE_FL_TELL_ERRORS);
2738 if (decoded_path == NULL) {
2739 int xerrno = errno;
2740
2741 pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", arg,
2742 strerror(xerrno));
2743 pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
2744 arg);
2745
2746 pr_cmd_set_errno(cmd, xerrno);
2747 errno = xerrno;
2748 return PR_ERROR(cmd);
2749 }
2750
2751 arg = decoded_path;
2752
2753 /* Get to the actual argument. */
2754 if (*arg == '-') {
2755 while (arg && *arg && !PR_ISSPACE(*arg)) {
2756 arg++;
2757 }
2758 }
2759
2760 while (arg && *arg && PR_ISSPACE(*arg)) {
2761 arg++;
2762 }
2763
2764 ptr = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
2765 if (ptr != NULL) {
2766 list_show_symlinks = *ptr;
2767 }
2768
2769 list_strict_opts = FALSE;
2770 list_ndepth.max = list_nfiles.max = list_ndirs.max = 0;
2771
2772 c = find_config(CURRENT_CONF, CONF_PARAM, "ListOptions", FALSE);
2773 while (c != NULL) {
2774 unsigned long flags;
2775
2776 pr_signals_handle();
2777
2778 flags = *((unsigned long *) c->argv[5]);
2779
2780 /* Make sure that this ListOptions can be applied to the STAT command.
2781 * If not, keep looking for other applicable ListOptions.
2782 */
2783 if (flags & LS_FL_LIST_ONLY) {
2784 pr_log_debug(DEBUG10, "%s: skipping LISTOnly ListOptions",
2785 (char *) cmd->argv[0]);
2786 c = find_config_next(c, c->next, CONF_PARAM, "ListOptions", FALSE);
2787 continue;
2788 }
2789
2790 if (flags & LS_FL_NLST_ONLY) {
2791 pr_log_debug(DEBUG10, "%s: skipping NLSTOnly ListOptions",
2792 (char *) cmd->argv[0]);
2793 c = find_config_next(c, c->next, CONF_PARAM, "ListOptions", FALSE);
2794 continue;
2795 }
2796
2797 list_options = c->argv[0];
2798 list_strict_opts = *((unsigned char *) c->argv[1]);
2799
2800 list_ndepth.max = *((unsigned int *) c->argv[2]);
2801
2802 /* We add one to the configured maxdepth in order to allow it to
2803 * function properly: if one configures a maxdepth of 2, one should
2804 * allowed to list the current directory, and all subdirectories one
2805 * layer deeper. For the checks to work, the maxdepth of 2 needs to
2806 * handled internally as a maxdepth of 3.
2807 */
2808 if (list_ndepth.max) {
2809 list_ndepth.max += 1;
2810 }
2811
2812 list_nfiles.max = *((unsigned int *) c->argv[3]);
2813 list_ndirs.max = *((unsigned int *) c->argv[4]);
2814 list_flags = *((unsigned long *) c->argv[5]);
2815
2816 break;
2817 }
2818
2819 fakeuser = get_param_ptr(CURRENT_CONF, "DirFakeUser", FALSE);
2820
2821 /* Check for a configured "logged in user" DirFakeUser. */
2822 if (fakeuser != NULL &&
2823 strncmp(fakeuser, "~", 2) == 0) {
2824 fakeuser = session.user;
2825 }
2826
2827 fakegroup = get_param_ptr(CURRENT_CONF, "DirFakeGroup", FALSE);
2828
2829 /* Check for a configured "logged in user" DirFakeGroup. */
2830 if (fakegroup != NULL &&
2831 strncmp(fakegroup, "~", 2) == 0) {
2832 fakegroup = session.group;
2833 }
2834
2835 fake_mode = get_param_ptr(CURRENT_CONF, "DirFakeMode", FALSE);
2836 if (fake_mode) {
2837 fakemode = *fake_mode;
2838 have_fake_mode = TRUE;
2839
2840 } else {
2841 have_fake_mode = FALSE;
2842 }
2843
2844 ptr = get_param_ptr(TOPLEVEL_CONF, "TimesGMT", FALSE);
2845 if (ptr != NULL) {
2846 list_times_gmt = *ptr;
2847 }
2848
2849 opt_C = opt_d = opt_F = opt_R = 0;
2850 opt_a = opt_l = opt_STAT = 1;
2851
2852 path = (arg && *arg) ? arg : ".";
2853
2854 pr_fs_clear_cache2(path);
2855 if (list_show_symlinks) {
2856 res = pr_fsio_lstat(path, &st);
2857
2858 } else {
2859 res = pr_fsio_stat(path, &st);
2860 }
2861
2862 if (res < 0) {
2863 int xerrno = errno;
2864
2865 pr_response_add_err(R_450, "%s: %s", path, strerror(xerrno));
2866
2867 pr_cmd_set_errno(cmd, xerrno);
2868 errno = xerrno;
2869 return PR_ERROR(cmd);
2870 }
2871
2872 if (S_ISDIR(st.st_mode)) {
2873 resp_code = R_212;
2874
2875 } else {
2876 resp_code = R_213;
2877 }
2878
2879 pr_response_add(resp_code, _("Status of %s:"),
2880 pr_fs_encode_path(cmd->tmp_pool, path));
2881 res = dolist(cmd, path, resp_code, FALSE);
2882 pr_response_add(resp_code, _("End of status"));
2883 return (res == -1 ? PR_ERROR(cmd) : PR_HANDLED(cmd));
2884 }
2885
ls_list(cmd_rec * cmd)2886 MODRET ls_list(cmd_rec *cmd) {
2887 list_nfiles.curr = list_ndirs.curr = list_ndepth.curr = 0;
2888 list_nfiles.logged = list_ndirs.logged = list_ndepth.logged = FALSE;
2889
2890 opt_l = 1;
2891
2892 return genericlist(cmd);
2893 }
2894
2895 /* NLST is a very simplistic directory listing, unlike LIST (which emulates
2896 * ls(1)), it only sends a list of all files/directories matching the glob(s).
2897 */
ls_nlst(cmd_rec * cmd)2898 MODRET ls_nlst(cmd_rec *cmd) {
2899 char *decoded_path, *target, buf[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
2900 size_t targetlen = 0;
2901 config_rec *c = NULL;
2902 int res = 0, hidden = 0;
2903 int glob_flags = GLOB_NOSORT;
2904 unsigned char *tmp = NULL;
2905
2906 list_nfiles.curr = list_ndirs.curr = list_ndepth.curr = 0;
2907 list_nfiles.logged = list_ndirs.logged = list_ndepth.logged = FALSE;
2908
2909 tmp = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
2910 if (tmp != NULL) {
2911 list_show_symlinks = *tmp;
2912 }
2913
2914 decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->arg,
2915 FSIO_DECODE_FL_TELL_ERRORS);
2916 if (decoded_path == NULL) {
2917 int xerrno = errno;
2918
2919 pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", cmd->arg,
2920 strerror(xerrno));
2921 pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
2922 cmd->arg);
2923
2924 pr_cmd_set_errno(cmd, xerrno);
2925 errno = xerrno;
2926 return PR_ERROR(cmd);
2927 }
2928
2929 target = cmd->argc == 1 ? "." : decoded_path;
2930
2931 c = find_config(CURRENT_CONF, CONF_PARAM, "ListOptions", FALSE);
2932 while (c != NULL) {
2933 unsigned long flags;
2934
2935 pr_signals_handle();
2936
2937 flags = *((unsigned long *) c->argv[5]);
2938
2939 /* Make sure that this ListOptions can be applied to the NLST command.
2940 * If not, keep looking for other applicable ListOptions.
2941 */
2942 if (flags & LS_FL_LIST_ONLY) {
2943 pr_log_debug(DEBUG10, "%s: skipping LISTOnly ListOptions",
2944 (char *) cmd->argv[0]);
2945 c = find_config_next(c, c->next, CONF_PARAM, "ListOptions", FALSE);
2946 continue;
2947 }
2948
2949 list_options = c->argv[0];
2950 list_strict_opts = *((unsigned char *) c->argv[1]);
2951
2952 list_ndepth.max = *((unsigned int *) c->argv[2]);
2953
2954 /* We add one to the configured maxdepth in order to allow it to
2955 * function properly: if one configures a maxdepth of 2, one should
2956 * allowed to list the current directory, and all subdirectories one
2957 * layer deeper. For the checks to work, the maxdepth of 2 needs to
2958 * handled internally as a maxdepth of 3.
2959 */
2960 if (list_ndepth.max) {
2961 list_ndepth.max += 1;
2962 }
2963
2964 list_nfiles.max = *((unsigned int *) c->argv[3]);
2965 list_ndirs.max = *((unsigned int *) c->argv[4]);
2966 list_flags = *((unsigned long *) c->argv[5]);
2967
2968 break;
2969 }
2970
2971 /* Clear the listing option flags. */
2972 opt_1 = opt_A = opt_a = opt_B = opt_C = opt_d = opt_F = opt_n = opt_r =
2973 opt_R = opt_S = opt_t = opt_STAT = opt_L = 0;
2974
2975 if (have_options(cmd, target)) {
2976 if (!list_strict_opts) {
2977 parse_list_opts(&target, &glob_flags, FALSE);
2978
2979 } else {
2980 char *ptr;
2981
2982 /* Even if the user-given options are ignored, they still need to
2983 * "processed" (i.e. skip past options) in order to get to the paths.
2984 *
2985 * First, scan for options. Any leading whitespace before options can
2986 * be skipped, as long as there ARE options.
2987 */
2988 ptr = target;
2989
2990 while (PR_ISSPACE(*ptr)) {
2991 pr_signals_handle();
2992 ptr++;
2993 }
2994
2995 if (*ptr == '-') {
2996 /* Options are found; skip past the leading whitespace. */
2997 target = ptr;
2998 }
2999
3000 while (target && *target == '-') {
3001 /* Advance to the next whitespace */
3002 while (*target != '\0' && !PR_ISSPACE(*target)) {
3003 target++;
3004 }
3005
3006 ptr = target;
3007
3008 while (*ptr &&
3009 PR_ISSPACE(*ptr)) {
3010 pr_signals_handle();
3011 ptr++;
3012 }
3013
3014 if (*ptr == '-') {
3015 /* Options are found; skip past the leading whitespace. */
3016 target = ptr;
3017
3018 } else if (*target && *(target + 1) == ' ') {
3019 /* If the next character is a blank space, advance just one
3020 * character.
3021 */
3022 target++;
3023 break;
3024
3025 } else {
3026 target = ptr;
3027 break;
3028 }
3029 }
3030 }
3031 }
3032
3033 if (list_options) {
3034 parse_list_opts(&list_options, &glob_flags, TRUE);
3035 }
3036
3037 /* If, after parsing out any options, the target string is empty, assume
3038 * the current directory (Bug#4069).
3039 */
3040 if (*target == '\0') {
3041 target = pstrdup(cmd->tmp_pool, ".");
3042 }
3043
3044 /* If the target starts with '~' ... */
3045 if (*target == '~') {
3046 char pb[PR_TUNABLE_PATH_MAX + 1] = {'\0'};
3047 struct passwd *pw = NULL;
3048 int i = 0;
3049 const char *p = target;
3050
3051 p++;
3052
3053 while (*p && *p != '/' && i < PR_TUNABLE_PATH_MAX)
3054 pb[i++] = *p++;
3055 pb[i] = '\0';
3056
3057 pw = pr_auth_getpwnam(cmd->tmp_pool, i ? pb : session.user);
3058 if (pw != NULL) {
3059 pr_snprintf(pb, sizeof(pb), "%s%s", pw->pw_dir, p);
3060 sstrncpy(buf, pb, sizeof(buf));
3061 target = buf;
3062 }
3063 }
3064
3065 /* If the target is a glob, get the listing of files/dirs to send. */
3066 if (use_globbing &&
3067 pr_str_is_fnmatch(target)) {
3068 glob_t g;
3069 char **path, *p;
3070 int globbed = FALSE;
3071
3072 /* Make sure the glob_t is initialized */
3073 memset(&g, '\0', sizeof(glob_t));
3074
3075 res = pr_fs_glob(target, glob_flags, NULL, &g);
3076 if (res == 0) {
3077 pr_log_debug(DEBUG8, "NLST: glob(3) returned %lu %s",
3078 (unsigned long) g.gl_pathc, g.gl_pathc != 1 ? "paths" : "path");
3079 globbed = TRUE;
3080
3081 } else {
3082 if (res == GLOB_NOMATCH) {
3083 struct stat st;
3084
3085 pr_fs_clear_cache2(target);
3086 if (pr_fsio_stat(target, &st) == 0) {
3087 pr_log_debug(DEBUG10, "NLST: glob(3) returned GLOB_NOMATCH for '%s', "
3088 "handling as literal path", target);
3089
3090 /* Trick the following code into using the non-glob() processed path.
3091 */
3092 res = 0;
3093 g.gl_pathv = (char **) pcalloc(cmd->tmp_pool, 2 * sizeof(char *));
3094 g.gl_pathv[0] = (char *) pstrdup(cmd->tmp_pool, target);
3095 g.gl_pathv[1] = NULL;
3096
3097 } else {
3098 if (list_flags & LS_FL_NO_ERROR_IF_ABSENT) {
3099 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3100 int xerrno = errno;
3101
3102 pr_cmd_set_errno(cmd, xerrno);
3103 errno = xerrno;
3104 return PR_ERROR(cmd);
3105 }
3106
3107 session.sf_flags |= SF_ASCII_OVERRIDE;
3108 pr_response_add(R_226, _("Transfer complete"));
3109 ls_done(cmd);
3110
3111 return PR_HANDLED(cmd);
3112 }
3113
3114 pr_response_add_err(R_450, _("No files found"));
3115
3116 pr_cmd_set_errno(cmd, ENOENT);
3117 errno = ENOENT;
3118 return PR_ERROR(cmd);
3119 }
3120
3121 } else {
3122 if (list_flags & LS_FL_NO_ERROR_IF_ABSENT) {
3123 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3124 int xerrno = errno;
3125
3126 pr_cmd_set_errno(cmd, xerrno);
3127 errno = xerrno;
3128 return PR_ERROR(cmd);
3129 }
3130
3131 session.sf_flags |= SF_ASCII_OVERRIDE;
3132 pr_response_add(R_226, _("Transfer complete"));
3133 ls_done(cmd);
3134
3135 return PR_HANDLED(cmd);
3136 }
3137
3138 pr_response_add_err(R_450, _("No files found"));
3139
3140 pr_cmd_set_errno(cmd, ENOENT);
3141 errno = ENOENT;
3142 return PR_ERROR(cmd);
3143 }
3144 }
3145
3146 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3147 int xerrno = errno;
3148
3149 pr_cmd_set_errno(cmd, xerrno);
3150 errno = xerrno;
3151 return PR_ERROR(cmd);
3152 }
3153
3154 session.sf_flags |= SF_ASCII_OVERRIDE;
3155
3156 /* Iterate through each matching entry */
3157 path = g.gl_pathv;
3158 while (path && *path && res >= 0) {
3159 struct stat st;
3160
3161 pr_signals_handle();
3162
3163 p = *path;
3164 path++;
3165
3166 if (*p == '.' && (!opt_A || is_dotdir(p))) {
3167 continue;
3168 }
3169
3170 pr_fs_clear_cache2(p);
3171 if (pr_fsio_stat(p, &st) == 0) {
3172 /* If it's a directory... */
3173 if (S_ISDIR(st.st_mode)) {
3174 if (opt_R) {
3175 /* ...and we are recursing, hand off to nlstdir()...*/
3176 res = nlstdir(cmd, p);
3177
3178 } else {
3179 /*...otherwise, just list the name. */
3180 res = nlstfile(cmd, p);
3181 }
3182
3183 } else if (S_ISREG(st.st_mode) &&
3184 ls_perms(cmd->tmp_pool, cmd, p, &hidden)) {
3185 /* Don't display hidden files */
3186 if (hidden) {
3187 continue;
3188 }
3189
3190 res = nlstfile(cmd, p);
3191 }
3192 }
3193 }
3194
3195 sendline(LS_SENDLINE_FL_FLUSH, " ");
3196 if (globbed) {
3197 pr_fs_globfree(&g);
3198 }
3199
3200 } else {
3201 /* A single target. If it's a directory, list the contents; if it's a
3202 * file, just list the file.
3203 */
3204 struct stat st;
3205
3206 if (!is_dotdir(target)) {
3207 /* Clean the path. */
3208 if (*target != '/') {
3209 pr_fs_clean_path2(target, buf, sizeof(buf), 0);
3210
3211 } else {
3212 pr_fs_clean_path(target, buf, sizeof(buf));
3213 }
3214
3215 target = buf;
3216
3217 } else {
3218 /* Remove any trailing separators. */
3219 targetlen = strlen(target);
3220 while (targetlen >= 1 &&
3221 target[targetlen-1] == '/') {
3222 if (strncmp(target, "/", 2) == 0) {
3223 break;
3224 }
3225
3226 target[targetlen-1] = '\0';
3227 targetlen = strlen(target);
3228 }
3229 }
3230
3231 if (!ls_perms_full(cmd->tmp_pool, cmd, target, &hidden)) {
3232 int xerrno = errno;
3233
3234 if (xerrno == ENOENT &&
3235 (list_flags & LS_FL_NO_ERROR_IF_ABSENT)) {
3236 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3237 xerrno = errno;
3238
3239 pr_cmd_set_errno(cmd, xerrno);
3240 errno = xerrno;
3241 return PR_ERROR(cmd);
3242 }
3243 session.sf_flags |= SF_ASCII_OVERRIDE;
3244 pr_response_add(R_226, _("Transfer complete"));
3245 ls_done(cmd);
3246
3247 return PR_HANDLED(cmd);
3248 }
3249
3250 pr_response_add_err(R_450, "%s: %s", *cmd->arg ? cmd->arg :
3251 pr_fs_encode_path(cmd->tmp_pool, session.vwd), strerror(xerrno));
3252
3253 pr_cmd_set_errno(cmd, xerrno);
3254 errno = xerrno;
3255 return PR_ERROR(cmd);
3256 }
3257
3258 /* Don't display hidden files */
3259 if (hidden) {
3260 c = find_ls_limit(target);
3261 if (c) {
3262 unsigned char *ignore_hidden;
3263 int xerrno;
3264
3265 ignore_hidden = get_param_ptr(c->subset, "IgnoreHidden", FALSE);
3266 if (ignore_hidden &&
3267 *ignore_hidden == TRUE) {
3268
3269 if (list_flags & LS_FL_NO_ERROR_IF_ABSENT) {
3270 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3271 xerrno = errno;
3272
3273 pr_cmd_set_errno(cmd, xerrno);
3274 errno = xerrno;
3275 return PR_ERROR(cmd);
3276 }
3277 session.sf_flags |= SF_ASCII_OVERRIDE;
3278 pr_response_add(R_226, _("Transfer complete"));
3279 ls_done(cmd);
3280
3281 return PR_HANDLED(cmd);
3282 }
3283
3284 xerrno = ENOENT;
3285
3286 } else {
3287 xerrno = EACCES;
3288 }
3289
3290 pr_response_add_err(R_450, "%s: %s",
3291 pr_fs_encode_path(cmd->tmp_pool, target), strerror(xerrno));
3292
3293 pr_cmd_set_errno(cmd, xerrno);
3294 errno = xerrno;
3295 return PR_ERROR(cmd);
3296 }
3297 }
3298
3299 /* Make sure the target is a file or directory, and that we have access
3300 * to it.
3301 */
3302 pr_fs_clear_cache2(target);
3303 if (pr_fsio_stat(target, &st) < 0) {
3304 int xerrno = errno;
3305
3306 if (xerrno == ENOENT &&
3307 (list_flags & LS_FL_NO_ERROR_IF_ABSENT)) {
3308 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3309 xerrno = errno;
3310
3311 pr_cmd_set_errno(cmd, xerrno);
3312 errno = xerrno;
3313 return PR_ERROR(cmd);
3314 }
3315 session.sf_flags |= SF_ASCII_OVERRIDE;
3316 pr_response_add(R_226, _("Transfer complete"));
3317 ls_done(cmd);
3318
3319 return PR_HANDLED(cmd);
3320 }
3321
3322 pr_response_add_err(R_450, "%s: %s", cmd->arg, strerror(xerrno));
3323
3324 pr_cmd_set_errno(cmd, xerrno);
3325 errno = xerrno;
3326 return PR_ERROR(cmd);
3327 }
3328
3329 if (S_ISREG(st.st_mode)) {
3330 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3331 int xerrno = errno;
3332
3333 pr_cmd_set_errno(cmd, xerrno);
3334 errno = xerrno;
3335 return PR_ERROR(cmd);
3336 }
3337 session.sf_flags |= SF_ASCII_OVERRIDE;
3338
3339 res = nlstfile(cmd, target);
3340
3341 } else if (S_ISDIR(st.st_mode)) {
3342 if (pr_data_open(NULL, "file list", PR_NETIO_IO_WR, 0) < 0) {
3343 int xerrno = errno;
3344
3345 pr_cmd_set_errno(cmd, xerrno);
3346 errno = xerrno;
3347 return PR_ERROR(cmd);
3348 }
3349 session.sf_flags |= SF_ASCII_OVERRIDE;
3350
3351 res = nlstdir(cmd, target);
3352
3353 } else {
3354 pr_response_add_err(R_450, _("%s: Not a regular file"), cmd->arg);
3355
3356 pr_cmd_set_errno(cmd, EPERM);
3357 errno = EPERM;
3358 return PR_ERROR(cmd);
3359 }
3360
3361 sendline(LS_SENDLINE_FL_FLUSH, " ");
3362 }
3363
3364 if (XFER_ABORTED) {
3365 pr_data_abort(0, 0);
3366 res = -1;
3367
3368 } else {
3369 /* Note that the data connection is NOT cleared here, as an error in
3370 * NLST still leaves data ready for another command.
3371 */
3372 ls_done(cmd);
3373 }
3374
3375 return (res < 0 ? PR_ERROR(cmd) : PR_HANDLED(cmd));
3376 }
3377
3378 /* Check for the UseGlobbing setting, if any, after the PASS command has
3379 * been successfully handled.
3380 */
ls_post_pass(cmd_rec * cmd)3381 MODRET ls_post_pass(cmd_rec *cmd) {
3382 unsigned char *globbing = NULL;
3383
3384 globbing = get_param_ptr(TOPLEVEL_CONF, "UseGlobbing", FALSE);
3385 if (globbing != NULL &&
3386 *globbing == FALSE) {
3387 pr_log_debug(DEBUG3, "UseGlobbing: disabling globbing functionality");
3388 use_globbing = FALSE;
3389 }
3390
3391 return PR_DECLINED(cmd);
3392 }
3393
3394 /* Configuration handlers
3395 */
3396
set_dirfakeusergroup(cmd_rec * cmd)3397 MODRET set_dirfakeusergroup(cmd_rec *cmd) {
3398 int bool = -1;
3399 char *as = "ftp";
3400 config_rec *c = NULL;
3401
3402 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_ANON|CONF_GLOBAL|
3403 CONF_DIR|CONF_DYNDIR);
3404
3405 if (cmd->argc < 2 ||
3406 cmd->argc > 3) {
3407 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "syntax: ", (char *) cmd->argv[0],
3408 " on|off [<id to display>]", NULL));
3409 }
3410
3411 bool = get_boolean(cmd, 1);
3412 if (bool == -1) {
3413 CONF_ERROR(cmd, "expected boolean argument");
3414 }
3415
3416 if (bool == TRUE) {
3417 /* Use the configured ID to display rather than the default "ftp". */
3418 if (cmd->argc > 2) {
3419 as = cmd->argv[2];
3420 }
3421
3422 c = add_config_param_str(cmd->argv[0], 1, as);
3423
3424 } else {
3425 /* Still need to add a config_rec to turn off the display of fake IDs. */
3426 c = add_config_param_str(cmd->argv[0], 0);
3427 }
3428
3429 c->flags |= CF_MERGEDOWN;
3430 return PR_HANDLED(cmd);
3431 }
3432
set_dirfakemode(cmd_rec * cmd)3433 MODRET set_dirfakemode(cmd_rec *cmd) {
3434 config_rec *c = NULL;
3435 char *endp = NULL;
3436 mode_t fake_mode;
3437
3438 CHECK_ARGS(cmd, 1);
3439 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR|
3440 CONF_DYNDIR);
3441
3442 fake_mode = (mode_t) strtol(cmd->argv[1], &endp, 8);
3443
3444 if (endp && *endp)
3445 CONF_ERROR(cmd, "parameter must be an octal number");
3446
3447 c = add_config_param(cmd->argv[0], 1, NULL);
3448 c->argv[0] = pcalloc(c->pool, sizeof(mode_t));
3449 *((mode_t *) c->argv[0]) = fake_mode;
3450 c->flags |= CF_MERGEDOWN;
3451
3452 return PR_HANDLED(cmd);
3453 }
3454
set_listoptions(cmd_rec * cmd)3455 MODRET set_listoptions(cmd_rec *cmd) {
3456 config_rec *c = NULL;
3457 unsigned long flags = 0;
3458
3459 if (cmd->argc-1 < 1) {
3460 CONF_ERROR(cmd, "wrong number of parameters");
3461 }
3462
3463 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|
3464 CONF_DIR|CONF_DYNDIR);
3465
3466 c = add_config_param(cmd->argv[0], 6, NULL, NULL, NULL, NULL, NULL, NULL);
3467 c->flags |= CF_MERGEDOWN;
3468
3469 c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
3470
3471 /* The default "strict" setting. */
3472 c->argv[1] = pcalloc(c->pool, sizeof(unsigned char));
3473 *((unsigned char *) c->argv[1]) = FALSE;
3474
3475 /* The default "maxdepth" setting. */
3476 c->argv[2] = pcalloc(c->pool, sizeof(unsigned int));
3477 *((unsigned int *) c->argv[2]) = 0;
3478
3479 /* The default "maxfiles" setting. */
3480 c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
3481 *((unsigned int *) c->argv[3]) = 0;
3482
3483 /* The default "maxdirs" setting. */
3484 c->argv[4] = pcalloc(c->pool, sizeof(unsigned int));
3485 *((unsigned int *) c->argv[4]) = 0;
3486
3487 /* The default flags */
3488 c->argv[5] = pcalloc(c->pool, sizeof(unsigned long));
3489
3490 /* Check for, and handle, optional arguments. */
3491 if (cmd->argc-1 >= 2) {
3492 register unsigned int i = 0;
3493
3494 for (i = 2; i < cmd->argc; i++) {
3495
3496 if (strcasecmp(cmd->argv[i], "strict") == 0) {
3497 *((unsigned int *) c->argv[1]) = TRUE;
3498
3499 } else if (strcasecmp(cmd->argv[i], "maxdepth") == 0) {
3500 int maxdepth = atoi(cmd->argv[++i]);
3501
3502 if (maxdepth < 1) {
3503 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
3504 ": maxdepth must be greater than 0: '", cmd->argv[i],
3505 "'", NULL));
3506 }
3507
3508 *((unsigned int *) c->argv[2]) = maxdepth;
3509
3510 } else if (strcasecmp(cmd->argv[i], "maxfiles") == 0) {
3511 int maxfiles = atoi(cmd->argv[++i]);
3512
3513 if (maxfiles < 1) {
3514 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
3515 ": maxfiles must be greater than 0: '", (char *) cmd->argv[i],
3516 "'", NULL));
3517 }
3518
3519 *((unsigned int *) c->argv[3]) = maxfiles;
3520
3521 } else if (strcasecmp(cmd->argv[i], "maxdirs") == 0) {
3522 int maxdirs = atoi(cmd->argv[++i]);
3523
3524 if (maxdirs < 1) {
3525 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
3526 ": maxdirs must be greater than 0: '", (char *) cmd->argv[i],
3527 "'", NULL));
3528 }
3529
3530 *((unsigned int *) c->argv[4]) = maxdirs;
3531
3532 } else if (strcasecmp(cmd->argv[i], "LISTOnly") == 0) {
3533 flags |= LS_FL_LIST_ONLY;
3534
3535 } else if (strcasecmp(cmd->argv[i], "NLSTOnly") == 0) {
3536 flags |= LS_FL_NLST_ONLY;
3537
3538 } else if (strcasecmp(cmd->argv[i], "NoErrorIfAbsent") == 0) {
3539 flags |= LS_FL_NO_ERROR_IF_ABSENT;
3540
3541 } else if (strcasecmp(cmd->argv[i], "AdjustedSymlinks") == 0) {
3542 flags |= LS_FL_ADJUSTED_SYMLINKS;
3543
3544 } else if (strcasecmp(cmd->argv[i], "NoAdjustedSymlinks") == 0) {
3545 /* Ignored, for backward compatibility. */
3546
3547 } else if (strcasecmp(cmd->argv[i], "SortedNLST") == 0) {
3548 flags |= LS_FL_SORTED_NLST;
3549
3550 } else {
3551 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown keyword: '",
3552 (char *) cmd->argv[i], "'", NULL));
3553 }
3554 }
3555 }
3556
3557 *((unsigned long *) c->argv[5]) = flags;
3558 return PR_HANDLED(cmd);
3559 }
3560
set_showsymlinks(cmd_rec * cmd)3561 MODRET set_showsymlinks(cmd_rec *cmd) {
3562 int bool = -1;
3563 config_rec *c = NULL;
3564
3565 CHECK_ARGS(cmd, 1);
3566 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
3567
3568 if ((bool = get_boolean(cmd, 1)) == -1)
3569 CONF_ERROR(cmd, "expected Boolean parameter");
3570
3571 c = add_config_param(cmd->argv[0], 1, NULL);
3572 c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3573 *((unsigned char *) c->argv[0]) = bool;
3574 c->flags |= CF_MERGEDOWN;
3575
3576 return PR_HANDLED(cmd);
3577 }
3578
set_useglobbing(cmd_rec * cmd)3579 MODRET set_useglobbing(cmd_rec *cmd) {
3580 int bool = -1;
3581 config_rec *c = NULL;
3582
3583 CHECK_ARGS(cmd, 1);
3584 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
3585
3586 bool = get_boolean(cmd, 1);
3587 if (bool == -1) {
3588 CONF_ERROR(cmd, "expected Boolean parameter");
3589 }
3590
3591 c = add_config_param(cmd->argv[0], 1, NULL);
3592 c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
3593 *((unsigned char *) c->argv[0]) = bool;
3594 c->flags |= CF_MERGEDOWN;
3595
3596 return PR_HANDLED(cmd);
3597 }
3598
3599 /* Initialization routines
3600 */
3601
ls_init(void)3602 static int ls_init(void) {
3603
3604 /* Add the commands handled by this module to the HELP list. */
3605 pr_help_add(C_LIST, _("[<sp> pathname]"), TRUE);
3606 pr_help_add(C_NLST, _("[<sp> (pathname)]"), TRUE);
3607 pr_help_add(C_STAT, _("[<sp> pathname]"), TRUE);
3608
3609 return 0;
3610 }
3611
3612 /* Module API tables
3613 */
3614
3615 static conftable ls_conftab[] = {
3616 { "DirFakeUser", set_dirfakeusergroup, NULL },
3617 { "DirFakeGroup", set_dirfakeusergroup, NULL },
3618 { "DirFakeMode", set_dirfakemode, NULL },
3619 { "ListOptions", set_listoptions, NULL },
3620 { "ShowSymlinks", set_showsymlinks, NULL },
3621 { "UseGlobbing", set_useglobbing, NULL },
3622 { NULL, NULL, NULL }
3623 };
3624
3625 static cmdtable ls_cmdtab[] = {
3626 { CMD, C_NLST, G_DIRS, ls_nlst, TRUE, FALSE, CL_DIRS },
3627 { CMD, C_LIST, G_DIRS, ls_list, TRUE, FALSE, CL_DIRS },
3628 { CMD, C_STAT, G_DIRS, ls_stat, TRUE, FALSE, CL_INFO },
3629 { POST_CMD, C_PASS, G_NONE, ls_post_pass, FALSE, FALSE },
3630 { LOG_CMD, C_LIST, G_NONE, ls_log_nlst, FALSE, FALSE },
3631 { LOG_CMD, C_NLST, G_NONE, ls_log_nlst, FALSE, FALSE },
3632 { LOG_CMD_ERR,C_LIST, G_NONE, ls_err_nlst, FALSE, FALSE },
3633 { LOG_CMD_ERR,C_NLST, G_NONE, ls_err_nlst, FALSE, FALSE },
3634 { 0, NULL }
3635 };
3636
3637 module ls_module = {
3638 NULL, NULL,
3639
3640 /* Module API version */
3641 0x20,
3642
3643 /* Module name */
3644 "ls",
3645
3646 /* Module configuration handler table */
3647 ls_conftab,
3648
3649 /* Module command handler table */
3650 ls_cmdtab,
3651
3652 /* Module authentication handler table */
3653 NULL,
3654
3655 /* Module initialization */
3656 ls_init,
3657
3658 /* Session initialization */
3659 NULL
3660 };
3661