OpenSSH ControlAllowUsers, et al Patch

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

OpenSSH ControlAllowUsers, et al Patch

William Ahern-2
Attached (and inline) is a patch to add the following config options:

        ControlBindMask
        ControlAllowUsers
        ControlAllowGroups
        ControlDenyUsers
        ControlDenyGroups

It pulls the peer credential check from client_process_control() in ssh.c,
and expounds upon it in a new function, client_control_grant().

Supplemental groups are not checked in this patch. I didn't feel comfortable
taking a shot at defining the semantics of how supplemental groups would
work. groupaccess.c probably has all the code necessary to employ
supplemental group checking.

The patch includes documentation additions, as well. The diff is against
Portable OpenSSH 4.2. That's what we have in our tree at the moment.

Please send any corrections or complaints my way and I'll refactor the patch
as necessary (e.g. if the patch doesn't apply to 4.3 or against OpenBSD
trunk).

For posterity, let it be known that this work was funded by Barracuda
Networks, Inc.

- Bill


Index: scp.0
===================================================================
--- scp.0 (revision 15802)
+++ scp.0 (revision 15803)
@@ -70,6 +70,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    GlobalKnownHostsFile
                    GSSAPIAuthentication
                    GSSAPIDelegateCredentials
Index: scp.1
===================================================================
--- scp.1 (revision 15802)
+++ scp.1 (revision 15803)
@@ -130,6 +130,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
Index: ssh.0
===================================================================
--- ssh.0 (revision 15802)
+++ ssh.0 (revision 15803)
@@ -392,6 +392,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    DynamicForward
                    EscapeChar
                    ForwardAgent
Index: ssh.1
===================================================================
--- ssh.1 (revision 15802)
+++ ssh.1 (revision 15803)
@@ -691,6 +691,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It DynamicForward
 .It EscapeChar
 .It ForwardAgent
Index: sftp.0
===================================================================
--- sftp.0 (revision 15802)
+++ sftp.0 (revision 15803)
@@ -74,6 +74,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    GlobalKnownHostsFile
                    GSSAPIAuthentication
                    GSSAPIDelegateCredentials
