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