summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libexec/ftpd/ftpchroot.520
-rw-r--r--libexec/ftpd/ftpcmd.y6
-rw-r--r--libexec/ftpd/ftpd.811
-rw-r--r--libexec/ftpd/ftpd.c105
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();
}
OpenPOWER on IntegriCloud