Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sshd options for alternative ~/.ssh/environment and ~/.ssh/rc files #466

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ auth_root_allowed(struct ssh *ssh, const char *method)
* This returns a buffer allocated by xmalloc.
*/
char *
expand_authorized_keys(const char *filename, struct passwd *pw)
expand_user_file(const char *filename, struct passwd *pw, const char *filetype)
{
char *file, uidstr[32], ret[PATH_MAX];
int i;
Expand All @@ -400,7 +400,7 @@ expand_authorized_keys(const char *filename, struct passwd *pw)

i = snprintf(ret, sizeof(ret), "%s/%s", pw->pw_dir, file);
if (i < 0 || (size_t)i >= sizeof(ret))
fatal("expand_authorized_keys: path too long");
fatal("expand_user_file (%s): path too long", filetype);
free(file);
return (xstrdup(ret));
}
Expand All @@ -410,7 +410,8 @@ authorized_principals_file(struct passwd *pw)
{
if (options.authorized_principals_file == NULL)
return NULL;
return expand_authorized_keys(options.authorized_principals_file, pw);
return expand_user_file(options.authorized_principals_file, pw,
"AuthorizedPrincipals");
}

/* return ok if key exists in sysfile or userfile */
Expand Down
2 changes: 1 addition & 1 deletion auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ int bsdauth_respond(void *, u_int, char **);
int allowed_user(struct ssh *, struct passwd *);
struct passwd * getpwnamallow(struct ssh *, const char *user);

char *expand_authorized_keys(const char *, struct passwd *pw);
char *expand_user_file(const char *, struct passwd *pw, const char *);
char *authorized_principals_file(struct passwd *);