Index: sftp.1
===================================================================
--- sftp.1 (revision 15802)
+++ sftp.1 (revision 15803)
@@ -158,6 +158,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
Index: ssh.c
===================================================================
--- ssh.c (revision 15802)
+++ ssh.c (revision 15803)
@@ -1012,7 +1012,7 @@
  if ((control_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
  fatal("%s socket(): %s\n", __func__, strerror(errno));
 
- old_umask = umask(0177);
+ old_umask = umask(options.control_bind_mask);
  if (bind(control_fd, (struct sockaddr*)&addr, addr_len) == -1) {
  control_fd = -1;
  if (errno == EINVAL || errno == EADDRINUSE)
Index: clientloop.c
===================================================================
--- clientloop.c (revision 15802)
+++ clientloop.c (revision 15803)
@@ -675,6 +675,84 @@
  xfree(cctx);
 }
 
+
+static int
+client_control_grant(int client_fd)
+{
+ struct passwd *epw = 0;
+ struct group *egr = 0;
+ char euidstr[48]; /* Sufficient for 2^128 in decimal ascii */
+ char egidstr[48]; /* Sufficient for 2^128 in decimal ascii */
+ uid_t euid;
+ gid_t egid;
+ u_int i;
+
+ if (getpeereid(client_fd, &euid, &egid) < 0) {
+ error("%s getpeereid failed: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ if ((euid == 0) || (getuid() == euid))
+ return 1; /* Short circuit. */
+
+ if ((int)sizeof euidstr <= snprintf(euidstr, sizeof euidstr, "%lu", (u_long)euid)) {
+ error("%s uid too high", __func__);
+ return -1;
+ }
+
+ if ((int)sizeof egidstr <= snprintf(egidstr, sizeof egidstr, "%lu", (u_long)egid)) {
+ error("%s gid too high", __func__);
+ return -1;
+ }
+
+ if ((options.num_control_allow_users || options.num_control_deny_users)
+ &&  !(epw = getpwuid(euid))) {
+ error("%s getpwuid failed: %s", __func__, strerror(errno));
+ return -1; /* Fail, otherwise we might miss a deny pattern. */
+ }
+
+ if ((options.num_control_allow_groups || options.num_control_deny_groups)
+ &&  !(egr = getgrgid(euid))) {
+ error("%s getgrgid failed: %s", __func__, strerror(errno));
+ return -1; /* Fail, otherwise we might miss a deny pattern. */
+ }
+
+ for (i = 0; i < options.num_control_deny_users; i++) {
+ if (match_pattern(euidstr,options.control_deny_users[i])
+ ||  (epw && match_pattern(epw->pw_name,options.control_deny_users[i]))) {
+ error("%s control mode uid denied: %s", __func__, options.control_deny_users[i]);
+ return 0;
+ }
+ }
+
+ for (i = 0; i < options.num_control_deny_groups; i++) {
+ if (match_pattern(egidstr,options.control_deny_groups[i])
+ ||  (egr && match_pattern(egr->gr_name,options.control_deny_groups[i]))) {
+ error("%s control mode gid denied: %s", __func__, options.control_deny_groups[i]);
+ return 0;
+ }
+ }
+
+ for (i = 0; i < options.num_control_allow_users; i++) {
+ if (match_pattern(euidstr,options.control_allow_users[i])
+ ||  (epw && match_pattern(epw->pw_name,options.control_allow_users[i]))) {
+ return 1;
+ }
+ }
+
+ for (i = 0; i < options.num_control_allow_groups; i++) {
+ if (match_pattern(egidstr,options.control_allow_groups[i])
+ ||  (egr && match_pattern(egr->gr_name,options.control_allow_groups[i]))) {
+ return 1;
+ }
+ }
+
+ error("%s control mode uid/gid denied: %s/%s", __func__, euidstr, egidstr);
+
+ return 0; /* Deny by default. */
+}
+
+
 static void
 client_process_control(fd_set * readset)
 {
@@ -686,8 +764,6 @@
  struct confirm_ctx *cctx;
  char *cmd;
  u_int i, len, env_len, command, flags;
- uid_t euid;
- gid_t egid;
 
  /*
  * Accept connection on control socket
@@ -703,16 +779,21 @@
  return;
  }
 
- if (getpeereid(client_fd, &euid, &egid) < 0) {
- error("%s getpeereid failed: %s", __func__, strerror(errno));
+ switch(client_control_grant(client_fd)) {
+ case 1: /* ALLOWED */
+ break;
+ case 0: /* DENIED */
+ error("control access denied!");
  close(client_fd);
  return;
- }
- if ((euid != 0) && (getuid() != euid)) {
- error("control mode uid mismatch: peer euid %u != uid %u",
-    (u_int) euid, (u_int) getuid());
+ case -1: /* SYSERR! */
+ error("error granting access");
  close(client_fd);
  return;
+ default: /* OOPS! */
+ error("unknown error granting access");
+ close(client_fd);
+ return;
  }
 
  unset_nonblock(client_fd);
Index: readconf.c
===================================================================
--- readconf.c (revision 15802)
+++ readconf.c (revision 15803)
@@ -106,8 +106,9 @@
  oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
  oAddressFamily, oGssAuthentication, oGssDelegateCreds,
  oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
- oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
- oDeprecated, oUnsupported
+ oSendEnv, oControlBindMask, oControlPath, oControlMaster,
+ oControlAllowUsers, oControlDenyUsers, oControlAllowGroups,
+ oControlDenyGroups, oHashKnownHosts, oDeprecated, oUnsupported
 } OpCodes;
 
 /* Textual representations of the tokens. */
@@ -195,8 +196,13 @@
  { "serveraliveinterval", oServerAliveInterval },
  { "serveralivecountmax", oServerAliveCountMax },
  { "sendenv", oSendEnv },
+ { "controlbindmask", oControlBindMask },
  { "controlpath", oControlPath },
  { "controlmaster", oControlMaster },
+ { "controlallowusers", oControlAllowUsers },
+ { "controldenyusers", oControlDenyUsers },
+ { "controlallowgroups", oControlAllowUsers },
+ { "controldenygroups", oControlDenyGroups },
  { "hashknownhosts", oHashKnownHosts },
  { NULL, oBadOption }
 };
@@ -790,6 +796,17 @@
  }
  break;
 
+ case oControlBindMask:
+ arg = strdelim(&s);
+ if (!arg || *arg == '\0')
+ fatal("%.200s line %d: Missing ControlBindMask argument.",
+    filename, linenum);
+ value = strtol(arg, &endofnumber, 0);
+ if (*endofnumber != '\0' || value < 0 || value > 0777)
+ fatal("%.200s line %d: Bad mask.", filename, linenum);
+ options->control_bind_mask = value;
+ break;
+
  case oControlPath:
  charptr = &options->control_path;
  goto parse_string;
@@ -818,6 +835,46 @@
  *intptr = value;
  break;
 
+ case oControlAllowUsers:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_allow_users >= MAX_CONTROL_ALLOW_USERS)
+ fatal("%s line %d: too many control allow users.",
+    filename, linenum);
+ options->control_allow_users[options->num_control_allow_users++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlDenyUsers:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_deny_users >= MAX_CONTROL_DENY_USERS)
+ fatal("%s line %d: too many control deny users.",
+    filename, linenum);
+ options->control_deny_users[options->num_control_deny_users++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlAllowGroups:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_allow_groups >= MAX_CONTROL_ALLOW_GROUPS)
+ fatal("%s line %d: too many control allow groups.",
+    filename, linenum);
+ options->control_allow_groups[options->num_control_allow_groups++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlDenyGroups:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_deny_groups >= MAX_CONTROL_DENY_GROUPS)
+ fatal("%s line %d: too many control deny groups.",
+    filename, linenum);
+ options->control_deny_groups[options->num_control_deny_groups++] =
+    xstrdup(arg);
+ }
+ break;
+
  case oHashKnownHosts:
  intptr = &options->hash_known_hosts;
  goto parse_flag;
