ProFTPD and Apache have a lot in common in their concept for inheriting the per-directory settings files. ProFTPD and Apache use “.ftpaccess” and “.htaccess” files respectively.
There is one substantial difference though. ProFTPD does not always look from the current directory to the very root “/” directory, because ProFTPD uses chroot() when the “DefaultRoot” directive is set to “~”, for example. Many distributions set this directive to “~” by default.
Let’s have an example:
- “/home/$user/.ftpaccess” — a “.ftpaccess” file EXISTS
- “/home/$user/private/files/” — no “.ftpaccess” file
Let’s also assume that an FTP user has “/home/$user/private/files” for a home directory in the “/etc/passwd” file. If this user logs in, ProFTPD will chroot() to the user’s home directory first. This will effectively make the per-session “/” root directory to start from “/home/$user/private/files”. Therefore, the ProFTPD process will have access to the following directories:
- “/home/$user/private/files/and-any-subdirectories” — accessible as “/and-any-subdirectories”
- “/home/$user/private/files/” — accessible as “/”
- “/home/$user/private/” — not accessible
- “/home/$user/” — not accessible (“/home/$user/.ftpaccess” — EXISTS but not accessible)
- “/home/” — not accessible
- “/” — not accessible
As a result, even that we have the file “/home/$user/.ftpaccess” it won’t take effect for the FTP user who has “/home/$user/private/files/” for their home directory.
An strace excerpt follows which shows the exact steps which ProFTPD performs when it logs in an FTP user with “DefaultRoot” setting configured to “~”:
root@mysrv:~# getent passwd nobody nobody:x:99:99:Nobody:/:/sbin/nologin root@mysrv:~# getent group 99 nobody:x:99: root@mysrv:~# getent passwd ftpuser1 ftpuser1:x:5178:4388::/home/ftpuser1/private/public_upload:/dev/null root@mysrv:~# getent group 4388 ftpuser1:x:4388: root@mysrv:~# strace -tt -f -p 3442 # main ProFTP process (PID=3442) accepts the new connection and forks a child process to handle it 3442 14:36:58.767425 accept(2, 0, NULL) = 7 3442 14:36:58.768078 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xec66f728) = 9389 # ProFTP child (PID=9389) becomes "root" temporarily 9389 14:36:58.771897 setresuid32(-1, 0, -1) = 0 9389 14:36:58.772026 setresgid32(-1, 0, -1) = 0 # open "syslog" # read server's SSL CA, public and private files # open "/var/log/proftpd/extended.log" # manage the "/var/run/proftpd.scoreboard" file # child becomes "nobody" 9389 14:36:58.785372 setresgid32(-1, 99, -1) = 0 9389 14:36:58.785421 setresuid32(-1, 99, -1) = 0 # log the connection and send a greeting message to the connected FTP client # FTP client sends login information 9389 14:37:00.964572 read(0, "USER ftpuser1\r\n", 4102) = 14 9389 14:37:02.178714 read(0, "PASS SOME-PASS-INPUT\r\n", 4102) = 20 # ProFTP child becomes "root" temporarily 9389 14:37:02.192030 setresuid32(-1, 0, -1) = 0 9389 14:37:02.192090 setresgid32(-1, 0, -1) = 0 # open "/etc/shadow" to compare the supplied username and password # open "/etc/shells" (temporarily switched UID/GID back to "nobody") # open "/etc/ftpusers" # open "/var/log/wtmp" to add a login entry there # open "/var/log/xferlog" # set any additional user "ftpuser1" groups 9389 14:37:02.201134 setgroups32(1, [4388]) = 0 9389 14:37:02.201301 setgid32(4388) = 0 # chroot() to user's home directory (we are still with "root" privileges) 9389 14:37:02.208208 chroot("/home/ftpuser1/private/public_upload") = 0 # drop privileges to user's UID/GID 9389 14:37:02.209190 setgid32(4388) = 0 9389 14:37:02.209253 setresuid32(-1, 5178, -1) = 0 # check for any ".ftpaccess" files 9389 14:37:02.209841 stat64("/.ftpaccess", 0xf640ddd0) = -1 ENOENT (No such file or directory)