1 /*
2 * ProFTPD: mod_vroot FSIO API
3 * Copyright (c) 2002-2016 TJ Saunders
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18 *
19 * As a special exemption, TJ Saunders and other respective copyright holders
20 * give permission to link this program with OpenSSL, and distribute the
21 * resulting executable, without including the source code for OpenSSL in the
22 * source distribution.
23 */
24
25 #include "fsio.h"
26 #include "path.h"
27 #include "alias.h"
28
29 static pool *vroot_dir_pool = NULL;
30 static pr_table_t *vroot_dirtab = NULL;
31
32 static const char *trace_channel = "vroot.fsio";
33
vroot_fsio_stat(pr_fs_t * fs,const char * stat_path,struct stat * st)34 int vroot_fsio_stat(pr_fs_t *fs, const char *stat_path, struct stat *st) {
35 int res, xerrno;
36 char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
37 pool *tmp_pool = NULL;
38
39 if (session.curr_phase == LOG_CMD ||
40 session.curr_phase == LOG_CMD_ERR ||
41 (session.sf_flags & SF_ABORT) ||
42 vroot_path_have_base() == FALSE) {
43 /* NOTE: once stackable FS modules are supported, have this fall through
44 * to the next module in the stack.
45 */
46 return stat(stat_path, st);
47 }
48
49 tmp_pool = make_sub_pool(session.pool);
50 pr_pool_tag(tmp_pool, "VRoot FSIO stat pool");
51 path = vroot_realpath(tmp_pool, stat_path, 0);
52
53 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
54 xerrno = errno;
55
56 destroy_pool(tmp_pool);
57 errno = xerrno;
58 return -1;
59 }
60
61 res = stat(vpath, st);
62 xerrno = errno;
63
64 destroy_pool(tmp_pool);
65 errno = xerrno;
66 return res;
67 }
68
vroot_fsio_lstat(pr_fs_t * fs,const char * lstat_path,struct stat * st)69 int vroot_fsio_lstat(pr_fs_t *fs, const char *lstat_path, struct stat *st) {
70 int res, xerrno;
71 char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
72 size_t pathlen = 0;
73 pool *tmp_pool = NULL;
74
75 if (session.curr_phase == LOG_CMD ||
76 session.curr_phase == LOG_CMD_ERR ||
77 (session.sf_flags & SF_ABORT) ||
78 vroot_path_have_base() == FALSE) {
79 /* NOTE: once stackable FS modules are supported, have this fall through
80 * to the next module in the stack.
81 */
82 return lstat(lstat_path, st);
83 }
84
85 tmp_pool = make_sub_pool(session.pool);
86 pr_pool_tag(tmp_pool, "VRoot FSIO lstat pool");
87
88 path = pstrdup(tmp_pool, lstat_path);
89 vroot_path_clean(path);
90
91 /* If the given path ends in a slash, remove it. The handling of
92 * VRootAliases is sensitive to such things.
93 */
94 pathlen = strlen(path);
95 if (pathlen > 1 &&
96 path[pathlen-1] == '/') {
97 path[pathlen-1] = '\0';
98 pathlen--;
99 }
100
101 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
102 xerrno = errno;
103
104 destroy_pool(tmp_pool);
105 errno = xerrno;
106 return -1;
107 }
108
109 if ((vroot_opts & VROOT_OPT_ALLOW_SYMLINKS) ||
110 vroot_alias_exists(path) == TRUE) {
111 res = lstat(vpath, st);
112 if (res < 0) {
113 xerrno = errno;
114
115 destroy_pool(tmp_pool);
116 errno = xerrno;
117 return -1;
118 }
119
120 res = stat(vpath, st);
121 xerrno = errno;
122
123 destroy_pool(tmp_pool);
124 errno = xerrno;
125 return res;
126 }
127
128 res = lstat(vpath, st);
129 xerrno = errno;
130
131 destroy_pool(tmp_pool);
132 errno = xerrno;
133 return res;
134 }
135
vroot_fsio_rename(pr_fs_t * fs,const char * from,const char * to)136 int vroot_fsio_rename(pr_fs_t *fs, const char *from, const char *to) {
137 char vpath1[PR_TUNABLE_PATH_MAX + 1], vpath2[PR_TUNABLE_PATH_MAX + 1];
138
139 if (session.curr_phase == LOG_CMD ||
140 session.curr_phase == LOG_CMD_ERR ||
141 (session.sf_flags & SF_ABORT) ||
142 vroot_path_have_base() == FALSE) {
143 /* NOTE: once stackable FS modules are supported, have this fall through
144 * to the next module in the stack.
145 */
146 return rename(from, to);
147 }
148
149 if (vroot_path_lookup(NULL, vpath1, sizeof(vpath1)-1, from, 0, NULL) < 0) {
150 return -1;
151 }
152
153 if (vroot_path_lookup(NULL, vpath2, sizeof(vpath2)-1, to, 0, NULL) < 0) {
154 return -1;
155 }
156
157 return rename(vpath1, vpath2);
158 }
159
vroot_fsio_unlink(pr_fs_t * fs,const char * path)160 int vroot_fsio_unlink(pr_fs_t *fs, const char *path) {
161 char vpath[PR_TUNABLE_PATH_MAX + 1];
162
163 if (vroot_path_have_base() == FALSE) {
164 /* NOTE: once stackable FS modules are supported, have this fall through
165 * to the next module in the stack.
166 */
167 return unlink(path);
168 }
169
170 /* Do not allow deleting of aliased files/directories; the aliases may only
171 * exist for this user/group.
172 */
173 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path,
174 VROOT_LOOKUP_FL_NO_ALIAS, NULL) < 0) {
175 return -1;
176 }
177
178 if (vroot_alias_exists(vpath) == TRUE) {
179 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
180 "denying delete of '%s' because it is a VRootAlias", vpath);
181 errno = EACCES;
182 return -1;
183 }
184
185 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
186 return -1;
187 }
188
189 return unlink(vpath);
190 }
191
vroot_fsio_open(pr_fh_t * fh,const char * path,int flags)192 int vroot_fsio_open(pr_fh_t *fh, const char *path, int flags) {
193 char vpath[PR_TUNABLE_PATH_MAX + 1];
194
195 if (session.curr_phase == LOG_CMD ||
196 session.curr_phase == LOG_CMD_ERR ||
197 (session.sf_flags & SF_ABORT) ||
198 vroot_path_have_base() == FALSE) {
199 /* NOTE: once stackable FS modules are supported, have this fall through
200 * to the next module in the stack.
201 */
202 return open(path, flags, PR_OPEN_MODE);
203 }
204
205 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
206 return -1;
207 }
208
209 return open(vpath, flags, PR_OPEN_MODE);
210 }
211
vroot_fsio_creat(pr_fh_t * fh,const char * path,mode_t mode)212 int vroot_fsio_creat(pr_fh_t *fh, const char *path, mode_t mode) {
213 int res;
214 #if PROFTPD_VERSION_NUMBER < 0x0001030603
215 char vpath[PR_TUNABLE_PATH_MAX + 1];
216
217 if (session.curr_phase == LOG_CMD ||
218 session.curr_phase == LOG_CMD_ERR ||
219 (session.sf_flags & SF_ABORT) ||
220 vroot_path_have_base() == FALSE) {
221 /* NOTE: once stackable FS modules are supported, have this fall through
222 * to the next module in the stack.
223 */
224 return creat(path, mode);
225 }
226
227 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
228 return -1;
229 }
230
231 res = creat(vpath, mode);
232 #else
233 errno = ENOSYS;
234 res = -1;
235 #endif /* ProFTPD 1.3.6rc2 or earlier */
236
237 return res;
238 }
239
vroot_fsio_link(pr_fs_t * fs,const char * path1,const char * path2)240 int vroot_fsio_link(pr_fs_t *fs, const char *path1, const char *path2) {
241 char vpath1[PR_TUNABLE_PATH_MAX + 1], vpath2[PR_TUNABLE_PATH_MAX + 1];
242
243 if (session.curr_phase == LOG_CMD ||
244 session.curr_phase == LOG_CMD_ERR ||
245 (session.sf_flags & SF_ABORT) ||
246 vroot_path_have_base() == FALSE) {
247 /* NOTE: once stackable FS modules are supported, have this fall through
248 * to the next module in the stack.
249 */
250 return link(path1, path2);
251 }
252
253 if (vroot_path_lookup(NULL, vpath1, sizeof(vpath1)-1, path1, 0, NULL) < 0) {
254 return -1;
255 }
256
257 if (vroot_path_lookup(NULL, vpath2, sizeof(vpath2)-1, path2, 0, NULL) < 0) {
258 return -1;
259 }
260
261 return link(vpath1, vpath2);
262 }
263
vroot_fsio_symlink(pr_fs_t * fs,const char * path1,const char * path2)264 int vroot_fsio_symlink(pr_fs_t *fs, const char *path1, const char *path2) {
265 char vpath1[PR_TUNABLE_PATH_MAX + 1], vpath2[PR_TUNABLE_PATH_MAX + 1];
266
267 if (session.curr_phase == LOG_CMD ||
268 session.curr_phase == LOG_CMD_ERR ||
269 (session.sf_flags & SF_ABORT) ||
270 vroot_path_have_base() == FALSE) {
271 /* NOTE: once stackable FS modules are supported, have this fall through
272 * to the next module in the stack.
273 */
274 return symlink(path1, path2);
275 }
276
277 if (vroot_path_lookup(NULL, vpath1, sizeof(vpath1)-1, path1, 0, NULL) < 0) {
278 return -1;
279 }
280
281 if (vroot_path_lookup(NULL, vpath2, sizeof(vpath2)-1, path2, 0, NULL) < 0) {
282 return -1;
283 }
284
285 return symlink(vpath1, vpath2);
286 }
287
vroot_fsio_readlink(pr_fs_t * fs,const char * readlink_path,char * buf,size_t bufsz)288 int vroot_fsio_readlink(pr_fs_t *fs, const char *readlink_path, char *buf,
289 size_t bufsz) {
290 int res, xerrno;
291 char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL, *alias_path = NULL;
292 pool *tmp_pool = NULL;
293
294 if (session.curr_phase == LOG_CMD ||
295 session.curr_phase == LOG_CMD_ERR ||
296 (session.sf_flags & SF_ABORT) ||
297 vroot_path_have_base() == FALSE) {
298 /* NOTE: once stackable FS modules are supported, have this fall through
299 * to the next module in the stack.
300 */
301 return readlink(readlink_path, buf, bufsz);
302 }
303
304 /* In order to find any VRootAlias paths, we need to use the full path.
305 * However, if we do NOT find any VRootAlias, then we do NOT want to use
306 * the full path.
307 */
308
309 tmp_pool = make_sub_pool(session.pool);
310 pr_pool_tag(tmp_pool, "VRoot FSIO readlink pool");
311
312 path = vroot_realpath(tmp_pool, readlink_path, VROOT_REALPATH_FL_ABS_PATH);
313
314 if (vroot_path_lookup(tmp_pool, vpath, sizeof(vpath)-1, path, 0,
315 &alias_path) < 0) {
316 xerrno = errno;
317
318 destroy_pool(tmp_pool);
319 errno = xerrno;
320 return -1;
321 }
322
323 if (alias_path == NULL) {
324 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, readlink_path, 0,
325 NULL) < 0) {
326 xerrno = errno;
327
328 destroy_pool(tmp_pool);
329 errno = xerrno;
330 return -1;
331 }
332 }
333
334 res = readlink(vpath, buf, bufsz);
335 xerrno = errno;
336
337 destroy_pool(tmp_pool);
338 errno = xerrno;
339 return res;
340 }
341
vroot_fsio_truncate(pr_fs_t * fs,const char * path,off_t len)342 int vroot_fsio_truncate(pr_fs_t *fs, const char *path, off_t len) {
343 char vpath[PR_TUNABLE_PATH_MAX + 1];
344
345 if (session.curr_phase == LOG_CMD ||
346 session.curr_phase == LOG_CMD_ERR ||
347 (session.sf_flags & SF_ABORT) ||
348 vroot_path_have_base() == FALSE) {
349 /* NOTE: once stackable FS modules are supported, have this fall through
350 * to the next module in the stack.
351 */
352 return truncate(path, len);
353 }
354
355 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
356 return -1;
357 }
358
359 return truncate(vpath, len);
360 }
361
vroot_fsio_chmod(pr_fs_t * fs,const char * path,mode_t mode)362 int vroot_fsio_chmod(pr_fs_t *fs, const char *path, mode_t mode) {
363 char vpath[PR_TUNABLE_PATH_MAX + 1];
364
365 if (session.curr_phase == LOG_CMD ||
366 session.curr_phase == LOG_CMD_ERR ||
367 (session.sf_flags & SF_ABORT) ||
368 vroot_path_have_base() == FALSE) {
369 /* NOTE: once stackable FS modules are supported, have this fall through
370 * to the next module in the stack.
371 */
372 return chmod(path, mode);
373 }
374
375 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
376 return -1;
377 }
378
379 return chmod(vpath, mode);
380 }
381
vroot_fsio_chown(pr_fs_t * fs,const char * path,uid_t uid,gid_t gid)382 int vroot_fsio_chown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
383 char vpath[PR_TUNABLE_PATH_MAX + 1];
384
385 if (session.curr_phase == LOG_CMD ||
386 session.curr_phase == LOG_CMD_ERR ||
387 (session.sf_flags & SF_ABORT) ||
388 vroot_path_have_base() == FALSE) {
389 /* NOTE: once stackable FS modules are supported, have this fall through
390 * to the next module in the stack.
391 */
392 return chown(path, uid, gid);
393 }
394
395 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
396 return -1;
397 }
398
399 return chown(vpath, uid, gid);
400 }
401
vroot_fsio_lchown(pr_fs_t * fs,const char * path,uid_t uid,gid_t gid)402 int vroot_fsio_lchown(pr_fs_t *fs, const char *path, uid_t uid, gid_t gid) {
403 int res;
404 #if PROFTPD_VERSION_NUMBER >= 0x0001030407
405 char vpath[PR_TUNABLE_PATH_MAX + 1];
406
407 if (session.curr_phase == LOG_CMD ||
408 session.curr_phase == LOG_CMD_ERR ||
409 (session.sf_flags & SF_ABORT) ||
410 vroot_path_have_base() == FALSE) {
411 /* NOTE: once stackable FS modules are supported, have this fall through
412 * to the next module in the stack.
413 */
414 return lchown(path, uid, gid);
415 }
416
417 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
418 return -1;
419 }
420
421 res = lchown(vpath, uid, gid);
422 #else
423 errno = ENOSYS;
424 res = -1;
425 #endif /* ProFTPD 1.3.4c or later */
426
427 return res;
428 }
429
vroot_fsio_chroot(pr_fs_t * fs,const char * path)430 int vroot_fsio_chroot(pr_fs_t *fs, const char *path) {
431 char base[PR_TUNABLE_PATH_MAX + 1];
432 char *chroot_path = "/", *tmp = NULL;
433 config_rec *c;
434 size_t baselen = 0;
435
436 if (path == NULL ||
437 *path == '\0') {
438 errno = EINVAL;
439 return -1;
440 }
441
442 memset(base, '\0', sizeof(base));
443
444 if (path[0] == '/' &&
445 path[1] == '\0') {
446 /* chrooting to '/', nothing needs to be done. */
447 return 0;
448 }
449
450 c = find_config(main_server->conf, CONF_PARAM, "VRootServerRoot", FALSE);
451 if (c != NULL) {
452 int res;
453 char *server_root, *ptr = NULL;
454
455 server_root = c->argv[0];
456
457 /* If the last character in the configured path is a slash, remove
458 * it temporarily.
459 */
460 if (server_root[strlen(server_root)-1] == '/') {
461 ptr = &(server_root[strlen(server_root)-1]);
462 *ptr = '\0';
463 }
464
465 /* Now, make sure that the given path is below the configured
466 * VRootServerRoot. If so, then we perform a real chroot to the
467 * VRootServerRoot directory, then use vroots from there.
468 */
469
470 res = strncmp(path, server_root, strlen(server_root));
471
472 if (ptr != NULL) {
473 *ptr = '/';
474 }
475
476 if (res == 0) {
477 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
478 "chroot path '%s' within VRootServerRoot '%s', "
479 "chrooting to VRootServerRoot", path, server_root);
480
481 if (chroot(server_root) < 0) {
482 int xerrno = errno;
483
484 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
485 "error chrooting to VRootServerRoot '%s': %s", server_root,
486 strerror(xerrno));
487
488 errno = xerrno;
489 return -1;
490 }
491
492 pr_fs_clean_path(path + strlen(server_root), base, sizeof(base));
493 chroot_path = server_root;
494
495 } else {
496 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
497 "chroot path '%s' is not within VRootServerRoot '%s', "
498 "not chrooting to VRootServerRoot", path, server_root);
499 pr_fs_clean_path(path, base, sizeof(base));
500 }
501
502 } else {
503 pr_fs_clean_path(path, base, sizeof(base));
504 }
505
506 tmp = base;
507
508 /* Advance to the end of the path. */
509 while (*tmp != '\0') {
510 tmp++;
511 }
512
513 for (;;) {
514 tmp--;
515
516 pr_signals_handle();
517
518 if (tmp == base ||
519 *tmp != '/') {
520 break;
521 }
522
523 *tmp = '\0';
524 }
525
526 baselen = strlen(base);
527 if (baselen >= PR_TUNABLE_PATH_MAX) {
528 errno = ENAMETOOLONG;
529 return -1;
530 }
531
532 vroot_path_set_base(base, baselen);
533 session.chroot_path = pstrdup(session.pool, chroot_path);
534 return 0;
535 }
536
vroot_fsio_chdir(pr_fs_t * fs,const char * path)537 int vroot_fsio_chdir(pr_fs_t *fs, const char *path) {
538 int res, xerrno;
539 const char *base_path;
540 size_t base_pathlen = 0;
541 char vpath[PR_TUNABLE_PATH_MAX + 1], *vpathp = NULL, *alias_path = NULL;
542 pool *tmp_pool = NULL;
543
544 if (session.curr_phase == LOG_CMD ||
545 session.curr_phase == LOG_CMD_ERR ||
546 (session.sf_flags & SF_ABORT) ||
547 vroot_path_have_base() == FALSE) {
548 /* NOTE: once stackable FS modules are supported, have this fall through
549 * to the next module in the stack.
550 */
551 return chdir(path);
552 }
553
554 tmp_pool = make_sub_pool(session.pool);
555 pr_pool_tag(tmp_pool, "VRoot FSIO chdir pool");
556
557 if (vroot_path_lookup(tmp_pool, vpath, sizeof(vpath)-1, path, 0,
558 &alias_path) < 0) {
559 xerrno = errno;
560
561 destroy_pool(tmp_pool);
562 errno = xerrno;
563 return -1;
564 }
565
566 res = chdir(vpath);
567 if (res < 0) {
568 xerrno = errno;
569
570 destroy_pool(tmp_pool);
571 errno = xerrno;
572 return -1;
573 }
574
575 if (alias_path != NULL) {
576 vpathp = alias_path;
577
578 } else {
579 vpathp = vpath;
580 }
581
582 base_path = vroot_path_get_base(tmp_pool, &base_pathlen);
583 if (strncmp(vpathp, base_path, base_pathlen) == 0) {
584 pr_trace_msg(trace_channel, 19,
585 "adjusting vpath '%s' to account for vroot base '%s' (%lu)", vpathp,
586 base_path, (unsigned long) base_pathlen);
587 vpathp += base_pathlen;
588 }
589
590 pr_trace_msg(trace_channel, 19,
591 "setting current working directory to '%s'", vpathp);
592
593 /* pr_fs_setcwd() makes a copy of the argument path, so we can safely
594 * destroy our temporary pool.
595 */
596 pr_fs_setcwd(vpathp);
597
598 destroy_pool(tmp_pool);
599 return 0;
600 }
601
vroot_fsio_utimes(pr_fs_t * fs,const char * utimes_path,struct timeval * tvs)602 int vroot_fsio_utimes(pr_fs_t *fs, const char *utimes_path,
603 struct timeval *tvs) {
604 int res, xerrno;
605 char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
606 pool *tmp_pool = NULL;
607
608 if (session.curr_phase == LOG_CMD ||
609 session.curr_phase == LOG_CMD_ERR ||
610 (session.sf_flags & SF_ABORT) ||
611 vroot_path_have_base() == FALSE) {
612 /* NOTE: once stackable FS modules are supported, have this fall through
613 * to the next module in the stack.
614 */
615 return utimes(utimes_path, tvs);
616 }
617
618 tmp_pool = make_sub_pool(session.pool);
619 pr_pool_tag(tmp_pool, "VRoot FSIO utimes pool");
620
621 path = vroot_realpath(tmp_pool, utimes_path, VROOT_REALPATH_FL_ABS_PATH);
622
623 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
624 xerrno = errno;
625
626 destroy_pool(tmp_pool);
627 errno = xerrno;
628 return -1;
629 }
630
631 res = utimes(vpath, tvs);
632 xerrno = errno;
633
634 destroy_pool(tmp_pool);
635 errno = xerrno;
636 return res;
637 }
638
639 static struct dirent *vroot_dent = NULL;
640 static size_t vroot_dentsz = 0;
641
642 /* On most systems, dirent.d_name is an array into which we can copy the
643 * name we want.
644 *
645 * However, on other systems (e.g. Solaris 2), dirent.d_name is an array size
646 * of 1. This approach makes use of the fact that the d_name member is the
647 * last member of the struct, meaning that the actual size is variable.
648 *
649 * We need to Do The Right Thing(tm) in either case.
650 */
651 static size_t vroot_dent_namesz = 0;
652
653 static array_header *vroot_dir_aliases = NULL;
654 static int vroot_dir_idx = -1;
655
vroot_alias_dirscan(const void * key_data,size_t key_datasz,const void * value_data,size_t value_datasz,void * user_data)656 static int vroot_alias_dirscan(const void *key_data, size_t key_datasz,
657 const void *value_data, size_t value_datasz, void *user_data) {
658 const char *alias_path = NULL, *dir_path = NULL, *real_path = NULL;
659 char *ptr = NULL;
660 size_t dir_pathlen;
661
662 alias_path = key_data;
663 real_path = value_data;
664 dir_path = user_data;
665
666 ptr = strrchr(alias_path, '/');
667 if (ptr == NULL) {
668 /* This is not likely to happen, but if it does, simply move to the
669 * next item in the table.
670 */
671 return 0;
672 }
673
674 /* If the dir path and the real path are the same, skip this alias.
675 * Otherwise we end up with an extraneous entry in the directory listing.
676 */
677 if (strcmp(real_path, dir_path) == 0) {
678 return 0;
679 }
680
681 dir_pathlen = strlen(dir_path);
682
683 if (strncmp(dir_path, alias_path, dir_pathlen) == 0) {
684 pr_trace_msg(trace_channel, 17,
685 "adding VRootAlias '%s' to list of aliases contained in '%s'",
686 alias_path, dir_path);
687 *((char **) push_array(vroot_dir_aliases)) = pstrdup(vroot_dir_pool,
688 ptr + 1);
689 }
690
691 return 0;
692 }
693
vroot_dirtab_keycmp_cb(const void * key1,size_t keysz1,const void * key2,size_t keysz2)694 static int vroot_dirtab_keycmp_cb(const void *key1, size_t keysz1,
695 const void *key2, size_t keysz2) {
696 unsigned long k1, k2;
697
698 memcpy(&k1, key1, sizeof(k1));
699 memcpy(&k2, key2, sizeof(k2));
700
701 return (k1 == k2 ? 0 : 1);
702 }
703
vroot_dirtab_hash_cb(const void * key,size_t keysz)704 static unsigned int vroot_dirtab_hash_cb(const void *key, size_t keysz) {
705 unsigned long h;
706
707 memcpy(&h, key, sizeof(h));
708 return h;
709 }
710
vroot_fsio_opendir(pr_fs_t * fs,const char * orig_path)711 void *vroot_fsio_opendir(pr_fs_t *fs, const char *orig_path) {
712 int res, xerrno;
713 char vpath[PR_TUNABLE_PATH_MAX + 1], *path = NULL;
714 void *dirh = NULL;
715 struct stat st;
716 size_t pathlen = 0;
717 pool *tmp_pool = NULL;
718 unsigned int alias_count;
719
720 if (session.curr_phase == LOG_CMD ||
721 session.curr_phase == LOG_CMD_ERR ||
722 (session.sf_flags & SF_ABORT) ||
723 vroot_path_have_base() == FALSE) {
724 /* NOTE: once stackable FS modules are supported, have this fall through
725 * to the next module in the stack.
726 */
727 return opendir(orig_path);
728 }
729
730 tmp_pool = make_sub_pool(session.pool);
731 pr_pool_tag(tmp_pool, "VRoot FSIO opendir pool");
732
733 /* If the given path ends in a slash, remove it. The handling of
734 * VRootAliases is sensitive to trailing slashes.
735 */
736 path = pstrdup(tmp_pool, orig_path);
737 vroot_path_clean(path);
738
739 /* If the given path ends in a slash, remove it. The handling of
740 * VRootAliases is sensitive to such things.
741 */
742 pathlen = strlen(path);
743 if (pathlen > 1 &&
744 path[pathlen-1] == '/') {
745 path[pathlen-1] = '\0';
746 pathlen--;
747 }
748
749 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
750 xerrno = errno;
751
752 destroy_pool(tmp_pool);
753 errno = xerrno;
754 return NULL;
755 }
756
757 /* Check if the looked-up vpath is a symlink; we may need to resolve any
758 * links ourselves, rather than assuming that the system opendir(3) can
759 * handle it.
760 */
761
762 res = vroot_fsio_lstat(fs, vpath, &st);
763 while (res == 0 &&
764 S_ISLNK(st.st_mode)) {
765 char data[PR_TUNABLE_PATH_MAX + 1];
766
767 pr_signals_handle();
768
769 memset(data, '\0', sizeof(data));
770 res = vroot_fsio_readlink(fs, vpath, data, sizeof(data)-1);
771 if (res < 0) {
772 break;
773 }
774
775 data[res] = '\0';
776
777 sstrncpy(vpath, data, sizeof(vpath));
778 res = vroot_fsio_lstat(fs, vpath, &st);
779 }
780
781 dirh = opendir(vpath);
782 if (dirh == NULL) {
783 xerrno = errno;
784
785 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
786 "error opening virtualized directory '%s' (from '%s'): %s", vpath, path,
787 strerror(xerrno));
788 destroy_pool(tmp_pool);
789
790 errno = xerrno;
791 return NULL;
792 }
793
794 alias_count = vroot_alias_count();
795 if (alias_count > 0) {
796 unsigned long *cache_dirh = NULL;
797
798 if (vroot_dirtab == NULL) {
799 vroot_dir_pool = make_sub_pool(session.pool);
800 pr_pool_tag(vroot_dir_pool, "VRoot Directory Pool");
801
802 vroot_dirtab = pr_table_alloc(vroot_dir_pool, 0);
803
804 /* Since this table will use DIR pointers as keys, we want to override
805 * the default hashing and key comparison functions used.
806 */
807
808 pr_table_ctl(vroot_dirtab, PR_TABLE_CTL_SET_KEY_HASH,
809 vroot_dirtab_hash_cb);
810 pr_table_ctl(vroot_dirtab, PR_TABLE_CTL_SET_KEY_CMP,
811 vroot_dirtab_keycmp_cb);
812 }
813
814 cache_dirh = palloc(vroot_dir_pool, sizeof(unsigned long));
815 *cache_dirh = (unsigned long) dirh;
816
817 if (pr_table_kadd(vroot_dirtab, cache_dirh, sizeof(unsigned long),
818 pstrdup(vroot_dir_pool, vpath), strlen(vpath) + 1) < 0) {
819 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
820 "error stashing path '%s' (key %p) in directory table: %s", vpath,
821 dirh, strerror(errno));
822
823 } else {
824 vroot_dir_aliases = make_array(vroot_dir_pool, 0, sizeof(char *));
825
826 res = vroot_alias_do(vroot_alias_dirscan, vpath);
827 if (res < 0) {
828 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
829 "error doing dirscan on aliases table: %s", strerror(errno));
830
831 } else {
832 register unsigned int i;
833
834 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
835 "found %d %s in directory '%s'", vroot_dir_aliases->nelts,
836 vroot_dir_aliases->nelts != 1 ? "VRootAliases" : "VRootAlias",
837 vpath);
838 vroot_dir_idx = 0;
839
840 for (i = 0; i < vroot_dir_aliases->nelts; i++) {
841 char **elts = vroot_dir_aliases->elts;
842
843 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
844 "'%s' aliases: [%u] %s", vpath, i, elts[i]);
845 }
846 }
847 }
848 }
849
850 destroy_pool(tmp_pool);
851 return dirh;
852 }
853
vroot_fsio_readdir(pr_fs_t * fs,void * dirh)854 struct dirent *vroot_fsio_readdir(pr_fs_t *fs, void *dirh) {
855 struct dirent *dent = NULL;
856
857 next_dent:
858 dent = readdir((DIR *) dirh);
859
860 if (vroot_dir_aliases != NULL) {
861 char **elts;
862
863 elts = vroot_dir_aliases->elts;
864
865 if (dent != NULL) {
866 register unsigned int i;
867
868 /* If this dent has the same name as an alias, the alias wins.
869 * This is similar to a mounted filesystem, which hides any directories
870 * underneath the mount point for the duration of the mount.
871 */
872
873 /* Yes, this is a linear scan; it assumes that the number of configured
874 * aliases for a site will be relatively few. Should this assumption
875 * not be borne out by reality, then we should switch to using a
876 * table, not an array_header, for storing the aliased paths.
877 */
878
879 for (i = 0; i < vroot_dir_aliases->nelts; i++) {
880 if (strcmp(dent->d_name, elts[i]) == 0) {
881 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
882 "skipping directory entry '%s', as it is aliased", dent->d_name);
883 goto next_dent;
884 }
885 }
886
887 } else {
888 if (vroot_dir_idx < 0 ||
889 vroot_dir_idx >= vroot_dir_aliases->nelts) {
890 return NULL;
891 }
892
893 memset(vroot_dent, 0, vroot_dentsz);
894
895 if (vroot_dent_namesz == 0) {
896 sstrncpy(vroot_dent->d_name, elts[vroot_dir_idx++],
897 sizeof(vroot_dent->d_name));
898
899 } else {
900 sstrncpy(vroot_dent->d_name, elts[vroot_dir_idx++],
901 vroot_dent_namesz);
902 }
903
904 return vroot_dent;
905 }
906 }
907
908 return dent;
909 }
910
vroot_fsio_closedir(pr_fs_t * fs,void * dirh)911 int vroot_fsio_closedir(pr_fs_t *fs, void *dirh) {
912 int res;
913
914 res = closedir((DIR *) dirh);
915
916 if (vroot_dirtab != NULL) {
917 unsigned long lookup_dirh;
918 int count;
919
920 lookup_dirh = (unsigned long) dirh;
921 (void) pr_table_kremove(vroot_dirtab, &lookup_dirh, sizeof(unsigned long),
922 NULL);
923
924 /* If the dirtab table is empty, destroy the table. */
925 count = pr_table_count(vroot_dirtab);
926 if (count == 0) {
927 pr_table_empty(vroot_dirtab);
928 destroy_pool(vroot_dir_pool);
929 vroot_dir_pool = NULL;
930 vroot_dirtab = NULL;
931 vroot_dir_aliases = NULL;
932 vroot_dir_idx = -1;
933 }
934 }
935
936 return res;
937 }
938
vroot_fsio_mkdir(pr_fs_t * fs,const char * path,mode_t mode)939 int vroot_fsio_mkdir(pr_fs_t *fs, const char *path, mode_t mode) {
940 char vpath[PR_TUNABLE_PATH_MAX + 1];
941
942 if (session.curr_phase == LOG_CMD ||
943 session.curr_phase == LOG_CMD_ERR ||
944 (session.sf_flags & SF_ABORT) ||
945 vroot_path_have_base() == FALSE) {
946 /* NOTE: once stackable FS modules are supported, have this fall through
947 * to the next module in the stack.
948 */
949 return mkdir(path, mode);
950 }
951
952 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
953 return -1;
954 }
955
956 return mkdir(vpath, mode);
957 }
958
vroot_fsio_rmdir(pr_fs_t * fs,const char * path)959 int vroot_fsio_rmdir(pr_fs_t *fs, const char *path) {
960 char vpath[PR_TUNABLE_PATH_MAX + 1];
961
962 if (session.curr_phase == LOG_CMD ||
963 session.curr_phase == LOG_CMD_ERR ||
964 (session.sf_flags & SF_ABORT) ||
965 vroot_path_have_base() == FALSE) {
966 /* NOTE: once stackable FS modules are supported, have this fall through
967 * to the next module in the stack.
968 */
969 return rmdir(path);
970 }
971
972 /* Do not allow deleting of aliased files/directories; the aliases may only
973 * exist for this user/group.
974 */
975 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path,
976 VROOT_LOOKUP_FL_NO_ALIAS, NULL) < 0) {
977 return -1;
978 }
979
980 if (vroot_alias_exists(vpath) == TRUE) {
981 (void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
982 "denying delete of '%s' because it is a VRootAlias", vpath);
983 errno = EACCES;
984 return -1;
985 }
986
987 if (vroot_path_lookup(NULL, vpath, sizeof(vpath)-1, path, 0, NULL) < 0) {
988 return -1;
989 }
990
991 return rmdir(vpath);
992 }
993
vroot_fsio_init(pool * p)994 int vroot_fsio_init(pool *p) {
995 struct dirent dent;
996
997 if (p == NULL) {
998 errno = EINVAL;
999 return -1;
1000 }
1001
1002 /* Allocate the memory for the static struct dirent that we use, including
1003 * determining the necessary sizes.
1004 */
1005 vroot_dentsz = sizeof(dent);
1006 if (sizeof(dent.d_name) == 1) {
1007 /* Allocate extra space for the dent path name. */
1008 vroot_dent_namesz = PR_TUNABLE_PATH_MAX;
1009 }
1010
1011 vroot_dentsz += vroot_dent_namesz;
1012 vroot_dent = palloc(p, vroot_dentsz);
1013
1014 return 0;
1015 }
1016
vroot_fsio_free(void)1017 int vroot_fsio_free(void) {
1018 return 0;
1019 }
1020