@@ -963,8 +1020,13 @@
  options->server_alive_interval = -1;
  options->server_alive_count_max = -1;
  options->num_send_env = 0;
+ options->control_bind_mask = 0177;
  options->control_path = NULL;
  options->control_master = -1;
+ options->num_control_allow_users = 0;
+ options->num_control_deny_users = 0;
+ options->num_control_allow_groups = 0;
+ options->num_control_deny_groups = 0;
  options->hash_known_hosts = -1;
 }
 
Index: readconf.h
===================================================================
--- readconf.h (revision 15802)
+++ readconf.h (revision 15803)
@@ -28,8 +28,13 @@
 }       Forward;
 /* Data structure for representing option data. */
 
-#define MAX_SEND_ENV 256
+#define MAX_SEND_ENV 256
 
+#define MAX_CONTROL_ALLOW_USERS 256
+#define MAX_CONTROL_DENY_USERS 256
+#define MAX_CONTROL_ALLOW_GROUPS 256
+#define MAX_CONTROL_DENY_GROUPS 256
+
 typedef struct {
  int     forward_agent; /* Forward authentication agent. */
  int     forward_x11; /* Forward X11 display. */
@@ -110,8 +115,17 @@
  int     num_send_env;
  char   *send_env[MAX_SEND_ENV];
 
+ mode_t control_bind_mask;
  char *control_path;
  int control_master;
+ u_int num_control_allow_users;
+ char *control_allow_users[MAX_CONTROL_ALLOW_USERS];
+ u_int num_control_deny_users;
+ char *control_deny_users[MAX_CONTROL_DENY_USERS];
+ u_int num_control_allow_groups;
+ char *control_allow_groups[MAX_CONTROL_ALLOW_GROUPS];
+ u_int num_control_deny_groups;
+ char *control_deny_groups[MAX_CONTROL_ALLOW_USERS];
 
  int hash_known_hosts;
 }       Options;
Index: ssh_config.0
===================================================================
--- ssh_config.0 (revision 15802)
+++ ssh_config.0 (revision 15803)
@@ -158,6 +158,38 @@
              three of these escape sequences.  This ensures that shared con-
              nections are uniquely identified.
 
+     ControlBindMask
+             Specify the umask to use when creating/binding the control
+             socket.
+
+     ControlAllowUsers
+             Specify a list of uid and/or user name patterns, separated by
+             spaces. control socket access is granted for non-process owner
+             users only if the requesting process has an effective uid of 0,
+             or if the effective uid/user name matches here or the effective
+             gid/group name matches ControlAllowGroups.
+
+     ControlAllowGroups
+             Specify a list of gid and/or group name patterns, separated by
+             spaces. control socket access is granted for non-process owner
+             users only if the requesting process has an effective uid of 0,
+             or if the effective gid/group name matches here or the
+             effective uid/user name matches ControlAllowUsers.
+
+     ControlDenyUsers
+             Unless the requesting process has an effective uid of 0 or an
+             effective uid which matches the uid of the control master,
+             control socket access is denied if the effective uid/user name
+             matches here or the effective gid/group name matches
+             ControlDenyGroups.
+
+     ControlDenyGroups
+             Unless the requesting process has an effective uid of 0 or an
+             effective uid which matches the uid of the control master,
+             control socket access is denied if the effective gid/group name
+             matches here or the effective uid/user name matches
+             ControlDenyUsers.
+
      DynamicForward
              Specifies that a TCP/IP port on the local machine be forwarded
              over the secure channel, and the application protocol is then
