Download raw body.
ssh: support both /tmp and ~/.ssh for agent sockets
Hi,
Some time ago, we changed sshd and ssh-agent to establish $SSH_AUTH_SOCK
under ~/.ssh/agent to make unveil/pledge tighter and to reduce ambient
usage of the shared /tmp directory that they used to use.
This unfortunately causes problems in some situations, e.g. users with
no (writable) $HOME or users with $HOME on a fs that doesn't support
unix sockets. Some people at $dayjob are affected by this.
This patch gives ssh-agent and sshd more control over where it puts
their agent sockets and how it treats them, by giving it a directive
that specifies the path to where the sockets will be placed and
whether that location is a shared directory.
This allows per-user sockets like the current default, but in
arbitrary directories, e.g. `ssh-agent -A "user:/run/%U" would
place sockets in /run/[uid]/sock.XXXXXX.
`ssh-agent -A shared:/tmp` (also ssh-agent -T) would bring back
the old behaviour of creating a subdirectory for the socket,
yielding a path like /tmp/ssh-XXXXX/agent.XXXXX.
ATM just specifying a path (i.e. ssh-agent -A /foo) is an error,
but I'd like that to "do the right thing" automatically as a next
step, i.e. check the ownership and permissions of the target
directory and only use a per-user socket if they are safe.
ok?
diff --git a/misc-agent.c b/misc-agent.c
index f42a251..7742b74 100644
--- a/misc-agent.c
+++ b/misc-agent.c
@@ -28,6 +28,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <libgen.h>
#include "digest.h"
#include "log.h"
@@ -76,7 +77,7 @@ hostname_hash(size_t len)
return xstrdup(p);
}
-char *
+static char *
agent_hostname_hash(void)
{
return hostname_hash(SOCKET_HOSTNAME_HASHLEN);
@@ -152,71 +153,188 @@ unix_listener_tmp(char *path, int backlog)
}
/*
- * Create a subdirectory under the supplied home directory if it
- * doesn't already exist
+ * Shared directory case (e.g. /tmp): create a temporary directory
+ * for the socket.
*/
static int
-ensure_mkdir(const char *homedir, const char *subdir)
+agent_listener_shared(const char *parent_dir, pid_t pid, const char *tag,
+ int *sockp, char **pathp, char **dirp)
{
- char *path;
+ char *dir = NULL, *path = NULL;
+ int sock, ret = -1;
+ mode_t prev_mask;
- xasprintf(&path, "%s/%s", homedir, subdir);
- if (mkdir(path, 0700) == 0)
- debug("created directory %s", path);
- else if (errno != EEXIST) {
- error_f("mkdir %s: %s", path, strerror(errno));
- free(path);
- return -1;
+ *pathp = *dirp = NULL;
+ xasprintf(&dir, "%s/ssh-XXXXXXXXXXXX", parent_dir);
+ if (mkdtemp(dir) == NULL) {
+ error_f("failed to create temporary directory "
+ "in \"%s\": %s", dir, strerror(errno));
+ goto out;
}
+ xasprintf(&path, "%s/agent.%s.%ld", dir, tag, (long)pid);
+ prev_mask = umask(0177);
+ if ((sock = unix_listener(path, SSH_LISTEN_BACKLOG, 0)) < 0) {
+ /* Error already logged */
+ umask(prev_mask);
+ if (rmdir(dir) != 0)
+ error_f("rmdir \"%s\": %s", dir, strerror(errno));
+ goto out;
+ }
+ umask(prev_mask);
+
+ /* Success */
+ *dirp = dir;
+ dir = NULL; /* transferred */
+ *pathp = path;
+ path = NULL; /* transferred */
+ *sockp = sock;
+ ret = 0;
+ out:
+ free(dir);
free(path);
- return 0;
+ return ret;
}
+/*
+ * User-specific directory case (e.g. ~/.ssh/agent): ensure directory
+ * exists, and use a temp socket name under it.
+ */
static int
-agent_prepare_sockdir(const char *homedir)
+agent_listener_user(const char *dir, pid_t pid, const char *tag,
+ int *sockp, char **pathp)
{
- if (homedir == NULL || *homedir == '\0' ||
- ensure_mkdir(homedir, _PATH_SSH_USER_DIR) != 0 ||
- ensure_mkdir(homedir, _PATH_SSH_AGENT_SOCKET_DIR) != 0)
- return -1;
- return 0;
-}
-
-
-/* Get a path template for an agent socket in the user's homedir */
-static char *
-agent_socket_template(const char *homedir, const char *tag)
-{
- char *hostnamehash, *ret;
+ char *hostnamehash = NULL, *path = NULL;
+ int sock, ret = -1;
if ((hostnamehash = hostname_hash(SOCKET_HOSTNAME_HASHLEN)) == NULL)
- return NULL;
- xasprintf(&ret, "%s/%s/s.%s.%s.XXXXXXXXXX",
- homedir, _PATH_SSH_AGENT_SOCKET_DIR, hostnamehash, tag);
+ return -1;
+ xasprintf(&path, "%s/s.%s.%s.%lld.XXXXXXXXXX",
+ dir, hostnamehash, tag, (long long)pid);
+ if (mkdir_path(dir, 0700) != 0) {
+ error_f("failed to create agent socket parent directory");
+ goto out;
+ }
+ if ((sock = unix_listener_tmp(path, SSH_LISTEN_BACKLOG)) == -1) {
+ /* error already logged */
+ goto out;
+ }
+ /* Success */
+ *pathp = path;
+ path = NULL; /* transferred */
+ *sockp = sock;
+ ret = 0;
+ out:
free(hostnamehash);
+ free(path);
return ret;
}
+static char *
+expand_pathspec(const char *path, const char *username,
+ uid_t uid, const char *homedir)
+{
+ char *uidbuf = NULL, *dir = NULL, *tmp = NULL;
+
+ xasprintf(&uidbuf, "%lld", (long long)uid);
+
+ if ((tmp = percent_expand(path, "u", username, "U", uidbuf,
+ "h", homedir, NULL)) == NULL) {
+ error_f("failed to percent-expand agent socket directory");
+ goto out;
+ }
+ if (tilde_expand(tmp, uid, &dir) != 0) {
+ error_f("failed to user-expand agent socket directory");
+ goto out;
+ }
+ if (dir[0] != '/') {
+ /* Assume it's relative to the home directory */
+ free(tmp);
+ tmp = dir;
+ xasprintf(&dir, "%s/%s", homedir, tmp);
+ }
+ out:
+ free(uidbuf);
+ free(tmp);
+ return dir;
+}
+
int
-agent_listener(const char *homedir, const char *tag, int *sockp, char **pathp)
+agent_listener(const char *pathspec, const char *username, uid_t uid,
+ const char *homedir, pid_t pid, const char *tag, int *sockp,
+ char **pathp, char **dirp)
{
- int sock;
- char *path;
+ int sock = -1, ret = -1;
+ char *path = NULL, *dir = NULL;
*sockp = -1;
- *pathp = NULL;
+ *pathp = *dirp = NULL;
- if (agent_prepare_sockdir(homedir) != 0)
- return -1; /* error already logged */
- if ((path = agent_socket_template(homedir, tag)) == NULL)
- return -1; /* error already logged */
- if ((sock = unix_listener_tmp(path, SSH_LISTEN_BACKLOG)) == -1) {
- free(path);
- return -1; /* error already logged */
+ if (pathspec == NULL || pathspec[0] == '\0') {
+ error_f("no agent path specified");
+ return -1;
}
+ if (strncmp(pathspec, "shared:", 7) == 0) {
+ if (pathspec[7] != '/') {
+ error_f("shared agent socket paths must be absoute");
+ goto out;
+ }
+ if ((dir = expand_pathspec(pathspec + 7,
+ username, uid, homedir)) == NULL) {
+ /* Error already logged */
+ goto out;
+ }
+ if (agent_listener_shared(dir, pid, tag,
+ &sock, &path, dirp) != 0) {
+ /* Error already logged */
+ goto out;
+ }
+ } else if (strncmp(pathspec, "user:", 5) == 0) {
+ if ((dir = expand_pathspec(pathspec + 5,
+ username, uid, homedir)) == NULL) {
+ /* Error already logged */
+ goto out;
+ }
+ if (agent_listener_user(dir, pid, tag, &sock, &path) != 0) {
+ /* Error already logged */
+ goto out;
+ }
+ } else {
+ /* Shouldn't happen */
+ error_f("unsupported agent path specification %s", pathspec);
+ goto out;
+ }
+
/* success */
+ ret = 0;
*sockp = sock;
*pathp = path;
+ path = NULL; /* transferred */
+ out:
+ free(path);
+ free(dir);
+ return ret;
+}
+
+int
+agent_listener_cleanup(const char *pathspec, const char *sockpath,
+ const char *sockdir)
+{
+ if (sockpath == NULL || pathspec == NULL)
+ return 0;
+ if (unlink(sockpath) != 0) {
+ error_f("unlink \"%s\": %s", sockpath, strerror(errno));
+ return -1;
+ }
+ debug3_f("removed socket %s", sockpath);
+
+ if (strncmp(pathspec, "shared:", 7) == 0 && sockdir != NULL) {
+ if (rmdir(sockdir) != 0) {
+ error_f("rmdir \"%s\": %s", sockdir, strerror(errno));
+ return -1;
+ }
+ debug3_f("removed socket directory %s", sockdir);
+ }
+
return 0;
}
@@ -262,14 +380,25 @@ socket_is_stale(const char *path)
}
void
-agent_cleanup_stale(const char *homedir, int ignore_hosthash)
+agent_cleanup_stale(const char *pathspec, const char *username, uid_t uid,
+ const char *homedir, int ignore_hosthash)
{
DIR *d = NULL;
struct dirent *dp;
struct stat sb;
- char *prefix = NULL, *dirpath = NULL, *path;
+ char *prefix = NULL, *dir = NULL, *path;
struct timespec now, sub;
+ /* Only clean up user socket directories */
+ if (pathspec == NULL || strncmp(pathspec, "user:", 5) != 0)
+ return;
+
+ if ((dir = expand_pathspec(pathspec + 5,
+ username, uid, homedir)) == NULL)
+ return; /* error already logged */
+
+ debug_f("cleanup %s", dir);
+
/* Only consider sockets last modified > 1 hour ago */
if (clock_gettime(CLOCK_REALTIME, &now) != 0) {
error_f("clock_gettime: %s", strerror(errno));
@@ -289,10 +418,9 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash)
free(path);
}
- xasprintf(&dirpath, "%s/%s", homedir, _PATH_SSH_AGENT_SOCKET_DIR);
- if ((d = opendir(dirpath)) == NULL) {
+ if ((d = opendir(dir)) == NULL) {
if (errno != ENOENT)
- error_f("opendir \"%s\": %s", dirpath, strerror(errno));
+ error_f("opendir \"%s\": %s", dir, strerror(errno));
goto out;
}
while ((dp = readdir(d)) != NULL) {
@@ -301,23 +429,23 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash)
if (fstatat(dirfd(d), dp->d_name,
&sb, AT_SYMLINK_NOFOLLOW) != 0 && errno != ENOENT) {
error_f("stat \"%s/%s\": %s",
- dirpath, dp->d_name, strerror(errno));
+ dir, dp->d_name, strerror(errno));
continue;
}
if (!S_ISSOCK(sb.st_mode))
continue;
if (timespeccmp(&sb.st_mtim, &now, >)) {
debug3_f("Ignoring recent socket \"%s/%s\"",
- dirpath, dp->d_name);
+ dir, dp->d_name);
continue;
}
if (!ignore_hosthash &&
strncmp(dp->d_name, prefix, strlen(prefix)) != 0) {
debug3_f("Ignoring socket \"%s/%s\" "
- "from different host", dirpath, dp->d_name);
+ "from different host", dir, dp->d_name);
continue;
}
- xasprintf(&path, "%s/%s", dirpath, dp->d_name);
+ xasprintf(&path, "%s/%s", dir, dp->d_name);
if (socket_is_stale(path)) {
debug_f("cleanup stale socket %s", path);
unlinkat(dirfd(d), dp->d_name, 0);
@@ -327,6 +455,6 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash)
out:
if (d != NULL)
closedir(d);
- free(dirpath);
+ free(dir);
free(prefix);
}
diff --git a/misc.c b/misc.c
index 4ce2e4e..0f24dad 100644
--- a/misc.c
+++ b/misc.c
@@ -3083,3 +3083,50 @@ get_homedir(void)
return NULL;
}
+
+int
+mkdir_path(const char *target, mode_t mode)
+{
+ char *dir, *odir = NULL, *next;
+ int fd = AT_FDCWD, fd2, subpath_len, ret = -1;
+
+ dir = odir = xstrdup(target);
+
+ if (*dir == '/' &&
+ (fd = open("/", O_RDONLY|O_DIRECTORY)) == -1) {
+ error_f("open(\"/\"): %s", strerror(errno));
+ return -1;
+ }
+ /* Work through the path, component-wise */
+ for (; dir != NULL && *dir != '\0'; dir = next) {
+ if ((next = strchr(dir, '/')) != NULL)
+ *(next++) = '\0';
+ if (*dir == '\0')
+ continue;
+ subpath_len = (next == NULL) ? INT_MAX : next - odir - 1;
+ if (mkdirat(fd, dir, mode) == 0)
+ debug_f("created directory %.*s", subpath_len, target);
+ else if (errno != EEXIST) {
+ error_f("mkdir(\"%.*s\"): %s",
+ subpath_len, target, strerror(errno));
+ goto out;
+ }
+
+ /* descend */
+ if ((fd2 = openat(fd, dir, O_RDONLY|O_DIRECTORY)) == -1) {
+ error_f("open(\"%.*s\"): %s",
+ subpath_len, target, strerror(errno));
+ goto out;
+ }
+ if (fd != AT_FDCWD)
+ close(fd);
+ fd = fd2;
+ }
+ /* success */
+ ret = 0;
+ out:
+ free(odir);
+ if (fd != AT_FDCWD)
+ close(fd);
+ return ret;
+}
diff --git a/misc.h b/misc.h
index 6ec6c3a..7ecaa56 100644
--- a/misc.h
+++ b/misc.h
@@ -113,6 +113,7 @@ int path_absolute(const char *);
int stdfd_devnull(int, int, int);
int lib_contains_symbol(const char *, const char *);
char *get_homedir(void);
+int mkdir_path(const char *, mode_t);
struct passwd *pwcopy(struct passwd *);
void pwfree(struct passwd *); /* NB. only use with pwcopy */
@@ -237,9 +238,11 @@ struct timespec *ptimeout_get_tsp(struct timespec *pt);
int ptimeout_isset(struct timespec *pt);
/* misc-agent.c */
-char *agent_hostname_hash(void);
-int agent_listener(const char *, const char *, int *, char **);
-void agent_cleanup_stale(const char *, int);
+int agent_listener(const char *, const char *, uid_t, const char *,
+ pid_t, const char *, int *, char **, char **);
+void agent_cleanup_stale(const char *, const char *, uid_t,
+ const char *, int);
+int agent_listener_cleanup(const char *, const char *, const char *);
/* readpass.c */
diff --git a/pathnames.h b/pathnames.h
index 5117def..74f527a 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -56,10 +56,13 @@
/*
- * The directory in which ssh-agent sockets and agent sockets forwarded by
+ * Directory spec for ssh-agent sockets and agent sockets forwarded by
* sshd reside. This directory should not be world-readable.
*/
-#define _PATH_SSH_AGENT_SOCKET_DIR _PATH_SSH_USER_DIR "/agent"
+#define _PATH_SSH_AGENT_SOCKET_DIR "user:" _PATH_SSH_USER_DIR "/agent"
+
+/* Directory spec for ssh-agent sockets in /tmp */
+#define _PATH_SSH_AGENT_SOCKET_TMPDIR "shared:/tmp"
/*
* Per-user file containing host keys of known hosts. This file need not be
diff --git a/servconf.c b/servconf.c
index 0f9c059..900a88b 100644
--- a/servconf.c
+++ b/servconf.c
@@ -392,6 +392,8 @@ fill_default_server_options(ServerOptions *options)
options->sshd_session_path = xstrdup(_PATH_SSHD_SESSION);
if (options->sshd_auth_path == NULL)
options->sshd_auth_path = xstrdup(_PATH_SSHD_AUTH);
+ if (options->agent_socket_path == NULL)
+ options->agent_socket_path = xstrdup(_PATH_SSH_AGENT_SOCKET_DIR);
assemble_algorithms(options);
@@ -423,6 +425,7 @@ fill_default_server_options(ServerOptions *options)
CLEAR_ON_NONE(options->routing_domain);
CLEAR_ON_NONE(options->host_key_agent);
CLEAR_ON_NONE(options->per_source_penalty_exempt);
+ CLEAR_ON_NONE(options->agent_socket_path);
for (i = 0; i < options->num_host_key_files; i++)
CLEAR_ON_NONE(options->host_key_files[i]);
@@ -1602,6 +1605,29 @@ process_server_config_line_depth(ServerOptions *options, char *line,
intptr = &options->allow_agent_forwarding;
goto parse_flag;
+ case sAgentSocketPath:
+ charptr = &options->agent_socket_path;
+ arg = argv_next(&ac, &av);
+ if (!arg || *arg == '\0')
+ fatal("%s line %d: missing path.", filename, linenum);
+ if (strncmp(arg, "shared:", 7) == 0) {
+ /* Shared paths must be absolute */
+ if (arg[7] != '/') {
+ fatal("%s line %d: invalid shared path.",
+ filename, linenum);
+ }
+ } else if (strncmp(arg, "user:", 5) == 0) {
+ /* User paths must not be empty */
+ if (arg[5] == '\0') {
+ fatal("%s line %d: invalid user path.",
+ filename, linenum);
+ }
+ } else if (strcmp(arg, "none") != 0)
+ fatal("%s line %d: invalid path.", filename, linenum);
+ if (*activep && *charptr == NULL)
+ *charptr = xstrdup(arg);
+ break;
+
case sDisableForwarding:
intptr = &options->disable_forwarding;
goto parse_flag;
@@ -4218,6 +4244,7 @@ dump_config(ServerOptions *o)
dump_cfg_string(sSshdSessionPath, o->sshd_session_path);
dump_cfg_string(sSshdAuthPath, o->sshd_auth_path);
dump_cfg_string(sPerSourcePenaltyExemptList, o->per_source_penalty_exempt);
+ dump_cfg_string(sAgentSocketPath, o->agent_socket_path);
/* string arguments requiring a lookup */
dump_cfg_string(sLogLevel, log_level_name(o->log_level));
diff --git a/servconf.h b/servconf.h
index 8ed333d..99e9183 100644
--- a/servconf.h
+++ b/servconf.h
@@ -235,7 +235,8 @@ SSHCONF_STRARRAY(channel_timeouts, num_channel_timeouts, ChannelTimeout, SSHCFG_
SSHCONF_INT(unused_connection_timeout, UnusedConnectionTimeout, SSHCFG_ALL, NULL, 0, SSHCFG_COPY_MATCH) \
SSHCONF_STRING(sshd_session_path, SshdSessionPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
SSHCONF_STRING(sshd_auth_path, SshdAuthPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
-SSHCONF_INTFLAG(refuse_connection, RefuseConnection, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH)
+SSHCONF_INTFLAG(refuse_connection, RefuseConnection, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \
+SSHCONF_STRING(agent_socket_path, AgentSocketPath, SSHCFG_ALL, SSHCFG_COPY_MATCH)
#define SSHD_CONFIG_ENTRIES_LEGACY \
SSHCONF_DEPRECATE(ServerKeyBits, SSHCFG_GLOBAL, SSHCONF_DEPRECATED) \
@@ -402,7 +403,6 @@ struct include_item {
};
TAILQ_HEAD(include_list, include_item);
-
void initialize_server_options(ServerOptions *);
void fill_default_server_options(ServerOptions *);
int process_server_config_line(ServerOptions *, char *, const char *, int,
diff --git a/session.c b/session.c
index 71ab49e..3c4c748 100644
--- a/session.c
+++ b/session.c
@@ -148,6 +148,7 @@ static char *auth_info_file = NULL;
/* Name and directory of socket for authentication agent forwarding. */
static char *auth_sock_name = NULL;
+static char *auth_sock_dir = NULL; /* only set if directory needs cleanup */
/* removes the agent forwarding socket */
@@ -156,7 +157,9 @@ auth_sock_cleanup_proc(struct passwd *pw)
{
if (auth_sock_name != NULL) {
temporarily_use_uid(pw);
- unlink(auth_sock_name);
+ agent_listener_cleanup(options.agent_socket_path,
+ auth_sock_name, auth_sock_dir);
+ free(auth_sock_name);
auth_sock_name = NULL;
restore_uid();
}
@@ -176,7 +179,9 @@ auth_input_request_forwarding(struct ssh *ssh, struct passwd *pw, int agent_new)
/* Temporarily drop privileged uid for mkdir/bind. */
temporarily_use_uid(pw);
- if (agent_listener(pw->pw_dir, "sshd", &sock, &auth_sock_name) != 0) {
+ if (agent_listener(options.agent_socket_path, pw->pw_name, pw->pw_uid,
+ pw->pw_dir, getpid(), "sshd", &sock, &auth_sock_name,
+ &auth_sock_dir) != 0) {
/* a more detailed error is already logged */
ssh_packet_send_debug(ssh, "Agent forwarding disabled: "
"couldn't create listener socket");
diff --git a/ssh-agent.1 b/ssh-agent.1
index 7230003..820dd27 100644
--- a/ssh-agent.1
+++ b/ssh-agent.1
@@ -43,15 +43,15 @@
.Sh SYNOPSIS
.Nm ssh-agent
.Op Fl c | s
-.Op Fl \&DdTU
-.Op Fl a Ar bind_address
+.Op Fl \&DdU
+.Op Fl T | A Ar directory | Fl a Ar bind_address
.Op Fl E Ar fingerprint_hash
.Op Fl O Ar option
.Op Fl P Ar allowed_providers
.Op Fl t Ar life
.Nm ssh-agent
-.Op Fl TU
-.Op Fl a Ar bind_address
+.Op Fl U
+.Op Fl T | A Ar directory | Fl a Ar bind_address
.Op Fl E Ar fingerprint_hash
.Op Fl O Ar option
.Op Fl P Ar allowed_providers
@@ -79,8 +79,31 @@ Bind the agent to the
.Ux Ns -domain
socket
.Ar bind_address .
-The default is to create a socket at a random path matching
-.Pa $HOME/.ssh/agent/s.* .
+The default is to create a socket in the
+.Pa $HOME/.ssh/agent
+directory using a random path matching
+.Pa s.* .
+.It Fl A Ar socket_path
+Specify a different directory path under which to create the socket.
+Sockets may be created in either a shared location or a user-specific
+directory.
+User-specific directories are specified by prefixing the path name with
+.Cm user: .
+.Xr ssh-agent 1
+will ensure the directory exists and create the listening socket
+directly in it.
+Relative user-specific directory paths will be created to the user's
+.Ev $HOME .
+.Pp
+Shared directories may be specified by prefixing an absolute path name with
+.Cm shared: .
+In this case, a temporary subdirectory will be created under the specified
+directory and the listening agent socket will be created in that.
+.Pp
+This option accepts the tokens described in the
+.Xr sshd_config 5
+.Sx TOKENS
+section.
.It Fl c
Generate C-shell commands on standard output.
This is the default if
diff --git a/ssh-agent.c b/ssh-agent.c
index 213f3ca..93f097a 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -60,6 +60,7 @@
#include <time.h>
#include <unistd.h>
#include <util.h>
+#include <pwd.h>
#include "xmalloc.h"
#include "ssh.h"
@@ -163,7 +164,8 @@ pid_t cleanup_pid = 0;
/* pathname and directory for AUTH_SOCKET */
static char *socket_name;
-static char socket_dir[PATH_MAX];
+static char *socket_dir;
+static char *socket_dirspec;
/* Pattern-list of allowed PKCS#11/Security key paths */
static char *allowed_providers;
@@ -2187,12 +2189,12 @@ cleanup_socket(void)
return;
debug_f("cleanup");
if (socket_name != NULL) {
- unlink(socket_name);
+ agent_listener_cleanup(socket_dirspec, socket_name, socket_dir);
free(socket_name);
socket_name = NULL;
+ free(socket_dir);
+ socket_dir = NULL;
}
- if (socket_dir[0])
- rmdir(socket_dir);
}
void
@@ -2235,9 +2237,11 @@ static void
usage(void)
{
fprintf(stderr,
- "usage: ssh-agent [-c | -s] [-DdTU] [-a bind_address] [-E fingerprint_hash]\n"
- " [-O option] [-P allowed_providers] [-t life]\n"
- " ssh-agent [-TU] [-a bind_address] [-E fingerprint_hash] [-O option]\n"
+ "usage: ssh-agent [-c | -s] [-DdU] [-T | -A directory | -a bind_address]\n"
+ " [-E fingerprint_hash] [-O option]\n"
+ " [-P allowed_providers] [-t life]\n"
+ " ssh-agent [-U] [-T | -A directory | -a bind_address]\n"
+ " [-E fingerprint_hash] [-O option]\n"
" [-P allowed_providers] [-t life] command [arg ...]\n"
" ssh-agent [-c | -s] -k\n"
" ssh-agent -u\n"
@@ -2265,6 +2269,7 @@ main(int ac, char **av)
size_t npfd = 0;
u_int maxfds;
sigset_t nsigset, osigset;
+ struct passwd *pw;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -2273,10 +2278,14 @@ main(int ac, char **av)
(void)setegid(getgid());
(void)setgid(getgid());
+ if ((pw = getpwuid(getuid())) == NULL)
+ fatal("No user exists for uid %lu", (u_long)getuid());
+ pw = pwcopy(pw);
+
if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
fatal("%s: getrlimit: %s", __progname, strerror(errno));
- while ((ch = getopt(ac, av, "cDdksTuUVE:a:O:P:t:")) != -1) {
+ while ((ch = getopt(ac, av, "cDdksTuUVA:E:a:O:P:t:")) != -1) {
switch (ch) {
case 'E':
fingerprint_hash = ssh_digest_alg_by_name(optarg);
@@ -2327,6 +2336,9 @@ main(int ac, char **av)
case 'a':
agentsocket = optarg;
break;
+ case 'A':
+ socket_dirspec = xstrdup(optarg);
+ break;
case 't':
if ((lifetime = convtime(optarg)) == -1) {
fprintf(stderr, "Invalid lifetime\n");
@@ -2356,6 +2368,9 @@ main(int ac, char **av)
if (ac > 0 &&
(c_flag || k_flag || s_flag || d_flag || D_flag || u_flag))
usage();
+ /* only one of -a, -A and -T allowed */
+ if (((socket_dirspec != NULL) + (agentsocket != NULL) + T_flag) > 1)
+ usage();
log_init(__progname,
d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO,
@@ -2366,6 +2381,11 @@ main(int ac, char **av)
if (websafe_allowlist == NULL)
websafe_allowlist = xstrdup(DEFAULT_WEBSAFE_ALLOWLIST);
+ if (T_flag)
+ socket_dirspec = xstrdup(_PATH_SSH_AGENT_SOCKET_TMPDIR);
+ else if (socket_dirspec == NULL && agentsocket == NULL)
+ socket_dirspec = xstrdup(_PATH_SSH_AGENT_SOCKET_DIR);
+
if (ac == 0 && !c_flag && !s_flag) {
shell = getenv("SHELL");
if (shell != NULL && (len = strlen(shell)) > 2 &&
@@ -2401,7 +2421,8 @@ main(int ac, char **av)
if (u_flag) {
if ((homedir = get_homedir()) == NULL)
fatal("Couldn't determine home directory");
- agent_cleanup_stale(homedir, u_flag > 1);
+ agent_cleanup_stale(socket_dirspec,
+ pw->pw_name, pw->pw_uid, homedir, u_flag > 1);
printf("Deleted stale agent sockets in ~/%s\n",
_PATH_SSH_AGENT_SOCKET_DIR);
exit(0);
@@ -2424,35 +2445,23 @@ main(int ac, char **av)
* Create socket early so it will exist before command gets run from
* the parent.
*/
- if (agentsocket == NULL && !T_flag) {
- /* Default case: ~/.ssh/agent/[socket] */
+ if (agentsocket == NULL) {
+ /* Listen on a socket in/under a given directory */
if ((homedir = get_homedir()) == NULL)
fatal("Couldn't determine home directory");
- if (!U_flag)
- agent_cleanup_stale(homedir, 0);
- if (agent_listener(homedir, "agent", &sock, &socket_name) != 0)
+ if (!U_flag) {
+ agent_cleanup_stale(socket_dirspec,
+ pw->pw_name, pw->pw_uid, homedir, 0);
+ }
+ if (agent_listener(socket_dirspec, pw->pw_name, pw->pw_uid,
+ homedir, getpid(), "local", &sock, &socket_name,
+ &socket_dir) != 0)
fatal_f("Couldn't prepare agent socket");
free(homedir);
} else {
- if (T_flag) {
- /*
- * Create private directory for agent socket
- * in $TMPDIR.
- */
- mktemp_proto(socket_dir, sizeof(socket_dir));
- if (mkdtemp(socket_dir) == NULL) {
- perror("mkdtemp: private socket dir");
- exit(1);
- }
- xasprintf(&socket_name, "%s/agent.%ld",
- socket_dir, (long)parent_pid);
- } else {
- /* Try to use specified agent socket */
- socket_dir[0] = '\0';
- socket_name = xstrdup(agentsocket);
- }
- /* Listen on socket */
+ /* Listen on explicit socket path */
prev_mask = umask(0177);
+ socket_name = xstrdup(agentsocket);
if ((sock = unix_listener(socket_name,
SSH_LISTEN_BACKLOG, 0)) < 0) {
*socket_name = '\0'; /* Don't unlink existing file */
@@ -2552,7 +2561,7 @@ skip:
fatal("%s: unveil %s %s", __progname, socket_name,
strerror(errno));
}
- if (*socket_dir != '\0' && unveil(socket_dir, "c") == -1) {
+ if (socket_dir != NULL && unveil(socket_dir, "c") == -1) {
fatal("%s: unveil %s %s", __progname, socket_dir,
strerror(errno));
}
diff --git a/sshd_config.5 b/sshd_config.5
index 51620b0..edc690b 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -97,6 +97,31 @@ Valid arguments are
(use IPv4 only), or
.Cm inet6
(use IPv6 only).
+.It Cm AgentSocketPath
+Specifies the filesystem path used for forwarded
+.Xr ssh-agent 1
+sockets.
+Sockets may be created in either a shared location or a user-specific
+directory.
+User-specific directories are specified by prefixing the path name with
+.Cm user: .
+.Xr sshd 8
+will ensure the directory exists and create the listening socket
+directly in it.
+Relative user-specific directory paths will be created to the user's
+.Ev $HOME .
+.Pp
+Shared directories may be specified by prefixing an absolute path name with
+.Cm shared: .
+In this case, a temporary subdirectory will be created under the specified
+directory and the listening agent socket will be created in that.
+.Pp
+.Cm AgentSocketPath
+accepts the tokens described in the
+.Sx TOKENS
+section.
+The default path speification is
+.Pa user:.ssh/agent .
.It Cm AllowAgentForwarding
Specifies whether
.Xr ssh-agent 1
@@ -2227,6 +2252,9 @@ The numeric user ID of the target user.
The username.
.El
.Pp
+.Cm AgentSocketPath
+accepts the tokens %%, %h, %U, and %u.
+.Pp
.Cm AuthorizedKeysCommand
accepts the tokens %%, %C, %D, %f, %h, %k, %t, %U, and %u.
.Pp
ssh: support both /tmp and ~/.ssh for agent sockets