1 /*
2  * list.c
3  *
4  * (C)1998-2011 by Marc Huber <Marc.Huber@web.de>
5  * All rights reserved.
6  *
7  * $Id: list.c,v 1.22 2015/03/14 06:11:26 marc Exp marc $
8  *
9  */
10 
11 #include "headers.h"
12 #include "glob.h"
13 #include "misc/rb.h"
14 #include "misc/tokenize.h"
15 #include <pwd.h>
16 #include <grp.h>
17 
18 static const char rcsid[] __attribute__ ((used)) = "$Id: list.c,v 1.22 2015/03/14 06:11:26 marc Exp marc $";
19 
20 static int list_dir(struct context *, char *, char *);
21 static void list_dir_details(struct context *, int);
22 
23 #define list_basename(A) ((strlen (A) <= ctx->rootlen) ? "/" : basename (A))
24 
25 rb_tree_t *mimetypes = NULL;
26 
27 struct mime_type {
28     char *extension;
29     char mimetype[1];
30 };
31 
compare_extension(const void * a,const void * b)32 static int compare_extension(const void *a, const void *b)
33 {
34     return strcasecmp(((struct mime_type *) a)->extension, ((struct mime_type *) b)->extension);
35 }
36 
free_payload(void * payload)37 static void free_payload(void *payload)
38 {
39     free(payload);
40 }
41 
lookup_mimetype(char * name)42 static char *lookup_mimetype(char *name)
43 {
44     char *e = strrchr(name, '.');
45     if (e) {
46 	rb_node_t *rb;
47 	static struct mime_type *mt_last = NULL;
48 	struct mime_type mt;
49 
50 	if (mt_last && !strcasecmp(e, mt_last->extension))
51 	    return mt_last->mimetype;
52 
53 	mt.extension = e + 1;
54 	if ((rb = RB_search(mimetypes, &mt))) {
55 	    mt_last = RB_payload(rb, struct mime_type *);
56 	    return mt_last->mimetype;
57 	}
58     }
59     return NULL;
60 }
61 
read_mimetypes(char * file)62 void read_mimetypes(char *file)
63 {
64     char inbuf[8000];
65     size_t offset = 0;
66     ssize_t inlength;
67     char *linestart = inbuf;
68     char *lineend;
69 
70     int fn = open(file, O_RDONLY);
71     if (fn < 0) {
72 	logerr("open (%s)", file);
73 	return;
74     }
75 
76     while ((inlength = read(fn, inbuf + offset, sizeof(inbuf) - 1 - offset)) > 0) {
77 	inlength += offset;
78 	inbuf[inlength] = 0;
79 	linestart = inbuf;
80 
81 	while ((lineend = strchr(linestart, '\n'))) {
82 #define VECTOR_SIZE 99
83 	    char *vector[VECTOR_SIZE];
84 	    char **a = vector;
85 	    *lineend = 0;
86 
87 	    tokenize(linestart, vector, VECTOR_SIZE);
88 
89 	    if (*a && **a != '#' && strchr(*a, '/')) {
90 		char *mt = *a++;
91 		ssize_t mtlen = strlen(mt);
92 		for (; *a; a++) {
93 		    struct mime_type *keyval;
94 		    if (!mimetypes)
95 			mimetypes = RB_tree_new(compare_extension, free_payload);
96 		    keyval = Xcalloc(1, sizeof(struct mime_type) + 1 + strlen(*a) + mtlen);
97 		    strcpy(keyval->mimetype, mt);
98 		    keyval->extension = keyval->mimetype + mtlen + 1;
99 		    strcpy(keyval->extension, *a);
100 		    RB_search_and_delete(mimetypes, keyval);
101 		    RB_insert(mimetypes, keyval);
102 		}
103 	    }
104 	    linestart = lineend + 1;
105 	}
106 
107 	offset = inbuf + inlength - linestart;
108 	if (offset)
109 	    memmove(inbuf, linestart, offset);
110     }
111     close(fn);
112 }
113 
check_gids(struct context * ctx,gid_t gid)114 int check_gids(struct context *ctx, gid_t gid)
115 {
116     int i;
117     for (i = 0; i < ctx->gids_size; i++)
118 	if (gid == ctx->gids[i])
119 	    return -1;
120     return 0;
121 }
122 
123 struct id_item {
124     int id;
125     char name[1];
126 };
127 
compare_id(const void * a,const void * b)128 static int compare_id(const void *a, const void *b)
129 {
130     return (((struct id_item *) a)->id - ((struct id_item *) b)->id);
131 }
132 
lookup_uid(struct context * ctx,uid_t uid)133 char *lookup_uid(struct context *ctx, uid_t uid)
134 {
135     struct passwd *pw;
136     char *u = NULL;
137     static rb_tree_t *cache = NULL;
138     static uid_t last_uid;
139     static char *last_user = NULL;
140     rb_node_t *t;
141     struct id_item idi, *i;
142 
143     if (!ctx->resolve_ids)
144 	return ctx->ftpuser;
145 
146     if (!cache) {
147 	cache = RB_tree_new(compare_id, free_payload);
148 	setpwent();
149 	while ((pw = getpwent()))
150 	    if (pw->pw_name) {
151 		i = Xcalloc(1, sizeof(struct id_item) + strlen(pw->pw_name));
152 		i->id = pw->pw_uid;
153 		strcpy(i->name, pw->pw_name);
154 		RB_insert(cache, i);
155 	    }
156 	endpwent();
157     }
158 
159     if (uid == last_uid && last_user)
160 	return last_user;
161 
162     idi.id = uid;
163 
164     if ((t = RB_search(cache, &idi)))
165 	u = RB_payload(t, struct id_item *)->name;
166     else {
167 	pw = getpwuid(uid);
168 	if (pw && pw->pw_name)
169 	    u = pw->pw_name;
170 	if (!u)
171 	    u = ctx->ftpuser;
172 
173 	i = Xcalloc(1, sizeof(struct id_item) + strlen(u));
174 	i->id = uid;
175 	strcpy(i->name, u);
176 	RB_insert(cache, i);
177     }
178     last_user = u, last_uid = uid;
179     return u;
180 }
181 
lookup_gid(struct context * ctx,gid_t gid)182 char *lookup_gid(struct context *ctx, gid_t gid)
183 {
184     struct group *gr;
185     char *g = NULL;
186     static rb_tree_t *cache = NULL;
187     static uid_t last_gid;
188     static char *last_group = NULL;
189     rb_node_t *t;
190     struct id_item idi, *i;
191 
192     if (!ctx->resolve_ids)
193 	return ctx->ftpgroup;
194 
195     if (!cache) {
196 	cache = RB_tree_new(compare_id, free_payload);
197 	setgrent();
198 	while ((gr = getgrent()))
199 	    if (gr->gr_name) {
200 		i = Xcalloc(1, sizeof(struct id_item) + strlen(gr->gr_name));
201 		i->id = gr->gr_gid;
202 		strcpy(i->name, gr->gr_name);
203 		RB_insert(cache, i);
204 	    }
205 	endgrent();
206     }
207 
208     if (gid == last_gid && last_group)
209 	return last_group;
210 
211     idi.id = gid;
212 
213     if ((t = RB_search(cache, &idi)))
214 	g = RB_payload(t, struct id_item *)->name;
215     else {
216 	gr = getgrgid(gid);
217 	if (gr && gr->gr_name)
218 	    g = gr->gr_name;
219 	if (!g)
220 	    g = ctx->ftpgroup;
221 
222 	i = Xcalloc(1, sizeof(struct id_item) + strlen(g));
223 	i->id = gid;
224 	strcpy(i->name, g);
225 	RB_insert(cache, i);
226     }
227     last_group = g, last_gid = gid;
228     return g;
229 }
230 
231 static char *permtable = NULL;
232 
init_permtable(void)233 static char *init_permtable(void)
234 {
235     int i;
236     char *pt[] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" };
237     char *p;
238 
239     permtable = Xcalloc(5120, 1);
240 
241     for (p = permtable, i = 0; i < 512; i++, p += 10)
242 	sprintf(p, "%s%s%s", pt[i >> 6], pt[7 & (i >> 3)], pt[7 & i]);
243 
244     return permtable;
245 }
246 
list_one(struct context * ctx,char * filename,enum list_mode mode,char * buffer,size_t buflen)247 static char *list_one(struct context *ctx, char *filename, enum list_mode mode, char *buffer, size_t buflen)
248 {
249     int l;
250     struct stat st;
251     char *t = buffer;
252 
253     Debug((DEBUG_PROC, "+ %s(\"%s\", ...\n", __func__, filename));
254 
255     if (pickystat(ctx, &st, filename)) {
256 	DebugOut(DEBUG_PROC);
257 	return NULL;
258     }
259 
260     buffer[0] = 0;
261 
262     switch (mode) {
263     case List_list:
264 	if (!permtable)
265 	    init_permtable();
266 
267 	l = snprintf(buffer, buflen,
268 		     "%c%s %4lu %-8s %-8s %8llu ",
269 		     S_ISDIR(st.st_mode) ? 'd' : '-',
270 		     permtable + 10 * (st.st_mode & 0777),
271 		     (u_long) st.st_nlink, lookup_uid(ctx, st.st_uid), lookup_gid(ctx, st.st_gid), (unsigned long long) st.st_size);
272 	strftime(buffer + l, buflen - l, (st.st_mtime + 15552000 < io_now.tv_sec)
273 		 ? "%b %e  %Y " : "%b %e %H:%M ", localtime(&st.st_mtime));
274 	break;
275     case List_mlsd:
276 	if (!(ctx->mlst_facts & MLST_fact_type) && filename[0] == '.' && (!filename[1] || (filename[1] == '.' && !filename[2])))
277 	    break;
278     case List_mlst:
279 	if (ctx->mlst_facts & MLST_fact_type) {
280 	    *t++ = 'T';
281 	    *t++ = 'y';
282 	    *t++ = 'p';
283 	    *t++ = 'e';
284 	    *t++ = '=';
285 	    if (S_ISDIR(st.st_mode)) {
286 		if (filename[0] == '.') {
287 		    if (filename[1] == '.' && !filename[2])
288 			*t++ = 'p';
289 		    else if (!filename[1])
290 			*t++ = 'c';
291 		}
292 		*t++ = 'd';
293 		*t++ = 'i';
294 		*t++ = 'r';
295 	    } else {
296 		*t++ = 'f';
297 		*t++ = 'i';
298 		*t++ = 'l';
299 		*t++ = 'e';
300 	    }
301 	    *t++ = ';';
302 	}
303 
304 	if (ctx->mlst_facts & MLST_fact_size && !S_ISDIR(st.st_mode))
305 	    t += snprintf(t, (size_t) (buffer + buflen - t), "Size=%llu;", (unsigned long long) st.st_size);
306 
307 	if (ctx->mlst_facts & MLST_fact_modify)
308 	    t += strftime(t, 30, "Modify=%Y%m%d%H%M%S;", gmtime(&st.st_mtime));
309 
310 	if (ctx->mlst_facts & MLST_fact_change)
311 	    t += strftime(t, 30, "Change=%Y%m%d%H%M%S;", gmtime(&st.st_ctime));
312 
313 	if (ctx->mlst_facts & MLST_fact_unique) {
314 	    char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789+/";
315 
316 	    u_long l1 = (u_long) st.st_dev;
317 	    u_long l2 = (u_long) st.st_ino;
318 
319 	    *t++ = 'U';
320 	    *t++ = 'n';
321 	    *t++ = 'i';
322 	    *t++ = 'q';
323 	    *t++ = 'u';
324 	    *t++ = 'e';
325 	    *t++ = '=';
326 
327 	    do
328 		*t++ = table[l1 & 0x3F];
329 	    while (l1 >>= 6);
330 	    *t++ = '.';
331 
332 	    do
333 		*t++ = table[l2 & 0x3F];
334 	    while (l2 >>= 6);
335 	    *t++ = ';';
336 	}
337 
338 	if (ctx->mlst_facts & MLST_fact_perm) {
339 	    *t++ = 'P';
340 	    *t++ = 'e';
341 	    *t++ = 'r';
342 	    *t++ = 'm';
343 	    *t++ = '=';
344 
345 	    if (!S_ISDIR(st.st_mode) && !ctx->anonymous && (ctx->uid == st.st_uid) && (S_IWUSR & st.st_mode))
346 		*t++ = 'a';	/* APPE may be applied */
347 
348 	    if (S_ISDIR(st.st_mode)) {
349 		if (ctx->anonymous) {
350 		    if (check_incoming(ctx, filename, 077)) {
351 			*t++ = 'c';	/* file creation should succeed */
352 			*t++ = 'm';	/* directory creation should succeed */
353 		    }
354 		} else {
355 		    if ((ctx->uid == st.st_uid) && (S_IWUSR & st.st_mode)) {
356 			*t++ = 'c';	/* file creation should succeed */
357 			*t++ = 'm';	/* directory creation should succeed */
358 			*t++ = 'p';	/* directory contents may be removed */
359 		    }
360 		}
361 	    }
362 
363 	    if (!ctx->anonymous && ctx->uid == st.st_uid &&
364 		(!ctx->pst_valid ||
365 		 (ctx->pst.st_mode & S_IWUSR) || (ctx->pst.st_mode & S_IWGRP && check_gids(ctx, ctx->pst.st_gid)) || (ctx->pst.st_mode & S_IWOTH))) {
366 		*t++ = 'd';	/* rename should succeed */
367 		*t++ = 'f';	/* delete should succeed */
368 	    }
369 
370 	    if (S_ISDIR(st.st_mode) && ((ctx->uid == st.st_uid) || (S_IXGRP & st.st_mode && check_gids(ctx, st.st_gid)) || (S_IXOTH & st.st_mode))) {
371 		*t++ = 'e';	/* cwd should succeed */
372 		*t++ = 'l';	/* list command may be applied */
373 	    }
374 
375 	    if (!S_ISDIR(st.st_mode) && ((ctx->uid == st.st_uid) || (S_IRGRP & st.st_mode && check_gids(ctx, st.st_gid)) || (S_IROTH & st.st_mode)))
376 		*t++ = 'r';	/* RETR command may be applied */
377 
378 	    if (!S_ISDIR(st.st_mode) && !ctx->anonymous)
379 		*t++ = 'w';	/* STOR command may be applied */
380 
381 	    *t++ = ';';
382 	}
383 
384 	if (!S_ISDIR(st.st_mode) && (ctx->mlst_facts & MLST_fact_mediatype && mimetypes)) {
385 	    char *mt = lookup_mimetype(filename);
386 	    if (mt)
387 		t += snprintf(t, (size_t) (buffer + buflen - t), "Media-Type=%s;", mt);
388 	}
389 
390 	if (ctx->mlst_facts & MLST_fact_UNIX_mode)
391 	    t += snprintf(t, (size_t) (buffer + buflen - t), "UNIX.mode=%o;", 0777 & (u_int) st.st_mode);
392 
393 	if (ctx->mlst_facts & MLST_fact_UNIX_owner)
394 	    t += snprintf(t, (size_t) (buffer + buflen - t), "UNIX.owner=%s;", lookup_uid(ctx, st.st_uid));
395 
396 	if (ctx->mlst_facts & MLST_fact_UNIX_group)
397 	    t += snprintf(t, (size_t) (buffer + buflen - t), "UNIX.group=%s;", lookup_gid(ctx, st.st_gid));
398 	*t++ = ' ';
399 	*t = 0;
400 
401 	break;
402     case List_nlst:
403 	if (nlst_files_only && !S_ISREG(st.st_mode))
404 	    return NULL;
405     default:
406 	buffer[0] = 0;
407     }
408 
409     DebugOut(DEBUG_PROC);
410     return buffer;
411 }
412 
list_stat(struct context * ctx,char * path)413 void list_stat(struct context *ctx, char *path)
414 {
415     char *t, *u;
416 
417     DebugIn(DEBUG_PROC);
418 
419     ctx->list_to_cc = 1;
420     ctx->list_mode = List_list;
421     ctx->stat_reply = MSG_550_No_such_file_or_directory;
422 
423     if ((t = buildpath(ctx, (path && *path) ? path : "."))) {
424 	char buffer[1024];
425 	if (!list_dir(ctx, t, NULL)) {
426 	    replyf(ctx, MSG_212_status_of, path);
427 	    ctx->stat_reply = MSG_212_status_end;
428 	    io_sched_add(ctx->io, ctx, (void *) list_dir_details, 0, 0);
429 	    io_clr_o(ctx->io, ctx->cfn);
430 	} else if ((u = list_one(ctx, t, List_list, buffer, sizeof(buffer)))) {
431 	    replyf(ctx, MSG_213_status_of, path);
432 	    ctx->stat_reply = MSG_213_status_end;
433 	    replyf(ctx, "%s%s\r\n", u, list_basename(t));
434 	} else if (path && !strchr(path, '/')
435 		   && !list_dir(ctx, ctx->cwd, path)) {
436 	    replyf(ctx, MSG_212_status_of, path);
437 	    ctx->stat_reply = MSG_212_status_end;
438 	    io_sched_add(ctx->io, ctx, (void *) list_dir_details, 0, 0);
439 	    io_clr_o(ctx->io, ctx->cfn);
440 	}
441     }
442     DebugOut(DEBUG_PROC);
443 }
444 
h_mlst(struct context * ctx,char * path)445 void h_mlst(struct context *ctx, char *path)
446 {
447     char *t, *u, buffer[1024];
448 
449     DebugIn(DEBUG_PROC);
450 
451     t = buildpath(ctx, (path && *path) ? path : ".");
452 
453     if (t) {
454 	char *v = strrchr(t, '/');
455 
456 	ctx->pst_valid = 0;
457 
458 	if (v) {
459 	    *v = 0;
460 	    if (!pickystat(ctx, &ctx->pst, t))
461 		ctx->pst_valid = 1;
462 	}
463 
464 	if ((u = list_one(ctx, t, List_mlst, buffer, sizeof(buffer)))) {
465 	    replyf(ctx, MSG_250_listing_start, t[ctx->rootlen] ? t + ctx->rootlen : "/");
466 	    replyf(ctx, " %s%s\r\n", u, t[ctx->rootlen] ? t + ctx->rootlen : "/");
467 	    reply(ctx, MSG_250_listing_end);
468 	}
469 
470 	ctx->pst_valid = 0;
471 
472     } else
473 	reply(ctx, MSG_501_No_such_file_or_directory);
474 
475     DebugOut(DEBUG_PROC);
476 }
477 
list(struct context * ctx,char * path,enum list_mode mode)478 void list(struct context *ctx, char *path, enum list_mode mode)
479 {
480     char *t, *u;
481 
482     DebugIn(DEBUG_PROC);
483 
484     ctx->list_to_cc = 0;
485     ctx->buffer_filled = 1;
486     ctx->list_mode = mode;
487 
488     if ((t = buildpath(ctx, (path && *path) ? path : ".")))
489 	switch (list_dir(ctx, t, NULL)) {
490 	case 0:
491 	    io_sched_add(ctx->io, ctx, (void *) list_dir_details, 0, 0);
492 	    io_clr_o(ctx->io, ctx->cfn);
493 	case EPERM:
494 	    break;
495 	default:
496 	    if (mode != List_mlsd) {
497 		char buffer[1024];
498 
499 		/* t is a single file */
500 		if ((u = list_one(ctx, t, mode, buffer, sizeof(buffer)))) {
501 		    if (mode != List_nlst)
502 			ctx->dbufi = buffer_write(ctx->dbufi, u, strlen(u));
503 		    t = list_basename(t);
504 		    ctx->dbufi = buffer_write(ctx->dbufi, t, strlen(t));
505 		    ctx->dbufi = buffer_write(ctx->dbufi, "\r\n", 2);
506 		}
507 		/* Workaround for UNIX guys. And for broken clients. */
508 		else if (mode != List_mlsd && mode != List_mlst && path && !strchr(path, '/')) {
509 		    if (path[0] == '-')
510 			list(ctx, ".", List_list);
511 		    else if (!list_dir(ctx, ctx->cwd, path)) {
512 			io_sched_add(ctx->io, ctx, (void *) list_dir_details, 0, 0);
513 			io_clr_o(ctx->io, ctx->cfn);
514 		    }
515 		}
516 	    }
517 	}
518     DebugOut(DEBUG_PROC);
519 }
520 
521 /* list_dir() return values:
522  *  0: OK, Caller should call list_dir_details ()
523  *  EINVAL: filter is not a valid globbing expression
524  *  EPERM: user has no access to directory
525  *  ENOTDIR: dirname is not a directory
526  *  ENOENT: dirname does not exist
527  */
528 
list_dir(struct context * ctx,char * dirname,char * filter)529 static int list_dir(struct context *ctx, char *dirname, char *filter)
530 {
531     DIR *dir;
532     struct dirent *de;
533     struct glob_pattern *g = NULL;
534 
535     Debug((DEBUG_PROC, "+ %s(\"%s\", ...)\n", __func__, dirname));
536 
537     if (!dirname)
538 	dirname = ".";
539 
540     if (filter && !(g = glob_comp(filter))) {
541 	Debug((DEBUG_PROC, "- %s: glob_comp failed\n", __func__));
542 	return EINVAL;
543     }
544 
545     ctx->pst_valid = 0;
546 
547     if (pickystat(ctx, &ctx->pst, dirname)) {
548 	DebugOut(DEBUG_PROC);
549 	return (errno == ENOENT ? ENOENT : EPERM);
550     }
551 
552     ctx->pst_valid = 1;
553 
554     if (!(dir = opendir(dirname))) {
555 	Debug((DEBUG_PROC, "- %s: opendir failure\n", __func__));
556 	return (errno == ENOTDIR ? ENOTDIR : EPERM);
557     }
558 
559     RB_tree_delete(ctx->filelist);
560     ctx->filelist = RB_tree_new(NULL, free_payload);
561 
562     while ((de = readdir(dir))) {
563 	char *copy;
564 	if (de->d_name[0] == '.') {
565 	    if (de->d_name[1] == '.' && !de->d_name[2]) {
566 		/* don't display ".." in top level root directory */
567 		if (ctx->pst.st_ino == ctx->root_ino && ctx->pst.st_dev == ctx->root_dev)
568 		    continue;
569 	    } else if (!ctx->allow_dotfiles)
570 		continue;
571 
572 	    /* wildcards my not match files starting with a dot */
573 	    if (filter && filter[0] != '.')
574 		continue;
575 	}
576 
577 	if (filter && !glob_exec(g, de->d_name))
578 	    continue;
579 
580 	copy = Xstrdup(de->d_name);
581 	RB_insert(ctx->filelist, copy);
582 	Debug((DEBUG_PROC, "inserted %s\n", copy));
583     }
584 
585     if (g)
586 	glob_free(g);
587 
588     if (RB_empty(ctx->filelist)) {
589 	closedir(dir);
590 	Debug((DEBUG_PROC, "- %s: no files\n", __func__));
591 	return ENOENT;
592     }
593 #ifdef WITH_DIRFD
594     ctx->dirfn = dup(dirfd(dir));
595     closedir(dir);
596 #else				/* WITH_DIRFD */
597     closedir(dir);
598     ctx->dirfn = open(dirname, O_RDONLY);
599 #endif				/* WITH_DIRFD */
600 
601     fcntl(ctx->dirfn, F_SETFD, FD_CLOEXEC);
602 
603     DebugOut(DEBUG_PROC);
604     return 0;
605 }
606 
list_dir_details(struct context * ctx,int cur)607 static void list_dir_details(struct context *ctx, int cur __attribute__ ((unused)))
608 {
609     rb_node_t *rbn;
610     struct buffer *b;
611     int i = 5;
612     struct buffer *(*bf) (struct buffer *, char *, size_t);
613 
614     DebugIn(DEBUG_PROC);
615 
616     if ((ctx->uid != (uid_t) - 1) && (current_uid != ctx->uid || current_gid != ctx->gid || update_ids)) {
617 	seteuid(0);
618 	setgroups(ctx->gids_size, ctx->gids);
619 	setegid(ctx->gid);
620 	seteuid(ctx->uid);
621 	current_gid = ctx->gid;
622 	current_uid = ctx->uid;
623 	update_ids = 0;
624     }
625 
626     if (ctx->dirfn < 0 || fchdir(ctx->dirfn)) {
627 	RB_tree_delete(ctx->filelist);
628 	ctx->filelist = NULL;
629 	if (chdir("/")) {
630 	    // FIXME
631 	}
632 	DebugOut(DEBUG_PROC);
633 	return;
634     }
635 
636     if (ctx->list_to_cc)
637 	b = ctx->cbufo, bf = buffer_reply;
638     else
639 	b = ctx->dbufi, bf = buffer_write;
640 
641     while (i-- && (rbn = RB_first(ctx->filelist))) {
642 	char *u, buffer[1024];
643 	char *p;
644 
645 	if ((u = list_one(ctx, p = RB_payload(rbn, char *), ctx->list_mode, buffer, sizeof(buffer))))
646 	    switch (ctx->list_mode) {
647 	    case List_mlsd:
648 	    case List_list:
649 		b = bf(b, u, strlen(u));
650 	    case List_nlst:
651 		b = bf(b, p, strlen(p));
652 		b = buffer_write(b, "\r\n", 2);
653 	    default:
654 		;
655 	    }
656 	RB_delete(ctx->filelist, rbn);
657     }
658 
659     /*  Could use
660      *     *(ctx->list_to_cc ? &ctx->cbufo : &ctx->dbufi) = b;
661      *  instead of:
662      */
663     if (ctx->list_to_cc)
664 	ctx->cbufo = b;
665     else
666 	ctx->dbufi = b;
667 
668     if (RB_empty(ctx->filelist)) {
669 	Debug((DEBUG_PROC, "filelist empty\n"));
670 	io_sched_pop(ctx->io, ctx);
671 	RB_tree_delete(ctx->filelist);
672 	ctx->filelist = NULL;
673 	close(ctx->dirfn);
674 	ctx->dirfn = -1;
675 	ctx->pst_valid = 0;
676     } else
677 	io_sched_renew_proc(ctx->io, ctx, (void *) list_dir_details);
678 
679     if (chdir("/")) {
680 	//FIXME
681     }
682 
683     DebugOut(DEBUG_PROC);
684 }
685