[openssh-commits] [openssh] 04/14: upstream: Allow specification of agent socket directories
git+noreply at mindrot.org
git+noreply at mindrot.org
Wed Sep 16 11:15:58 AEST 2026
This is an automated email from the git hooks/post-receive script.
djm pushed a commit to branch master
in repository openssh.
commit cee6aedb1a176959164ab92556402e316469b8fb
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Wed Sep 16 00:25:50 2026 +0000
upstream: Allow specification of agent socket directories
Add AgentSocketPath for sshd_config and -A flag for ssh-agent.
Agent socket directories may be shared or user-specific.
Shared directories (specified like "shared:/tmp") will cause the listening
program to create a temporary subdirectory ssh-XXXXXXXXXX under the requeted
path to hold the socket. This supports the old sshd/ssh-agent behaviour before
we switched to the socket directory being under ~/.ssh/agent
User-specific directories just create the socket directly in the requested
directory. This is the default, as user:.ssh/agent
bz3860; ok markus, deraadt
OpenBSD-Commit-ID: 91b95d02f376ad8959ffb23902cc115a0b74d9bf
---
misc-agent.c | 238 ++++++++++++++++++++++++++++++++++++++++++++--------------
misc.h | 10 ++-
pathnames.h | 9 ++-
servconf.c | 29 ++++++-
servconf.h | 6 +-
session.c | 11 ++-
ssh-agent.1 | 39 ++++++++--
ssh-agent.c | 77 ++++++++++---------
sshd_config.5 | 30 +++++++-
9 files changed, 335 insertions(+), 114 deletions(-)
diff --git a/misc-agent.c b/misc-agent.c
index cb61405a7..eaf08b079 100644
--- a/misc-agent.c
+++ b/misc-agent.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc-agent.c,v 1.7 2026/02/11 17:05:32 dtucker Exp $ */
+/* $OpenBSD: misc-agent.c,v 1.8 2026/09/16 00:25:50 djm Exp $ */
/*
* Copyright (c) 2025 Damien Miller <djm at mindrot.org>
*
@@ -30,6 +30,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <libgen.h>
#include "digest.h"
#include "log.h"
@@ -78,7 +79,7 @@ hostname_hash(size_t len)
return xstrdup(p);
}
-char *
+static char *
agent_hostname_hash(void)
{
return hostname_hash(SOCKET_HOSTNAME_HASHLEN);
@@ -154,71 +155,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;
}
@@ -271,14 +389,25 @@ socket_is_stale(const char *path)
#endif
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 = NULL;
+ char *prefix = NULL, *dir = NULL, *path;
struct timespec now, sub, *mtimp = NULL;
+ /* 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));
@@ -296,20 +425,14 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash)
}
xasprintf(&prefix, "s.%s.", path);
free(path);
- path = NULL;
}
- 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;
}
-
- path = NULL;
while ((dp = readdir(d)) != NULL) {
- free(path);
- xasprintf(&path, "%s/%s", dirpath, dp->d_name);
#ifdef HAVE_DIRENT_D_TYPE
if (dp->d_type != DT_SOCK && dp->d_type != DT_UNKNOWN)
continue;
@@ -317,7 +440,7 @@ 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))
@@ -331,25 +454,26 @@ agent_cleanup_stale(const char *homedir, int ignore_hosthash)
#endif
if (timespeccmp(mtimp, &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", dir, dp->d_name);
if (socket_is_stale(path)) {
debug_f("cleanup stale socket %s", path);
unlinkat(dirfd(d), dp->d_name, 0);
}
+ free(path);
}
out:
if (d != NULL)
closedir(d);
- free(path);
- free(dirpath);
+ free(dir);
free(prefix);
}
diff --git a/misc.h b/misc.h
index 87899a90d..589213f21 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.118 2026/09/16 00:13:58 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.119 2026/09/16 00:25:50 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -241,9 +241,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 2f0b9f4f6..8713153dc 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: pathnames.h,v 1.37 2026/06/14 03:59:34 djm Exp $ */
+/* $OpenBSD: pathnames.h,v 1.38 2026/09/16 00:25:50 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -68,10 +68,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 a5bf3479a..becf72b81 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.454 2026/09/16 00:16:52 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.455 2026/09/16 00:25:50 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
* All rights reserved
@@ -414,6 +414,8 @@ fill_default_server_options(ServerOptions *options)
options->pubkey_auth_options = 0;
options->max_pubkey_ok = DEFAULT_AUTH_FAIL_MAX;
}
+ if (options->agent_socket_path == NULL)
+ options->agent_socket_path = xstrdup(_PATH_SSH_AGENT_SOCKET_DIR);
assemble_algorithms(options);
@@ -445,6 +447,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]);
@@ -1673,6 +1676,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;
@@ -4341,6 +4367,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 089d600ad..e54e56c62 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.181 2026/09/16 00:16:52 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.182 2026/09/16 00:25:50 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -240,7 +240,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) \
@@ -433,7 +434,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 fc9c9d6f8..9125cbb83 100644
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.c,v 1.350 2026/06/05 08:53:07 djm Exp $ */
+/* $OpenBSD: session.c,v 1.351 2026/09/16 00:25:50 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
* All rights reserved
@@ -168,6 +168,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 */
@@ -176,7 +177,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();
}
@@ -196,7 +199,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 ff1653386..bd1677e74 100644
--- a/ssh-agent.1
+++ b/ssh-agent.1
@@ -1,4 +1,4 @@
-.\" $OpenBSD: ssh-agent.1,v 1.87 2026/05/27 03:04:30 djm Exp $
+.\" $OpenBSD: ssh-agent.1,v 1.88 2026/09/16 00:25:50 djm Exp $
.\"
.\" Author: Tatu Ylonen <ylo at cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -34,7 +34,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd $Mdocdate: May 27 2026 $
+.Dd $Mdocdate: September 16 2026 $
.Dt SSH-AGENT 1
.Os
.Sh NAME
@@ -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 1604f540a..2a7e1d259 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-agent.c,v 1.331 2026/08/07 05:18:05 djm Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.332 2026/09/16 00:25:50 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo at cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -63,6 +63,7 @@
#include <time.h>
#include <unistd.h>
#include <util.h>
+#include <pwd.h>
#include "xmalloc.h"
#include "ssh.h"
@@ -166,7 +167,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;
@@ -2190,12 +2192,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
@@ -2238,9 +2240,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"
@@ -2273,6 +2277,7 @@ main(int ac, char **av)
u_int maxfds;
sigset_t nsigset, osigset;
int socket_activated = 0;
+ struct passwd *pw;
/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd();
@@ -2283,6 +2288,10 @@ main(int ac, char **av)
platform_disable_tracing(0); /* strict=no */
+ if ((pw = getpwuid(getuid())) == NULL)
+ fatal("No user exists for uid %lu", (u_long)getuid());
+ pw = pwcopy(pw);
+
#ifdef RLIMIT_NOFILE
if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
fatal("%s: getrlimit: %s", __progname, strerror(errno));
@@ -2291,7 +2300,7 @@ main(int ac, char **av)
__progname = ssh_get_progname(av[0]);
seed_rng();
- 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);
@@ -2342,6 +2351,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");
@@ -2371,6 +2383,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,
@@ -2381,6 +2396,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 &&
@@ -2414,7 +2434,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);
@@ -2453,35 +2474,23 @@ main(int ac, char **av)
socket_activated = 1;
}
- if (sock == -1 && agentsocket == NULL && !T_flag) {
- /* Default case: ~/.ssh/agent/[socket] */
+ if (sock == -1 && 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 (sock == -1) {
- 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 */
@@ -2603,7 +2612,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 0d9f88061..bfd208901 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -33,7 +33,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: sshd_config.5,v 1.405 2026/09/16 00:16:52 djm Exp $
+.\" $OpenBSD: sshd_config.5,v 1.406 2026/09/16 00:25:50 djm Exp $
.Dd $Mdocdate: September 16 2026 $
.Dt SSHD_CONFIG 5
.Os
@@ -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
@@ -2279,6 +2304,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
--
To stop receiving notification emails like this one, please contact
djm at mindrot.org.
More information about the openssh-commits
mailing list