Directive described in sshd_config (5). Also remove chdir warning skip code both because this can be handled with more flexibility with this added directive and were broken since a56086b9903b62c1c4fdedf01b68338fe4dc90e4. --- regress/Makefile | 5 +++-- regress/sftp-chdir.sh | 30 ++++++++++++++++++++++++++++++ servconf.c | 15 ++++++++++++++- servconf.h | 2 ++ session.c | 23 +++++++++++++++-------- sshd_config | 1 + sshd_config.5 | 16 +++++++++++++++- 7 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 regress/sftp-chdir.sh diff --git a/regress/Makefile b/regress/Makefile index cba83f4..395241a 100644 --- a/regress/Makefile +++ b/regress/Makefile @@ -11,7 +11,7 @@ prep: clean: for F in $(CLEANFILES); do rm -f $(OBJ)$$F; done - test -z "${SUDO}" || ${SUDO} rm -f ${SUDO_CLEAN} + test -z "${SUDO}" || ${SUDO} rm -df ${SUDO_CLEAN} rm -rf $(OBJ).putty distclean: clean @@ -43,6 +43,7 @@ LTESTS= connect \ scp \ sftp \ sftp-chroot \ + sftp-chdir \ sftp-cmds \ sftp-badcmds \ sftp-batch \ @@ -107,7 +108,7 @@ CLEANFILES= t2.out t3.out t6.out1 t6.out2 t7.out t7.out.pub copy.1 copy.2 \ key.ed25519-512.pub netcat host_krl_* host_revoked_* \ kh.* user_*key* agent-key.* known_hosts.* hkr.* -SUDO_CLEAN+= /var/run/testdata_${USER} /var/run/keycommand_${USER} +SUDO_CLEAN+= /var/run/testdata_${USER} /var/run/testlanddir_${USER}{/testdata_${USER},} /var/run/keycommand_${USER} # Enable all malloc(3) randomisations and checks TEST_ENV= "MALLOC_OPTIONS=AFGJPRX" diff --git a/regress/sftp-chdir.sh b/regress/sftp-chdir.sh new file mode 100644 index 0000000..46f5759 --- /dev/null +++ b/regress/sftp-chdir.sh @@ -0,0 +1,30 @@ +# $OpenBSD: sftp-chroot.sh,v 1.4 2014/01/20 00:00:30 dtucker Exp $ +# Placed in the Public Domain. + +tid="sftp in chroot with chdir" + +CHROOT=/var/run +CHDIR=testlanddir_${USER} +CHDIROUT=${CHROOT}/${CHDIR} +FILENAME=testdata_${USER} +PRIVDATA=${CHDIROUT}/${FILENAME} + +if [ -z "$SUDO" ]; then + echo "skipped: need SUDO to create file in /var/run, test won't work without" + exit 0 +fi + +$SUDO sh -c "mkdir -p $CHDIROUT && echo mekmitastdigoat > $PRIVDATA" || \ + fatal "create $PRIVDATA failed" + +start_sshd -oChrootDirectory=$CHROOT -oForceCommand="internal-sftp" -oChangeDirectory=$CHDIR + +verbose "test $tid: get" +${SFTP} -S "$SSH" -F $OBJ/ssh_config host:${FILENAME} $COPY \ + >>$TEST_REGRESS_LOGFILE 2>&1 || \ + fatal "Fetch ${FILENAME} failed" +cmp $PRIVDATA $COPY || fail "$PRIVDATA $COPY differ" + +$SUDO rm $PRIVDATA +$SUDO rmdir $CHDIROUT + diff --git a/servconf.c b/servconf.c index eb32db0..6812c94 100644 --- a/servconf.c +++ b/servconf.c @@ -167,6 +167,7 @@ initialize_server_options(ServerOptions *options) options->ip_qos_bulk = -1; options->version_addendum = NULL; options->fingerprint_hash = -1; + options->change_directory = NULL; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ @@ -411,7 +412,7 @@ typedef enum { sAuthorizedKeysCommand, sAuthorizedKeysCommandUser, sAuthenticationMethods, sHostKeyAgent, sPermitUserRC, sStreamLocalBindMask, sStreamLocalBindUnlink, - sAllowStreamLocalForwarding, sFingerprintHash, + sAllowStreamLocalForwarding, sFingerprintHash, sChangeDirectory, sDeprecated, sUnsupported } ServerOpCodes; @@ -549,6 +550,7 @@ static struct { { "streamlocalbindunlink", sStreamLocalBindUnlink, SSHCFG_ALL }, { "allowstreamlocalforwarding", sAllowStreamLocalForwarding, SSHCFG_ALL }, { "fingerprinthash", sFingerprintHash, SSHCFG_GLOBAL }, + { "changedirectory", sChangeDirectory, SSHCFG_ALL }, { NULL, sBadOption, 0 } }; @@ -1826,6 +1828,15 @@ process_server_config_line(ServerOptions *options, char *line, options->fingerprint_hash = value; break; + case sChangeDirectory: + arg = strdelim(&cp); + if (!arg || *arg == '\0') + fatal("%s line %d: missing directory name.", + filename, linenum); + if (*activep && !options->change_directory) + options->change_directory = xstrdup(arg); + break; + case sDeprecated: logit("%s line %d: Deprecated option %s", filename, linenum, arg); @@ -2007,6 +2018,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) M_CP_STROPT(adm_forced_command); M_CP_STROPT(chroot_directory); + M_CP_STROPT(change_directory); } #undef M_CP_INTOPT @@ -2281,6 +2293,7 @@ dump_config(ServerOptions *o) o->hostbased_key_types : KEX_DEFAULT_PK_ALG); dump_cfg_string(sPubkeyAcceptedKeyTypes, o->pubkey_key_types ? o->pubkey_key_types : KEX_DEFAULT_PK_ALG); + dump_cfg_string(sChangeDirectory, o->change_directory); /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); diff --git a/servconf.h b/servconf.h index 606d80c..02089e1 100644 --- a/servconf.h +++ b/servconf.h @@ -194,6 +194,8 @@ typedef struct { char *auth_methods[MAX_AUTH_METHODS]; int fingerprint_hash; + + char *change_directory; } ServerOptions; /* Information about the incoming connection as used by Match */ diff --git a/session.c b/session.c index 5a64715..6955737 100644 --- a/session.c +++ b/session.c @@ -1670,6 +1670,7 @@ do_child(Session *s, const char *command) char *argv[ARGV_MAX]; const char *shell, *shell0, *hostname = NULL; struct passwd *pw = s->pw; + char *landingdir; int r = 0; /* remove hostkey from the child's memory */ @@ -1784,20 +1785,26 @@ do_child(Session *s, const char *command) } #endif - /* Change current directory to the user's home directory. */ - if (chdir(pw->pw_dir) < 0) { - /* Suppress missing homedir warning for chroot case */ + /* Change current directory to landing directory (either home or + * set by config). */ + if (!options.change_directory || !strcasecmp(options.change_directory, "none")) + landingdir = pw->pw_dir; + else + landingdir = percent_expand(options.change_directory, + "h", pw->pw_dir, "u", pw->pw_name, (char*)NULL); + if (chdir(landingdir) < 0) { #ifdef HAVE_LOGIN_CAP r = login_getcapbool(lc, "requirehome", 0); #endif - if (r || options.chroot_directory == NULL || - strcasecmp(options.chroot_directory, "none") == 0) - fprintf(stderr, "Could not chdir to home " - "directory %s: %s\n", pw->pw_dir, - strerror(errno)); + fprintf(stderr, "Could not chdir to landing " + "directory %s: %s\n", landingdir, + strerror(errno)); + if (r) exit(1); } + if (options.change_directory && strcasecmp(options.change_directory, "none")) + free(landingdir); closefrom(STDERR_FILENO + 1); diff --git a/sshd_config b/sshd_config index cf7d8e1..e850f0d 100644 --- a/sshd_config +++ b/sshd_config @@ -118,6 +118,7 @@ UsePrivilegeSeparation sandbox # Default for new installations. #PermitTunnel no #ChrootDirectory none #VersionAddendum none +#ChangeDirectory none # no default banner path #Banner none diff --git a/sshd_config.5 b/sshd_config.5 index 5ab4318..539c229 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -378,6 +378,17 @@ PAM or through authentication styles supported in .Xr login.conf 5 ) The default is .Dq yes . +.It Cm ChangeDirectory +Specifies the pathname of a directory to +.Xr chdir 2 +to after authentication, and after entering the chroot if +.Cm ChrootDirectory +were specified. +.Pp +The pathname may contain the following tokens that are expanded at runtime once +the connecting user has been authenticated: %% is replaced by a literal '%', +%h is replaced by the home directory of the user being authenticated, and +%u is replaced by the username of that user. .It Cm ChrootDirectory Specifies the pathname of a directory to .Xr chroot 2 @@ -388,7 +399,9 @@ checks that all components of the pathname are root-owned directories which are not writable by any other user or group. After the chroot, .Xr sshd 8 -changes the working directory to the user's home directory. +changes the working directory to the user's home directory (by default), or +to directory specified with +.Cm ChangeDirectory . .Pp The pathname may contain the following tokens that are expanded at runtime once the connecting user has been authenticated: %% is replaced by a literal '%', @@ -1041,6 +1054,7 @@ Available keywords are .Cm AuthorizedKeysFile , .Cm AuthorizedPrincipalsFile , .Cm Banner , +.Cm ChangeDirectory , .Cm ChrootDirectory , .Cm DenyGroups , .Cm DenyUsers , -- 2.0.5 _______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev