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