[PATCH] features for restricted shell environments
Tony Finch
dot at dotat.at
Thu Jan 30 08:40:20 EST 2003
The patch below implements a couple of features which are useful
in an environment where users do not have a regular shell login.
It allows you to selectively disable certain features on a
system-wide level for users with a certain shell; it also allows
you to control and audit TCP forwarding in more detail.
Our system is an email server with a menu for the login shell;
we selectively allow port forwarding for users to connect to the
IMAP server etc. and prevent users from escaping via ~/.ssh/rc.
This patch may also be useful with secure CVS servers.
Tony.
--
f.a.n.finch <dot at dotat.at> http://dotat.at/
FISHER GERMAN BIGHT: NORTHWESTERLY 6 OR 7, OCCASIONALLY GALE 8 IN WEST,
VEERING NORTHEASTERLY 5 LATER. WINTRY SHOWERS. GOOD.
--- auth-options.c 28 Jan 2003 18:06:50 -0000 1.1.1.2
+++ auth-options.c 29 Jan 2003 20:39:19 -0000 1.7
@@ -133,7 +135,7 @@
goto next_option;
}
cp = "environment=\"";
- if (options.permit_user_env &&
+ if (!auth_restricted(RESTRICT_ENV, pw) &&
strncasecmp(opts, cp, strlen(cp)) == 0) {
char *s;
struct envstring *new_envstring;
@@ -217,8 +219,6 @@
}
cp = "permitopen=\"";
if (strncasecmp(opts, cp, strlen(cp)) == 0) {
- char host[256], sport[6];
- u_short port;
char *patterns = xmalloc(strlen(opts) + 1);
opts += strlen(cp);
@@ -243,8 +243,7 @@
}
patterns[i] = 0;
opts++;
- if (sscanf(patterns, "%255[^:]:%5[0-9]", host, sport) != 2 &&
- sscanf(patterns, "%255[^/]/%5[0-9]", host, sport) != 2) {
+ if (channel_add_permitted_opens(patterns) < 0) {
debug("%.100s, line %lu: Bad permitopen specification "
"<%.100s>", file, linenum, patterns);
auth_debug_add("%.100s, line %lu: "
@@ -252,16 +251,6 @@
xfree(patterns);
goto bad_option;
}
- if ((port = a2port(sport)) == 0) {
- debug("%.100s, line %lu: Bad permitopen port <%.100s>",
- file, linenum, sport);
- auth_debug_add("%.100s, line %lu: "
- "Bad permitopen port", file, linenum);
- xfree(patterns);
- goto bad_option;
- }
- if (options.allow_tcp_forwarding)
- channel_add_permitted_opens(host, port);
xfree(patterns);
goto next_option;
}
--- auth-pam.c 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ auth-pam.c 29 Jan 2003 20:39:19 -0000 1.2
@@ -358,7 +360,7 @@
no_port_forwarding_flag &= ~2;
no_agent_forwarding_flag &= ~2;
no_x11_forwarding_flag &= ~2;
- if (!no_port_forwarding_flag && options.allow_tcp_forwarding)
+ if (!auth_restricted(RESTRICT_TCP, auth_get_user()))
channel_permit_all_opens();
#endif
}
--- auth.c 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ auth.c 29 Jan 2003 21:26:11 -0000 1.4
@@ -291,6 +293,31 @@
return 0;
}
+/*
+ * Is the user subject to this restriction?
+ */
+int
+auth_restricted(int restriction, struct passwd *pw)
+{
+ debug2("user shell is %s", pw->pw_shell);
+ if ((options.restrictions & restriction) &&
+ options.restricted_shell != NULL &&
+ strcmp(options.restricted_shell, pw->pw_shell) == 0) {
+ debug("Restricted shell (%d)", restriction);
+ return 1;
+ } else if ((restriction & RESTRICT_AGENT) && no_agent_forwarding_flag)
+ return 1;
+ else if ((restriction & RESTRICT_ENV) && !options.permit_user_env)
+ return 1;
+ else if ((restriction & RESTRICT_TCP) &&
+ (!options.allow_tcp_forwarding || no_port_forwarding_flag))
+ return 1;
+ else if ((restriction & RESTRICT_X11) &&
+ (!options.x11_forwarding || no_x11_forwarding_flag))
+ return 1;
+ else
+ return 0;
+}
/*
* Given a template and a passwd structure, build a filename
--- auth.h 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ auth.h 29 Jan 2003 20:39:19 -0000 1.3
@@ -142,6 +143,7 @@
void auth_log(Authctxt *, int, char *, char *);
void userauth_finish(Authctxt *, int, char *);
int auth_root_allowed(char *);
+int auth_restricted(int, struct passwd *);
char *auth2_read_banner(void);
--- channels.c 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ channels.c 28 Jan 2003 19:06:35 -0000 1.4
@@ -96,6 +98,10 @@
/* Number of permitted host/port pairs in the array. */
static int num_permitted_opens = 0;
+
+/* Don't allow any more to be added. */
+static int fix_permitted_opens = 0;
+
/*
* If this is true, all opens are permitted. This is the case on the server
* on which we have to trust the client anyway, and the user could do
@@ -1972,7 +1978,7 @@
}
void
-channel_input_port_open(int type, u_int32_t seq, void *ctxt)
+channel_input_port_open(int type, u_int32_t seq, void *ctxt, int loud)
{
Channel *c = NULL;
u_short host_port;
@@ -1989,6 +1995,8 @@
originator_string = xstrdup("unknown (remote did not supply name)");
}
packet_check_eom();
+ if (loud)
+ log("TCP forwarding connection to %s port %d", host, host_port);
sock = channel_connect_to(host, host_port);
if (sock != -1) {
c = channel_new("connected socket",
@@ -2004,6 +2012,18 @@
xfree(host);
}
+void
+channel_input_port_open_quiet(int type, u_int32_t seq, void *ctxt)
+{
+ channel_input_port_open(type, seq, ctxt, 0);
+}
+
+void
+channel_input_port_open_loud(int type, u_int32_t seq, void *ctxt)
+{
+ channel_input_port_open(type, seq, ctxt, 1);
+}
+
/* -- tcp forwarding */
@@ -2209,6 +2229,8 @@
port);
#endif
/* Initiate forwarding */
+ log("TCP forwarding listening on port %d %s", port,
+ gateway_ports ? "open" : "private");
channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports);
/* Free the argument string. */
@@ -2227,10 +2249,31 @@
all_opens_permitted = 1;
}
+/*
+ * If the server-wide configuration specifies some permitted_opens
+ * then don't allow users to add to them.
+ */
void
-channel_add_permitted_opens(char *host, int port)
+channel_fix_permitted_opens(void)
{
- if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
+ if (num_permitted_opens != 0)
+ fix_permitted_opens = 1;
+}
+
+int
+channel_add_permitted_opens(char *hostport)
+{
+ char host[256], sport[6];
+ u_short port;
+
+ if (sscanf(hostport, "%255[^:]:%5[0-9]", host, sport) != 2 &&
+ sscanf(hostport, "%255[^/]/%5[0-9]", host, sport) != 2)
+ return -1;
+ if ((port = a2port(sport)) == 0)
+ return -1;
+
+ if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION ||
+ fix_permitted_opens)
fatal("channel_request_remote_forwarding: too many forwards");
debug("allow port forwarding to host %s port %d", host, port);
@@ -2239,6 +2282,7 @@
num_permitted_opens++;
all_opens_permitted = 0;
+ return 0;
}
void
@@ -2246,6 +2290,8 @@
{
int i;
+ if (fix_permitted_opens)
+ return;
for (i = 0; i < num_permitted_opens; i++)
xfree(permitted_opens[i].host_to_connect);
num_permitted_opens = 0;
@@ -2448,6 +2494,7 @@
0, xstrdup("X11 inet listener"), 1);
nc->single_connection = single_connection;
}
+ log("X11 forwarding listening on port %d", 6000+display_number);
/* Return the display number for the DISPLAY environment variable. */
*display_numberp = display_number;
--- channels.h 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ channels.h 28 Jan 2003 19:06:35 -0000 1.4
@@ -176,7 +177,9 @@
void channel_input_oclose(int, u_int32_t, void *);
void channel_input_open_confirmation(int, u_int32_t, void *);
void channel_input_open_failure(int, u_int32_t, void *);
-void channel_input_port_open(int, u_int32_t, void *);
+void channel_input_port_open(int, u_int32_t, void *, int);
+void channel_input_port_open_loud(int, u_int32_t, void *);
+void channel_input_port_open_quiet(int, u_int32_t, void *);
void channel_input_window_adjust(int, u_int32_t, void *);
/* file descriptor handling (read/write) */
@@ -194,7 +197,8 @@
/* tcp forwarding */
void channel_set_af(int af);
void channel_permit_all_opens(void);
-void channel_add_permitted_opens(char *, int);
+void channel_fix_permitted_opens(void);
+int channel_add_permitted_opens(char *);
void channel_clear_permitted_opens(void);
void channel_input_port_forward_request(int, int);
int channel_connect_to(const char *, u_short);
--- clientloop.c 28 Jan 2003 18:06:51 -0000 1.1.1.2
+++ clientloop.c 28 Jan 2003 19:06:35 -0000 1.3
@@ -1342,7 +1344,7 @@
dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data);
dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation);
dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure);
- dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open);
+ dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open_quiet);
dispatch_set(SSH_SMSG_EXITSTATUS, &client_input_exit_status);
dispatch_set(SSH_SMSG_STDERR_DATA, &client_input_stderr_data);
dispatch_set(SSH_SMSG_STDOUT_DATA, &client_input_stdout_data);
--- servconf.c 28 Jan 2003 18:06:52 -0000 1.1.1.2
+++ servconf.c 29 Jan 2003 21:26:11 -0000 1.8
@@ -39,6 +41,7 @@
#include "cipher.h"
#include "kex.h"
#include "mac.h"
+#include "channels.h"
static void add_listen_addr(ServerOptions *, char *, u_short);
static void add_one_listen_addr(ServerOptions *, char *, u_short);
@@ -102,6 +105,9 @@
options->challenge_response_authentication = -1;
options->permit_empty_passwd = -1;
options->permit_user_env = -1;
+ options->permit_tcp_listen = -1;
+ options->restricted_shell = NULL;
+ options->restrictions = -1;
options->use_login = -1;
options->compression = -1;
options->allow_tcp_forwarding = -1;
@@ -226,6 +232,10 @@
options->permit_empty_passwd = 0;
if (options->permit_user_env == -1)
options->permit_user_env = 0;
+ if (options->permit_tcp_listen == -1)
+ options->permit_tcp_listen = 1;
+ if (options->restrictions == -1)
+ options->restrictions = 0;
if (options->use_login == -1)
options->use_login = 0;
if (options->compression == -1)
@@ -234,6 +244,7 @@
options->allow_tcp_forwarding = 1;
if (options->gateway_ports == -1)
options->gateway_ports = 0;
+ channel_fix_permitted_opens();
if (options->max_startups == -1)
options->max_startups = 10;
if (options->max_startups_rate == -1)
@@ -294,6 +305,7 @@
sPrintMotd, sPrintLastLog, sIgnoreRhosts,
sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
sStrictModes, sEmptyPasswd, sKeepAlives,
+ sPermitTcpConnect, sPermitTcpListen, sRestrictedShell,
sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
@@ -355,6 +367,7 @@
{ "x11displayoffset", sX11DisplayOffset },
{ "x11uselocalhost", sX11UseLocalhost },
{ "xauthlocation", sXAuthLocation },
+ { "restrictedshell", sRestrictedShell },
{ "strictmodes", sStrictModes },
{ "permitemptypasswords", sEmptyPasswd },
{ "permituserenvironment", sPermitUserEnvironment },
@@ -362,6 +375,8 @@
{ "compression", sCompression },
{ "keepalive", sKeepAlives },
{ "allowtcpforwarding", sAllowTcpForwarding },
+ { "permittcpconnect", sPermitTcpConnect },
+ { "permittcplisten", sPermitTcpListen },
{ "allowusers", sAllowUsers },
{ "denyusers", sDenyUsers },
{ "allowgroups", sAllowGroups },
@@ -705,6 +720,30 @@
charptr = &options->xauth_location;
goto parse_filename;
+ case sRestrictedShell:
+ arg = strdelim(&cp);
+ if (!arg || *arg == '\0')
+ fatal("%s line %d: missing restrictions.",
+ filename, linenum);
+ options->restrictions = 0;
+ while ((p = strsep(&arg, ",")) != NULL) {
+ if (strcasecmp(p, "agent") == 0)
+ options->restrictions |= RESTRICT_AGENT;
+ else if (strcasecmp(p, "env") == 0)
+ options->restrictions |= RESTRICT_ENV;
+ else if (strcasecmp(p, "rc") == 0)
+ options->restrictions |= RESTRICT_RC;
+ else if (strcasecmp(p, "tcp") == 0)
+ options->restrictions |= RESTRICT_TCP;
+ else if (strcasecmp(p, "x11") == 0)
+ options->restrictions |= RESTRICT_X11;
+ else
+ fatal("%s line %d: unknown restriction %s.",
+ filename, linenum, p);
+ }
+ charptr = &options->restricted_shell;
+ goto parse_filename;
+
case sStrictModes:
intptr = &options->strict_modes;
goto parse_flag;
@@ -761,6 +800,22 @@
case sAllowTcpForwarding:
intptr = &options->allow_tcp_forwarding;
+ goto parse_flag;
+
+ case sPermitTcpConnect:
+ arg = strdelim(&cp);
+ p = NULL;
+ if (!arg || *arg == '\0')
+ p = "missing";
+ if (channel_add_permitted_opens(arg) < 0)
+ p = "bad";
+ if (p != NULL)
+ fatal("%.200s, line %d: %s inet addr:port.",
+ filename, linenum, p);
+ break;
+
+ case sPermitTcpListen:
+ intptr = &options->permit_tcp_listen;
goto parse_flag;
case sUsePrivilegeSeparation:
--- servconf.h 28 Jan 2003 18:06:52 -0000 1.1.1.2
+++ servconf.h 29 Jan 2003 21:26:12 -0000 1.7
@@ -32,6 +33,13 @@
#define PERMIT_NO_PASSWD 2
#define PERMIT_YES 3
+/* restrictions */
+#define RESTRICT_AGENT 1
+#define RESTRICT_ENV 2
+#define RESTRICT_RC 4
+#define RESTRICT_TCP 8
+#define RESTRICT_X11 16
+
typedef struct {
u_int num_ports;
@@ -98,6 +106,9 @@
int permit_empty_passwd; /* If false, do not permit empty
* passwords. */
int permit_user_env; /* If true, read ~/.ssh/environment */
+ int permit_tcp_listen; /* If true allow -R forwarding */
+ char *restricted_shell; /* Restrict users with this shell */
+ int restrictions; /* How they are restricted */
int use_login; /* If true, login(1) is used */
int compression; /* If true, compression is allowed */
int allow_tcp_forwarding;
--- serverloop.c 28 Jan 2003 18:06:52 -0000 1.1.1.2
+++ serverloop.c 29 Jan 2003 21:26:12 -0000 1.5
@@ -863,8 +865,7 @@
originator_port = packet_get_int();
packet_check_eom();
- debug("server_request_direct_tcpip: originator %s port %d, target %s port %d",
- originator, originator_port, target, target_port);
+ log("TCP forwarding connection to %s port %d", target, target_port);
/* XXX check permission */
sock = channel_connect_to(target, target_port);
@@ -973,12 +974,10 @@
fatal("server_input_global_request: no user");
listen_address = packet_get_string(NULL); /* XXX currently ignored */
listen_port = (u_short)packet_get_int();
- debug("server_input_global_request: tcpip-forward listen %s port %d",
- listen_address, listen_port);
/* check permissions */
- if (!options.allow_tcp_forwarding ||
- no_port_forwarding_flag
+ if (!options.permit_tcp_listen ||
+ auth_restricted(RESTRICT_TCP, pw)
#ifndef NO_IPPORT_RESERVED_CONCEPT
|| (listen_port < IPPORT_RESERVED && pw->pw_uid != 0)
#endif
@@ -987,6 +986,8 @@
packet_send_debug("Server has disabled port forwarding.");
} else {
/* Start listening on the port */
+ log("TCP forwarding listening on %s port %d",
+ listen_address, listen_port);
success = channel_setup_remote_fwd_listener(
listen_address, listen_port, options.gateway_ports);
}
@@ -1061,7 +1062,7 @@
dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data);
dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation);
dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure);
- dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open);
+ dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open_loud);
}
static void
server_init_dispatch_15(void)
--- session.c 28 Jan 2003 18:06:52 -0000 1.1.1.2
+++ session.c 29 Jan 2003 20:39:20 -0000 1.7
@@ -212,7 +214,7 @@
}
/* setup the channel layer */
- if (!no_port_forwarding_flag && options.allow_tcp_forwarding)
+ if (!auth_restricted(RESTRICT_TCP, authctxt->pw))
channel_permit_all_opens();
if (compat20)
@@ -312,7 +314,7 @@
break;
case SSH_CMSG_AGENT_REQUEST_FORWARDING:
- if (no_agent_forwarding_flag || compat13) {
+ if (auth_restricted(RESTRICT_AGENT, s->pw) || compat13) {
debug("Authentication agent forwarding not permitted for this authentication.");
break;
}
@@ -321,11 +323,7 @@
break;
case SSH_CMSG_PORT_FORWARD_REQUEST:
- if (no_port_forwarding_flag) {
- debug("Port forwarding not permitted for this authentication.");
- break;
- }
- if (!options.allow_tcp_forwarding) {
+ if (auth_restricted(RESTRICT_TCP, s->pw)) {
debug("Port forwarding not permitted.");
break;
}
@@ -1085,7 +1083,7 @@
auth_sock_name);
/* read $HOME/.ssh/environment. */
- if (options.permit_user_env && !options.use_login) {
+ if (!options.use_login && !auth_restricted(RESTRICT_ENV, pw)) {
snprintf(buf, sizeof buf, "%.200s/.ssh/environment",
strcmp(pw->pw_dir, "/") ? pw->pw_dir : "");
read_environment_file(&env, &envsize, buf);
@@ -1102,6 +1100,10 @@
/*
* Run $HOME/.ssh/rc, /etc/ssh/sshrc, or xauth (whichever is found
* first in this order).
+ *
+ * A properly-implemented restricted shell doesn't need the
+ * restriction tests, but they're useful for reducing the
+ * amount of noise in the process accounting logs.
*/
static void
do_rc_files(Session *s, const char *shell)
@@ -1111,11 +1113,12 @@
int do_xauth;
struct stat st;
- do_xauth =
+ do_xauth = !auth_restricted(RESTRICT_X11, s->pw) &&
s->display != NULL && s->auth_proto != NULL && s->auth_data != NULL;
/* ignore _PATH_SSH_USER_RC for subsystems */
- if (!s->is_subsystem && (stat(_PATH_SSH_USER_RC, &st) >= 0)) {
+ if (!s->is_subsystem && !auth_restricted(RESTRICT_RC, s->pw) &&
+ (stat(_PATH_SSH_USER_RC, &st) >= 0)) {
snprintf(cmd, sizeof cmd, "%s -c '%s %s'",
shell, _PATH_BSHELL, _PATH_SSH_USER_RC);
if (debug_flag)
@@ -1723,8 +1726,8 @@
{
static int called = 0;
packet_check_eom();
- if (no_agent_forwarding_flag) {
- debug("session_auth_agent_req: no_agent_forwarding_flag");
+ if (auth_restricted(RESTRICT_AGENT, s->pw)) {
+ debug("session_auth_agent_req: agent forwarding disabled");
return 0;
}
if (called) {
@@ -2019,12 +2022,8 @@
char display[512], auth_display[512];
char hostname[MAXHOSTNAMELEN];
- if (no_x11_forwarding_flag) {
- packet_send_debug("X11 forwarding disabled in user configuration file.");
- return 0;
- }
- if (!options.x11_forwarding) {
- debug("X11 forwarding disabled in server configuration file.");
+ if (auth_restricted(RESTRICT_X11, s->pw)) {
+ packet_send_debug("X11 forwarding disabled.");
return 0;
}
if (!options.xauth_location ||
--- sshd_config.5 28 Jan 2003 18:06:53 -0000 1.1.1.2
+++ sshd_config.5 29 Jan 2003 21:26:12 -0000 1.8
@@ -465,6 +466,35 @@
If this option is set to
.Dq no
root is not allowed to login.
+.It Cm PermitTcpConnect
+Restricts TCP forwarding from the client so that
+only certain connection destinations are permitted.
+In the absence of any
+.Cm PermitTcpConnect
+options, any outgoing connection is permitted
+(although per-key restrictions may be imposed by
+.Cm permitopen=""
+options in
+.Pa authorized_keys
+files).
+If
+.Cm PermitTcpConnect
+options are present then
+.Nm sshd
+will only allow connections to the
+.Ar host Ns : Ns Ar port
+pairs that are specified.
+Multiple permitted destinations may be specified using multiple
+.Cm PermitTcpConnect
+options.
+IPv6 addresses may be specified using the syntax
+.Ar host Ns / Ns Ar port
+for the argument instead of
+.Ar host Ns : Ns Ar port .
+.It Cm PermitTcpListen
+Specifies whether TCP forwarding to the client is allowed.
+The default is
+.Dq yes .
.It Cm PermitUserEnvironment
Specifies whether
.Pa ~/.ssh/environment
@@ -533,6 +563,29 @@
The default is
.Dq yes .
Note that this option applies to protocol version 2 only.
+.It Cm RestrictedShell
+This option selectively turns off various features
+for users with the shell specified in the second argument.
+The first argument is a comma-separated list, as follows.
+If
+.Dq agent
+is specified then agent forwarding is disabled.
+If
+.Dq env
+is specified then
+.Cm PermitUserEnvironment
+is turned off.
+If
+.Dq rc
+is specified then
+.Pa ~/.ssh/rc
+is not run.
+If
+.Dq tcp
+is specified then TCP port forwarding is disabled.
+If
+.Dq x11
+is specified then X11 fowarding is disabled.
.It Cm RhostsAuthentication
Specifies whether authentication using rhosts or /etc/hosts.equiv
files is sufficient.
More information about the openssh-unix-dev
mailing list