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.