diff options
-rw-r--r-- | libexec/ftpd/ftpchroot.5 | 20 | ||||
-rw-r--r-- | libexec/ftpd/ftpcmd.y | 6 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.8 | 11 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.c | 105 |
4 files changed, 101 insertions, 41 deletions
diff --git a/libexec/ftpd/ftpchroot.5 b/libexec/ftpd/ftpchroot.5 index 8c8f629..ec14715 100644 --- a/libexec/ftpd/ftpchroot.5 +++ b/libexec/ftpd/ftpchroot.5 @@ -66,9 +66,16 @@ A username is specified otherwise. The optional second field describes the directory for the user or each member of the group to be locked up in using .Xr chroot 2 . +Be it omitted, the user's login directory will be used. If it is not an absolute pathname, then it will be relative to the user's login directory. -Be this field omitted, the user's login directory will be used. +If it contains the +.Qq \&/./ +seprator, +.Xr ftpd 8 +will treat its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. .Sh FILES .Bl -tag -width /etc/ftpchroot -compact .It Pa /etc/ftpchroot @@ -86,12 +93,17 @@ webuser @hostee .Ed .Pp -And this line will lock up the user +And this line will tell +.Xr ftpd 8 +to lock up the user .Qq joe in -.Pa /var/spool/ftp : +.Pa /var/spool/ftp +and then to change the current directory to +.Pa /joe , +which is relative to the session's new root: .Bd -literal -offset indent -joe /var/spool/ftp +joe /var/spool/ftp/./joe .Ed .Pp And finally the following line will lock up every user connecting diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y index cb9f97f..261ffd0 100644 --- a/libexec/ftpd/ftpcmd.y +++ b/libexec/ftpd/ftpcmd.y @@ -78,6 +78,7 @@ extern union sockunion data_dest, his_addr; extern int logged_in; extern struct passwd *pw; extern int guest; +extern char *homedir; extern int paranoid; extern int logging; extern int type; @@ -535,10 +536,7 @@ cmd | CWD check_login CRLF { if ($2) { - if (guest) - cwd("/"); - else - cwd(pw->pw_dir); + cwd(homedir); } } | CWD check_login SP pathname CRLF diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 index 9d9fd82..491c177 100644 --- a/libexec/ftpd/ftpd.8 +++ b/libexec/ftpd/ftpd.8 @@ -401,6 +401,17 @@ The server performs a to the home directory of the .Dq ftp user. +As a special case if the +.Dq ftp +user's home directory pathname contains the +.Dq \&/./ +separator, +.Nm +uses its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. +A typical example for this case would be +.Pa /usr/local/ftp/./pub . In order that system security is not breached, it is recommended that the .Dq ftp diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c index 8d6d118..3d60f42 100644 --- a/libexec/ftpd/ftpd.c +++ b/libexec/ftpd/ftpd.c @@ -118,6 +118,7 @@ int data; int dataport; int logged_in; struct passwd *pw; +char *homedir; int ftpdebug; int timeout = 900; /* timeout after 15 minutes of inactivity */ int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ @@ -1349,6 +1350,7 @@ pass(char *passwd) #ifdef USE_PAM int e; #endif + char *chrootdir; char *residue = NULL; char *xpasswd; @@ -1471,50 +1473,79 @@ skip: || login_getcapbool(lc, "ftp-chroot", 0) #endif ; - if (guest) { + chrootdir = NULL; + /* + * For a chrooted local user, + * a) see whether ftpchroot(5) specifies a chroot directory, + * b) extract the directory pathname from the line, + * c) expand it to the absolute pathname if necessary. + */ + if (dochroot && residue && + (chrootdir = strtok(residue, " \t")) != NULL && + chrootdir[0] != '/') { + asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir); + if (chrootdir == NULL) + fatalerror("Ran out of memory."); + + } + if (guest || dochroot) { /* - * We MUST do a chdir() after the chroot. Otherwise - * the old current directory will be accessible as "." - * outside the new root! + * If no chroot directory set yet, use the login directory. + * Copy it so it can be modified while pw->pw_dir stays intact. */ - if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { - reply(550, "Can't set guest privileges."); - goto bad; - } - } else if (dochroot) { - char *chrootdir = NULL; - - if (residue && - (chrootdir = strtok(residue, " \t")) != NULL && - chrootdir[0] != '/') { - asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir); - if (chrootdir == NULL) + if (chrootdir == NULL && + (chrootdir = strdup(pw->pw_dir)) == NULL) + fatalerror("Ran out of memory."); + /* + * Check for the "/chroot/./home" syntax, + * separate the chroot and home directory pathnames. + */ + if ((homedir = strstr(chrootdir, "/./")) != NULL) { + *(homedir++) = '\0'; /* wipe '/' */ + homedir++; /* skip '.' */ + /* so chrootdir can be freed later */ + if ((homedir = strdup(homedir)) == NULL) fatalerror("Ran out of memory."); - free(residue); - residue = chrootdir; + } else { + /* + * We MUST do a chdir() after the chroot. Otherwise + * the old current directory will be accessible as "." + * outside the new root! + */ + homedir = "/"; } - if (chrootdir == NULL) - chrootdir = pw->pw_dir; - if (chroot(chrootdir) < 0 || chdir("/") < 0) { + /* + * Finally, do chroot() + */ + if (chroot(chrootdir) < 0) { reply(550, "Can't change root."); - if (residue) - free(residue); goto bad; } - if (residue) - free(residue); - } else if (chdir(pw->pw_dir) < 0) { - if (chdir("/") < 0) { - reply(530, "User %s: can't change directory to %s.", - pw->pw_name, pw->pw_dir); - goto bad; - } else - lreply(230, "No directory! Logging in with home=/"); - } + } else /* real user w/o chroot */ + homedir = pw->pw_dir; + /* + * Set euid *before* doing chdir() so + * a) the user won't be carried to a directory that he couldn't reach + * on his own due to no permission to upper path components, + * b) NFS mounted homedirs w/restrictive permissions will be accessible + * (uid 0 has no root power over NFS if not mapped explicitly.) + */ if (seteuid((uid_t)pw->pw_uid) < 0) { reply(550, "Can't set uid."); goto bad; } + if (chdir(homedir) < 0) { + if (guest || dochroot) { + reply(550, "Can't change to base directory."); + goto bad; + } else { + if (chdir("/") < 0) { + reply(550, "Root is inaccessible."); + goto bad; + } + lreply(230, "No directory! Logging in with home=/"); + } + } /* * Display a login message, if it exists. @@ -1577,12 +1608,20 @@ skip: #ifdef LOGIN_CAP login_close(lc); #endif + if (chrootdir) + free(chrootdir); + if (residue) + free(residue); return; bad: /* Forget all about it... */ #ifdef LOGIN_CAP login_close(lc); #endif + if (chrootdir) + free(chrootdir); + if (residue) + free(residue); end_login(); } |