Index: ssh_config.5
===================================================================
--- ssh_config.5 (revision 15802)
+++ ssh_config.5 (revision 15803)
@@ -315,6 +315,36 @@
 used for opportunistic connection sharing include
 all three of these escape sequences.
 This ensures that shared connections are uniquely identified.
+.It Cm ControlBindMask
+Specify the umask to use when creating/binding the control socket.
+.It Cm ControlAllowUsers
+Specify a list of uid and/or user name patterns, separated by spaces.
+control socket access is granted for non-process owner users only if the
+requesting process has an effective uid of 0, or if the effective uid/user
+name matches here or the effective gid/group name matches
+.Cm ControlAllowGroups
+.
+.It Cm ControlAllowGroups
+Specify a list of gid and/or group name patterns, separated by spaces.
+control socket access is granted for non-process owner users only if the
+requesting process has an effective uid of 0, or if the effective gid/group
+name matches here or the effective uid/user name matches
+.Cm ControlAllowUsers
+.
+.It Cm ControlDenyUsers
+Unless the requesting process has an effective uid of 0 or an effective uid
+which matches the uid of the control master, control socket access is denied
+if the effective uid/user name matches here or the effective gid/group name
+matches
+.Cm ControlDenyGroups
+.
+.It Cm ControlDenyGroups
+Unless the requesting process has an effective uid of 0 or an effective uid
+which matches the uid of the control master, control socket access is denied
+if the effective gid/group name matches here or the effective uid/user name
+matches
+.Cm ControlDenyUsers
+.
 .It Cm DynamicForward
 Specifies that a TCP/IP port on the local machine be forwarded
 over the secure channel, and the application
Index: scp.0
===================================================================
--- scp.0 (revision 15802)
+++ scp.0 (revision 15803)
@@ -70,6 +70,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    GlobalKnownHostsFile
                    GSSAPIAuthentication
                    GSSAPIDelegateCredentials
Index: scp.1
===================================================================
--- scp.1 (revision 15802)
+++ scp.1 (revision 15803)
@@ -130,6 +130,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
Index: ssh.0
===================================================================
--- ssh.0 (revision 15802)
+++ ssh.0 (revision 15803)
@@ -392,6 +392,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    DynamicForward
                    EscapeChar
                    ForwardAgent
Index: ssh.1
===================================================================
--- ssh.1 (revision 15802)
+++ ssh.1 (revision 15803)
@@ -691,6 +691,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It DynamicForward
 .It EscapeChar
 .It ForwardAgent
Index: sftp.0
===================================================================
--- sftp.0 (revision 15802)
+++ sftp.0 (revision 15803)
@@ -74,6 +74,11 @@
                    ConnectTimeout
                    ControlMaster
                    ControlPath
+                   ControlBindMask
+                   ControlAllowUsers
+                   ControlAllowGroups
+                   ControlDenyUsers
+                   ControlDenyGroups
                    GlobalKnownHostsFile
                    GSSAPIAuthentication
                    GSSAPIDelegateCredentials
Index: sftp.1
===================================================================
--- sftp.1 (revision 15802)
+++ sftp.1 (revision 15803)
@@ -158,6 +158,11 @@
 .It ConnectTimeout
 .It ControlMaster
 .It ControlPath
+.It ControlBindMask
+.It ControlAllowUsers
+.It ControlAllowGroups
+.It ControlDenyUsers
+.It ControlDenyGroups
 .It GlobalKnownHostsFile
 .It GSSAPIAuthentication
 .It GSSAPIDelegateCredentials