int auth_key_is_revoked(struct sshkey *);
Expand Down
4 changes: 2 additions & 2 deletions auth2-pubkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -777,8 +777,8 @@ user_key_allowed(struct ssh *ssh, struct passwd *pw, struct sshkey *key,
for (i = 0; !success && i < options.num_authkeys_files; i++) {
if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
continue;
file = expand_authorized_keys(
options.authorized_keys_files[i], pw);
file = expand_user_file(options.authorized_keys_files[i], pw,
"AuthorizedKeys");
success = user_key_allowed2(pw, key, file,
remote_ip, remote_host, &opts);
free(file);
Expand Down
71 changes: 69 additions & 2 deletions servconf.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ initialize_server_options(ServerOptions *options)
options->x11_use_localhost = -1;
options->permit_tty = -1;
options->permit_user_rc = -1;
options->num_user_rc_files = 0;
options->xauth_location = NULL;
options->strict_modes = -1;
options->tcp_keep_alive = -1;
Expand All @@ -141,6 +142,7 @@ initialize_server_options(ServerOptions *options)
options->permit_empty_passwd = -1;
options->permit_user_env = -1;
options->permit_user_env_allowlist = NULL;
options->num_user_env_files = 0;
options->compression = -1;
options->rekey_limit = -1;
options->rekey_interval = -1;
Expand Down Expand Up @@ -332,6 +334,11 @@ fill_default_server_options(ServerOptions *options)
options->permit_tty = 1;
if (options->permit_user_rc == -1)
options->permit_user_rc = 1;
if (options->num_user_rc_files == 0)
opt_array_append("[default]", 0, "UserRCFiles",
&options->user_rc_files,
&options->num_user_rc_files,
_PATH_SSH_USER_RC);
if (options->strict_modes == -1)
options->strict_modes = 1;
if (options->tcp_keep_alive == -1)
Expand Down Expand Up @@ -372,6 +379,11 @@ fill_default_server_options(ServerOptions *options)
options->permit_user_env = 0;
options->permit_user_env_allowlist = NULL;
}
if (options->num_user_env_files == 0)
opt_array_append("[default]", 0, "UserEnvFiles",
&options->user_env_files,
&options->num_user_env_files,
_PATH_SSH_USER_DIR "/environment");
if (options->compression == -1)
#ifdef WITH_ZLIB
options->compression = COMP_DELAYED;
Expand Down Expand Up @@ -508,7 +520,8 @@ typedef enum {
sPrintMotd, sPrintLastLog, sIgnoreRhosts,
sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
sPermitTTY, sStrictModes, sEmptyPasswd, sTCPKeepAlive,
sPermitUserEnvironment, sAllowTcpForwarding, sCompression,
sPermitUserEnvironment, sUserEnvironmentFile,
sAllowTcpForwarding, sCompression,
sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
sIgnoreUserKnownHosts, sCiphers, sMacs, sPidFile, sModuliFile,
sGatewayPorts, sPubkeyAuthentication, sPubkeyAcceptedAlgorithms,
Expand All @@ -526,7 +539,7 @@ typedef enum {
sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum,
sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
sAuthenticationMethods, sHostKeyAgent, sPermitUserRC, sUserRCFile,
sStreamLocalBindMask, sStreamLocalBindUnlink,
sAllowStreamLocalForwarding, sFingerprintHash, sDisableForwarding,
sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider,
Expand Down Expand Up @@ -628,6 +641,7 @@ static struct {
{ "strictmodes", sStrictModes, SSHCFG_GLOBAL },
{ "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
{ "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
{ "userenvironmentfile", sUserEnvironmentFile, SSHCFG_GLOBAL },
{ "uselogin", sDeprecated, SSHCFG_GLOBAL },
{ "compression", sCompression, SSHCFG_GLOBAL },
{ "rekeylimit", sRekeyLimit, SSHCFG_ALL },
Expand Down Expand Up @@ -663,6 +677,7 @@ static struct {
{ "permittunnel", sPermitTunnel, SSHCFG_ALL },
{ "permittty", sPermitTTY, SSHCFG_ALL },
{ "permituserrc", sPermitUserRC, SSHCFG_ALL },
{ "userrcfile", sUserRCFile, SSHCFG_ALL },
{ "match", sMatch, SSHCFG_ALL },
{ "permitopen", sPermitOpen, SSHCFG_ALL },
{ "permitlisten", sPermitListen, SSHCFG_ALL },
Expand Down Expand Up @@ -1673,6 +1688,30 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->permit_user_rc;
goto parse_flag;

/*
* These options can contain %X options expanded at
* connect time, so that you can specify paths like:
*
* UserRCFile /etc/ssh_user_rc/%u
*/
case sUserRCFile:
value = options->num_user_rc_files;
while ((arg = argv_next(&ac, &av)) != NULL) {
if (*arg == '\0') {
error("%s line %d: keyword %s empty argument",
filename, linenum, keyword);
goto out;
}
arg2 = tilde_expand_filename(arg, getuid());
if (*activep && value == 0) {
opt_array_append(filename, linenum, keyword,
&options->user_rc_files,
&options->num_user_rc_files, arg2);
}
free(arg2);
}
break;

case sStrictModes:
intptr = &options->strict_modes;
goto parse_flag;
Expand Down Expand Up @@ -1711,6 +1750,30 @@ process_server_config_line_depth(ServerOptions *options, char *line,
free(p);
break;

/*
* These options can contain %X options expanded at
* connect time, so that you can specify paths like:
*
* UserEnvironmentFile /etc/ssh_user_env/%u
*/
case sUserEnvironmentFile:
value = options->num_user_env_files;
while ((arg = argv_next(&ac, &av)) != NULL) {
if (*arg == '\0') {
error("%s line %d: keyword %s empty argument",
filename, linenum, keyword);
goto out;
}
arg2 = tilde_expand_filename(arg, getuid());
if (*activep && value == 0) {
opt_array_append(filename, linenum, keyword,
&options->user_env_files,
&options->num_user_env_files, arg2);
}
free(arg2);
}
break;

case sCompression:
intptr = &options->compression;
multistate_ptr = multistate_compression;
Expand Down Expand Up @@ -3175,6 +3238,10 @@ dump_config(ServerOptions *o)
/* string array arguments */
dump_cfg_strarray_oneline(sAuthorizedKeysFile, o->num_authkeys_files,
o->authorized_keys_files);
dump_cfg_strarray_oneline(sUserRCFile, o->num_user_rc_files,
o->user_rc_files);
dump_cfg_strarray_oneline(sUserEnvironmentFile, o->num_user_env_files,
o->user_env_files);
dump_cfg_strarray(sHostKeyFile, o->num_host_key_files,
o->host_key_files);
dump_cfg_strarray(sHostCertificate, o->num_host_cert_files,
Expand Down
7 changes: 7 additions & 0 deletions servconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ typedef struct {
u_int num_channel_timeouts;

int unused_connection_timeout;

u_int num_user_env_files; /* alternative .ssh/environment files */
u_int num_user_rc_files; /* alternative .ssh/rc files */
char **user_env_files;
char **user_rc_files;
} ServerOptions;

/* Information about the incoming connection as used by Match */
Expand Down Expand Up @@ -281,6 +286,8 @@ TAILQ_HEAD(include_list, include_item);
M_CP_STROPT(routing_domain); \
M_CP_STROPT(permit_user_env_allowlist); \
M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \
M_CP_STRARRAYOPT(user_rc_files, num_user_rc_files); \
M_CP_STRARRAYOPT(user_env_files, num_user_env_files); \
M_CP_STRARRAYOPT(allow_users, num_allow_users); \
M_CP_STRARRAYOPT(deny_users, num_deny_users); \
M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \
Expand Down
97 changes: 74 additions & 23 deletions session.c
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,48 @@ check_quietlogin(Session *s, const char *command)
return 0;
}

/*
* Check that any custom user environment/rc file is "safe" to run, i.e.
* - If the file is located in the logged-in user's $HOME, it is owned by the
* user and accessible *only* to the user
* - If the file is located outside the user's $HOME, it is owned by the super
* user
*/
static int
safe_user_file(const char *filename, struct passwd *pw)
{
struct stat st;
char *path;
int safe = 0;

if (stat(filename, &st) == -1)
return safe;

path = realpath(filename, NULL);
if (strncmp(path, pw->pw_dir, strlen(pw->pw_dir)) == 0) {
if (st.st_uid != pw->pw_uid) {
fprintf(stderr, "Bad owner on %s, ignoring.\n", path);
goto out;
}
if ((st.st_mode & 077) != 0) {
fprintf(stderr,
"Permissions too open (%3o) on %s, ignoring.\n",
st.st_mode & 0777u, path);
goto out;
}
safe = 1;
goto out;
}
if (st.st_uid != 0 || st.st_gid != 0) {
fprintf(stderr, "%s not root-owned, ignoring.\n", path);
goto out;
}
safe = 1;
out:
free(path);
return safe;
}

/*
* Reads environment variables from the given file and adds/overrides them
* into the environment. If the file does not exist, this does nothing.
Expand Down Expand Up @@ -984,7 +1026,7 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
char buf[256];
size_t n;
u_int i, envsize;
char *ocp, *cp, *value, **env, *laddr;
char *ocp, *cp, *value, **env, *laddr, *userenv = NULL;
struct passwd *pw = s->pw;
#if !defined (HAVE_LOGIN_CAP) && !defined (HAVE_CYGWIN)
char *path = NULL;
Expand Down Expand Up @@ -1116,12 +1158,16 @@ do_setup_env(struct ssh *ssh, Session *s, const char *shell)
}
}

/* read $HOME/.ssh/environment. */
/* read user environment files. */
if (options.permit_user_env) {
snprintf(buf, sizeof buf, "%.200s/%s/environment",
pw->pw_dir, _PATH_SSH_USER_DIR);
read_environment_file(&env, &envsize, buf,
options.permit_user_env_allowlist);
for (i = 0; i < options.num_user_env_files; i++) {
userenv = expand_user_file(options.user_env_files[i], pw,
"UserEnvironment");
if (safe_user_file(userenv, pw))
read_environment_file(&env, &envsize, userenv,
options.permit_user_env_allowlist);
}
free(userenv);
}

#ifdef USE_PAM
Expand Down Expand Up @@ -1204,29 +1250,34 @@ do_rc_files(struct ssh *ssh, Session *s, const char *shell)
char *cmd = NULL, *user_rc = NULL;
int do_xauth;
struct stat st;
u_int i;

do_xauth =
s->display != NULL && s->auth_proto != NULL && s->auth_data != NULL;
xasprintf(&user_rc, "%s/%s", s->pw->pw_dir, _PATH_SSH_USER_RC);

/* ignore _PATH_SSH_USER_RC for subsystems and admin forced commands */
if (!s->is_subsystem && options.adm_forced_command == NULL &&
auth_opts->permit_user_rc && options.permit_user_rc &&
stat(user_rc, &st) >= 0) {
if (xasprintf(&cmd, "%s -c '%s %s'", shell, _PATH_BSHELL,
user_rc) == -1)
fatal_f("xasprintf: %s", strerror(errno));
if (debug_flag)
fprintf(stderr, "Running %s\n", cmd);
f = popen(cmd, "w");
if (f) {
if (do_xauth)
fprintf(f, "%s %s\n", s->auth_proto,
s->auth_data);
pclose(f);
} else
fprintf(stderr, "Could not run %s\n",
user_rc);
auth_opts->permit_user_rc && options.permit_user_rc) {
for (i = 0; i < options.num_user_rc_files; i++) {
user_rc = expand_user_file(options.user_rc_files[i],
s->pw, "UserRC");
if (safe_user_file(user_rc, s->pw)) {
if (xasprintf(&cmd, "%s -c '%s %s'", shell,
_PATH_BSHELL, user_rc) == -1)
fatal_f("xasprintf: %s", strerror(errno));
if (debug_flag)
fprintf(stderr, "Running %s\n", cmd);
f = popen(cmd, "w");
if (f) {
if (do_xauth)
fprintf(f, "%s %s\n", s->auth_proto,
s->auth_data);
pclose(f);
} else
fprintf(stderr, "Could not run %s\n",
user_rc);
}
}
} else if (stat(_PATH_SSH_SYSTEM_RC, &st) >= 0) {
if (debug_flag)
fprintf(stderr, "Running %s %s\n", _PATH_BSHELL,
Expand Down