Give the code around chroot(2)/chdir(2) a major overhaul by
separating its part around chroot(2) from that around initial chdir(2). This makes the below changes really easy. Move seteuid(to user's uid) to before calling chdir(2). There are two goals to achieve by that. First, NFS mounted home directories with restrictive permissions become accessible (local superuser can't access them if not mapped to uid 0 on the remote side explicitly.) Second, all the permissions to the home directory pathname components become effective; previously a user could be carried to any local directory despite its permissions since the chdir(2) was done with euid 0. This reduces possible impact from FTP server misconfiguration, e.g., assigning a wrong home directory to a user. Implement the "/./" feature. Now a guest or user subject to chrooting may have "/./" in his login directory, which separates his chroot directory from his home directory inside the chrooted environment. This works for ftpchroot(5) as well. PR: bin/17843 bin/23944
This commit is contained in:
parent
76499f1539
commit
ce9287fc02
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=110036
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 @@ pass(char *passwd)
|
||||
|| login_getcapbool(lc, "ftp-chroot", 0)
|
||||
#endif
|
||||
;
|
||||
if (guest) {
|
||||
/*
|
||||
* We MUST do a chdir() after the chroot. Otherwise
|
||||
* the old current directory will be accessible as "."
|
||||
* outside the new root!
|
||||
*/
|
||||
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)
|
||||
fatalerror("Ran out of memory.");
|
||||
free(residue);
|
||||
residue = chrootdir;
|
||||
}
|
||||
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)
|
||||
chrootdir = pw->pw_dir;
|
||||
if (chroot(chrootdir) < 0 || chdir("/") < 0) {
|
||||
fatalerror("Ran out of memory.");
|
||||
|
||||
}
|
||||
if (guest || dochroot) {
|
||||
/*
|
||||
* If no chroot directory set yet, use the login directory.
|
||||
* Copy it so it can be modified while pw->pw_dir stays intact.
|
||||
*/
|
||||
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.");
|
||||
} else {
|
||||
/*
|
||||
* We MUST do a chdir() after the chroot. Otherwise
|
||||
* the old current directory will be accessible as "."
|
||||
* outside the new root!
|
||||
*/
|
||||
homedir = "/";
|
||||
}
|
||||
/*
|
||||
* 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 @@ pass(char *passwd)
|
||||
#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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user