Index: ssh.c
===================================================================
--- ssh.c (revision 15802)
+++ ssh.c (revision 15803)
@@ -1012,7 +1012,7 @@
  if ((control_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
  fatal("%s socket(): %s\n", __func__, strerror(errno));
 
- old_umask = umask(0177);
+ old_umask = umask(options.control_bind_mask);
  if (bind(control_fd, (struct sockaddr*)&addr, addr_len) == -1) {
  control_fd = -1;
  if (errno == EINVAL || errno == EADDRINUSE)
Index: clientloop.c
===================================================================
--- clientloop.c (revision 15802)
+++ clientloop.c (revision 15803)
@@ -675,6 +675,84 @@
  xfree(cctx);
 }
 
+
+static int
+client_control_grant(int client_fd)
+{
+ struct passwd *epw = 0;
+ struct group *egr = 0;
+ char euidstr[48]; /* Sufficient for 2^128 in decimal ascii */
+ char egidstr[48]; /* Sufficient for 2^128 in decimal ascii */
+ uid_t euid;
+ gid_t egid;
+ u_int i;
+
+ if (getpeereid(client_fd, &euid, &egid) < 0) {
+ error("%s getpeereid failed: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ if ((euid == 0) || (getuid() == euid))
+ return 1; /* Short circuit. */
+
+ if ((int)sizeof euidstr <= snprintf(euidstr, sizeof euidstr, "%lu", (u_long)euid)) {
+ error("%s uid too high", __func__);
+ return -1;
+ }
+
+ if ((int)sizeof egidstr <= snprintf(egidstr, sizeof egidstr, "%lu", (u_long)egid)) {
+ error("%s gid too high", __func__);
+ return -1;
+ }
+
+ if ((options.num_control_allow_users || options.num_control_deny_users)
+ &&  !(epw = getpwuid(euid))) {
+ error("%s getpwuid failed: %s", __func__, strerror(errno));
+ return -1; /* Fail, otherwise we might miss a deny pattern. */
+ }
+
+ if ((options.num_control_allow_groups || options.num_control_deny_groups)
+ &&  !(egr = getgrgid(euid))) {
+ error("%s getgrgid failed: %s", __func__, strerror(errno));
+ return -1; /* Fail, otherwise we might miss a deny pattern. */
+ }
+
+ for (i = 0; i < options.num_control_deny_users; i++) {
+ if (match_pattern(euidstr,options.control_deny_users[i])
+ ||  (epw && match_pattern(epw->pw_name,options.control_deny_users[i]))) {
+ error("%s control mode uid denied: %s", __func__, options.control_deny_users[i]);
+ return 0;
+ }
+ }
+
+ for (i = 0; i < options.num_control_deny_groups; i++) {
+ if (match_pattern(egidstr,options.control_deny_groups[i])
+ ||  (egr && match_pattern(egr->gr_name,options.control_deny_groups[i]))) {
+ error("%s control mode gid denied: %s", __func__, options.control_deny_groups[i]);
+ return 0;
+ }
+ }
+
+ for (i = 0; i < options.num_control_allow_users; i++) {
+ if (match_pattern(euidstr,options.control_allow_users[i])
+ ||  (epw && match_pattern(epw->pw_name,options.control_allow_users[i]))) {
+ return 1;
+ }
+ }
+
+ for (i = 0; i < options.num_control_allow_groups; i++) {
+ if (match_pattern(egidstr,options.control_allow_groups[i])
+ ||  (egr && match_pattern(egr->gr_name,options.control_allow_groups[i]))) {
+ return 1;
+ }
+ }
+
+ error("%s control mode uid/gid denied: %s/%s", __func__, euidstr, egidstr);
+
+ return 0; /* Deny by default. */
+}
+
+
 static void
 client_process_control(fd_set * readset)
 {
@@ -686,8 +764,6 @@
  struct confirm_ctx *cctx;
  char *cmd;
  u_int i, len, env_len, command, flags;
- uid_t euid;
- gid_t egid;
 
  /*
  * Accept connection on control socket
@@ -703,16 +779,21 @@
  return;
  }
 
- if (getpeereid(client_fd, &euid, &egid) < 0) {
- error("%s getpeereid failed: %s", __func__, strerror(errno));
+ switch(client_control_grant(client_fd)) {
+ case 1: /* ALLOWED */
+ break;
+ case 0: /* DENIED */
+ error("control access denied!");
  close(client_fd);
  return;
- }
- if ((euid != 0) && (getuid() != euid)) {
- error("control mode uid mismatch: peer euid %u != uid %u",
-    (u_int) euid, (u_int) getuid());
+ case -1: /* SYSERR! */
+ error("error granting access");
  close(client_fd);
  return;
+ default: /* OOPS! */
+ error("unknown error granting access");
+ close(client_fd);
+ return;
  }
 
  unset_nonblock(client_fd);
Index: readconf.c
===================================================================
--- readconf.c (revision 15802)
+++ readconf.c (revision 15803)
@@ -106,8 +106,9 @@
  oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
  oAddressFamily, oGssAuthentication, oGssDelegateCreds,
  oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
- oSendEnv, oControlPath, oControlMaster, oHashKnownHosts,
- oDeprecated, oUnsupported
+ oSendEnv, oControlBindMask, oControlPath, oControlMaster,
+ oControlAllowUsers, oControlDenyUsers, oControlAllowGroups,
+ oControlDenyGroups, oHashKnownHosts, oDeprecated, oUnsupported
 } OpCodes;
 
 /* Textual representations of the tokens. */
@@ -195,8 +196,13 @@
  { "serveraliveinterval", oServerAliveInterval },
  { "serveralivecountmax", oServerAliveCountMax },
  { "sendenv", oSendEnv },
+ { "controlbindmask", oControlBindMask },
  { "controlpath", oControlPath },
  { "controlmaster", oControlMaster },
+ { "controlallowusers", oControlAllowUsers },
+ { "controldenyusers", oControlDenyUsers },
+ { "controlallowgroups", oControlAllowUsers },
+ { "controldenygroups", oControlDenyGroups },
  { "hashknownhosts", oHashKnownHosts },
  { NULL, oBadOption }
 };
@@ -790,6 +796,17 @@
  }
  break;
 
+ case oControlBindMask:
+ arg = strdelim(&s);
+ if (!arg || *arg == '\0')
+ fatal("%.200s line %d: Missing ControlBindMask argument.",
+    filename, linenum);
+ value = strtol(arg, &endofnumber, 0);
+ if (*endofnumber != '\0' || value < 0 || value > 0777)
+ fatal("%.200s line %d: Bad mask.", filename, linenum);
+ options->control_bind_mask = value;
+ break;
+
  case oControlPath:
  charptr = &options->control_path;
  goto parse_string;
@@ -818,6 +835,46 @@
  *intptr = value;
  break;
 
+ case oControlAllowUsers:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_allow_users >= MAX_CONTROL_ALLOW_USERS)
+ fatal("%s line %d: too many control allow users.",
+    filename, linenum);
+ options->control_allow_users[options->num_control_allow_users++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlDenyUsers:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_deny_users >= MAX_CONTROL_DENY_USERS)
+ fatal("%s line %d: too many control deny users.",
+    filename, linenum);
+ options->control_deny_users[options->num_control_deny_users++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlAllowGroups:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_allow_groups >= MAX_CONTROL_ALLOW_GROUPS)
+ fatal("%s line %d: too many control allow groups.",
+    filename, linenum);
+ options->control_allow_groups[options->num_control_allow_groups++] =
+    xstrdup(arg);
+ }
+ break;
+
+ case oControlDenyGroups:
+ while ((arg = strdelim(&s)) && *arg != '\0') {
+ if (options->num_control_deny_groups >= MAX_CONTROL_DENY_GROUPS)
+ fatal("%s line %d: too many control deny groups.",
+    filename, linenum);
+ options->control_deny_groups[options->num_control_deny_groups++] =
+    xstrdup(arg);
+ }
+ break;
+
  case oHashKnownHosts:
  intptr = &options->hash_known_hosts;
  goto parse_flag;
@@ -963,8 +1020,13 @@
  options->server_alive_interval = -1;
  options->server_alive_count_max = -1;
  options->num_send_env = 0;
+ options->control_bind_mask = 0177;
  options->control_path = NULL;
  options->control_master = -1;
+ options->num_control_allow_users = 0;
+ options->num_control_deny_users = 0;
+ options->num_control_allow_groups = 0;
+ options->num_control_deny_groups = 0;
  options->hash_known_hosts = -1;
 }
 
Index: readconf.h
===================================================================
--- readconf.h (revision 15802)
+++ readconf.h (revision 15803)
@@ -28,8 +28,13 @@
 }       Forward;
 /* Data structure for representing option data. */
 
-#define MAX_SEND_ENV 256
+#define MAX_SEND_ENV 256
 
+#define MAX_CONTROL_ALLOW_USERS 256
+#define MAX_CONTROL_DENY_USERS 256
+#define MAX_CONTROL_ALLOW_GROUPS 256
+#define MAX_CONTROL_DENY_GROUPS 256
+
 typedef struct {
  int     forward_agent; /* Forward authentication agent. */
  int     forward_x11; /* Forward X11 display. */
@@ -110,8 +115,17 @@
  int     num_send_env;
  char   *send_env[MAX_SEND_ENV];
 
+ mode_t control_bind_mask;
  char *control_path;
  int control_master;
+ u_int num_control_allow_users;
+ char *control_allow_users[MAX_CONTROL_ALLOW_USERS];
+ u_int num_control_deny_users;
+ char *control_deny_users[MAX_CONTROL_DENY_USERS];
+ u_int num_control_allow_groups;
+ char *control_allow_groups[MAX_CONTROL_ALLOW_GROUPS];
+ u_int num_control_deny_groups;
+ char *control_deny_groups[MAX_CONTROL_ALLOW_USERS];
 
  int hash_known_hosts;
 }       Options;
Index: ssh_config.0
===================================================================
--- ssh_config.0 (revision 15802)
+++ ssh_config.0 (revision 15803)
@@ -158,6 +158,38 @@
              three of these escape sequences.  This ensures that shared con-
              nections are uniquely identified.
 
+     ControlBindMask
+             Specify the umask to use when creating/binding the control
+             socket.
+
+     ControlAllowUsers
+             Specify a list of uid and/or user name patterns, separated by
+             spaces. control socket access is granted for non-process owner
+             users only if the requesting process has an effective uid of 0,
+             or if the effective uid/user name matches here or the effective
+             gid/group name matches ControlAllowGroups.
+
+     ControlAllowGroups
+             Specify a list of gid and/or group name patterns, separated by
+             spaces. control socket access is granted for non-process owner
+             users only if the requesting process has an effective uid of 0,
+             or if the effective gid/group name matches here or the
+             effective uid/user name matches ControlAllowUsers.
+
+     ControlDenyUsers
+             Unless the requesting process has an effective uid of 0 or an
+             effective uid which matches the uid of the control master,
+             control socket access is denied if the effective uid/user name
+             matches here or the effective gid/group name matches
+             ControlDenyGroups.
+
+     ControlDenyGroups
+             Unless the requesting process has an effective uid of 0 or an
+             effective uid which matches the uid of the control master,
+             control socket access is denied if the effective gid/group name
+             matches here or the effective uid/user name matches
+             ControlDenyUsers.
+
      DynamicForward
              Specifies that a TCP/IP port on the local machine be forwarded
              over the secure channel, and the application protocol is then
Index: ssh_config.5
===================================================================
--- ssh_config.5 (revision 15802)
+++ ssh_config.5 (revision 15803)
@@ -315,6 +315,36 @@
 used for opportunistic connection sharing include
 all three of these escape sequences.
 This ensures that shared connections are uniquely identified.
+.It Cm ControlBindMask
+Specify the umask to use when creating/binding the control socket.
+.It Cm ControlAllowUsers
+Specify a list of uid and/or user name patterns, separated by spaces.
+control socket access is granted for non-process owner users only if the
+requesting process has an effective uid of 0, or if the effective uid/user
+name matches here or the effective gid/group name matches
+.Cm ControlAllowGroups
+.
+.It Cm ControlAllowGroups
+Specify a list of gid and/or group name patterns, separated by spaces.
+control socket access is granted for non-process owner users only if the
+requesting process has an effective uid of 0, or if the effective gid/group
+name matches here or the effective uid/user name matches
+.Cm ControlAllowUsers
+.
+.It Cm ControlDenyUsers
+Unless the requesting process has an effective uid of 0 or an effective uid
+which matches the uid of the control master, control socket access is denied
+if the effective uid/user name matches here or the effective gid/group name
+matches
+.Cm ControlDenyGroups
+.
+.It Cm ControlDenyGroups
+Unless the requesting process has an effective uid of 0 or an effective uid
+which matches the uid of the control master, control socket access is denied
+if the effective gid/group name matches here or the effective uid/user name
+matches
+.Cm ControlDenyUsers
+.
 .It Cm DynamicForward
 Specifies that a TCP/IP port on the local machine be forwarded
 over the secure channel, and the application