auth2-gss.o gss-serv.o gss-serv-krb5.o \
loginrec.o auth-pam.o auth-shadow.o auth-sia.o \
sftp-server.o sftp-common.o \
- uidswap.o platform-listen.o $(SKOBJS)
+ uidswap.o platform-listen.o misc-agent.o $(SKOBJS)
SSHD_AUTH_OBJS=sshd-auth.o \
auth2-methods.o \
sandbox-null.o sandbox-rlimit.o sandbox-darwin.o \
sandbox-seccomp-filter.o sandbox-capsicum.o sandbox-solaris.o \
sftp-server.o sftp-common.o \
- uidswap.o $(SKOBJS)
+ uidswap.o misc-agent.o $(SKOBJS)
SFTP_CLIENT_OBJS=sftp-common.o sftp-client.o sftp-glob.o
SSHADD_OBJS= ssh-add.o $(SKOBJS)
-SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o $(SKOBJS)
+SSHAGENT_OBJS= ssh-agent.o ssh-pkcs11-client.o misc-agent.o $(SKOBJS)
SSHKEYGEN_OBJS= ssh-keygen.o sshsig.o $(SKOBJS)
-/* $OpenBSD: hostfile.c,v 1.97 2025/04/30 05:26:15 djm Exp $ */
+/* $OpenBSD: hostfile.c,v 1.98 2025/05/05 02:48:07 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
-/* $OpenBSD: misc.c,v 1.198 2024/10/24 03:14:37 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.199 2025/05/05 02:48:06 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005-2020 Damien Miller. All rights reserved.
}
return 0;
}
+
+char *
+get_homedir(void)
+{
+ char *cp;
+ struct passwd *pw;
+
+ if ((cp = getenv("HOME")) != NULL && *cp != '\0')
+ return xstrdup(cp);
+
+ if ((pw = getpwuid(getuid())) != NULL && *pw->pw_dir != '\0')
+ return xstrdup(pw->pw_dir);
+
+ return NULL;
+}
-/* $OpenBSD: misc.h,v 1.110 2024/09/25 01:24:04 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.111 2025/05/05 02:48:06 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
int path_absolute(const char *);
int stdfd_devnull(int, int, int);
int lib_contains_symbol(const char *, const char *);
+char *get_homedir(void);
void sock_set_v6only(int);
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);
+
/* readpass.c */
#define RP_ECHO 0x0001
-/* $OpenBSD: pathnames.h,v 1.32 2024/05/17 00:30:24 djm Exp $ */
+/* $OpenBSD: pathnames.h,v 1.34 2025/05/05 02:48:06 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
*/
#define _PATH_SSH_USER_DIR ".ssh"
+
+/*
+ * The directory in which 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"
+
/*
* Per-user file containing host keys of known hosts. This file need not be
* readable by anyone except the user him/herself, though this does not
-/* $OpenBSD: session.c,v 1.341 2025/04/09 07:00:03 djm Exp $ */
+/* $OpenBSD: session.c,v 1.342 2025/05/05 02:48:06 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
/* Name and directory of socket for authentication agent forwarding. */
static char *auth_sock_name = NULL;
-static char *auth_sock_dir = NULL;
/* removes the agent forwarding socket */
if (auth_sock_name != NULL) {
temporarily_use_uid(pw);
unlink(auth_sock_name);
- rmdir(auth_sock_dir);
auth_sock_name = NULL;
restore_uid();
}
/* Temporarily drop privileged uid for mkdir/bind. */
temporarily_use_uid(pw);
- /* Allocate a buffer for the socket name, and format the name. */
- auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX");
-
- /* Create private directory for socket */
- if (mkdtemp(auth_sock_dir) == NULL) {
+ if (agent_listener(pw->pw_dir, "sshd", &sock, &auth_sock_name) != 0) {
+ /* a more detailed error is already logged */
ssh_packet_send_debug(ssh, "Agent forwarding disabled: "
- "mkdtemp() failed: %.100s", strerror(errno));
+ "couldn't create listener socket");
restore_uid();
- free(auth_sock_dir);
- auth_sock_dir = NULL;
goto authsock_err;
}
-
- xasprintf(&auth_sock_name, "%s/agent.%ld",
- auth_sock_dir, (long) getpid());
-
- /* Start a Unix listener on auth_sock_name. */
- sock = unix_listener(auth_sock_name, SSH_LISTEN_BACKLOG, 0);
-
- /* Restore the privileged uid. */
restore_uid();
- /* Check for socket/bind/listen failure. */
- if (sock < 0)
- goto authsock_err;
-
/* Allocate a channel for the authentication agent socket. */
nc = channel_new(ssh, "auth-listener",
SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1,
authsock_err:
free(auth_sock_name);
- if (auth_sock_dir != NULL) {
- temporarily_use_uid(pw);
- rmdir(auth_sock_dir);
- restore_uid();
- free(auth_sock_dir);
- }
if (sock != -1)
close(sock);
auth_sock_name = NULL;
- auth_sock_dir = NULL;
return 0;
}
-.\" $OpenBSD: ssh-agent.1,v 1.82 2025/02/09 18:24:08 schwarze Exp $
+.\" $OpenBSD: ssh-agent.1,v 1.83 2025/05/05 02:48:06 djm Exp $
.\"
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
.\" (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: February 9 2025 $
+.Dd $Mdocdate: May 5 2025 $
.Dt SSH-AGENT 1
.Os
.Sh NAME
.Sh SYNOPSIS
.Nm ssh-agent
.Op Fl c | s
-.Op Fl \&Dd
+.Op Fl \&DdTU
.Op 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 E Ar fingerprint_hash
.Op Fl O Ar option
.Nm ssh-agent
.Op Fl c | s
.Fl k
+.Nm ssh-agent
+.Fl u
.Sh DESCRIPTION
.Nm
is a program to hold private keys used for public key authentication.
.Ux Ns -domain
socket
.Ar bind_address .
-The default is
-.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt .
+The default is to create a socket at a random path matching
+.Pa $HOME/.ssh/agent/s.*
.It Fl c
Generate C-shell commands on standard output.
This is the default if
This is the default if
.Ev SHELL
does not look like it's a csh style of shell.
+.It Fl T
+Bind the agent socket in a randomised subdirectory of the form
+.Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt ,
+instead of the default behaviour of using a randomised name matching
+.Pa $HOME/.ssh/agent/s.* .
.It Fl t Ar life
Set a default value for the maximum lifetime of identities added to the agent.
The lifetime may be specified in seconds or in a time format specified in
this is executed as a subprocess of the agent.
The agent exits automatically when the command given on the command
line terminates.
+.It Fl U
+Instructs
+.Nm
+not to clean up stale agent sockets under
+.Pa $HOME/.ssh/agent/ .
+.It Fl u
+Instructs
+.Nm
+to only clean up stale agent sockets under
+.Pa $HOME/.ssh/agent/
+and then exit immediately.
+If this option is given twice,
+.Nm
+will delete stale agent sockets regardless of the host name that created them.
.El
.Pp
There are three main ways to get an agent set up.
-/* $OpenBSD: ssh-agent.c,v 1.311 2025/04/15 09:22:25 dtucker Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.312 2025/05/05 02:48:06 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
usage(void)
{
fprintf(stderr,
- "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n"
+ "usage: ssh-agent [-c | -s] [-DdTU] [-a bind_address] [-E fingerprint_hash]\n"
" [-O option] [-P allowed_providers] [-t life]\n"
- " ssh-agent [-a bind_address] [-E fingerprint_hash] [-O option]\n"
+ " ssh-agent [-TU] [-a bind_address] [-E fingerprint_hash] [-O option]\n"
" [-P allowed_providers] [-t life] command [arg ...]\n"
- " ssh-agent [-c | -s] -k\n");
+ " ssh-agent [-c | -s] -k\n"
+ " ssh-agent -u\n");
exit(1);
}
int
main(int ac, char **av)
{
- int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0;
+ int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0;
+ int s_flag = 0, T_flag = 0, u_flag = 0, U_flag = 0;
int sock = -1, ch, result, saved_errno;
- char *shell, *format, *fdstr, *pidstr, *agentsocket = NULL;
+ char *homedir = NULL, *shell, *format, *pidstr, *agentsocket = NULL;
+ char *fdstr;
const char *errstr = NULL;
const char *ccp;
#ifdef HAVE_SETRLIMIT
__progname = ssh_get_progname(av[0]);
seed_rng();
- while ((ch = getopt(ac, av, "cDdksE:a:O:P:t:")) != -1) {
+ while ((ch = getopt(ac, av, "cDdksTuUE:a:O:P:t:")) != -1) {
switch (ch) {
case 'E':
fingerprint_hash = ssh_digest_alg_by_name(optarg);
usage();
}
break;
+ case 'T':
+ T_flag++;
+ break;
+ case 'u':
+ u_flag++;
+ break;
+ case 'U':
+ U_flag++;
+ break;
default:
usage();
}
ac -= optind;
av += optind;
- if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag))
+ if (ac > 0 &&
+ (c_flag || k_flag || s_flag || d_flag || D_flag || u_flag))
usage();
+ log_init(__progname,
+ d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO,
+ SYSLOG_FACILITY_AUTH, 1);
+
if (allowed_providers == NULL)
allowed_providers = xstrdup(DEFAULT_ALLOWED_PROVIDERS);
if (websafe_allowlist == NULL)
printf("echo Agent pid %ld killed;\n", (long)pid);
exit(0);
}
+ if (u_flag) {
+ if ((homedir = get_homedir()) == NULL)
+ fatal("Couldn't determine home directory");
+ agent_cleanup_stale(homedir, u_flag > 1);
+ printf("Deleted stale agent sockets in ~/%s\n",
+ _PATH_SSH_AGENT_SOCKET_DIR);
+ exit(0);
+ }
/*
* Minimum file descriptors:
sock = 3;
}
- /* Otherwise, create private directory for agent socket */
- if (sock == -1) {
- if (agentsocket == NULL) {
+ if (sock == -1 && agentsocket == NULL && !T_flag) {
+ /* Default case: ~/.ssh/agent/[socket] */
+ 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, &agentsocket) != 0)
+ fatal_f("Couldn't prepare agent socket");
+ if (strlcpy(socket_name, agentsocket,
+ sizeof(socket_name)) >= sizeof(socket_name)) {
+ fatal_f("Socket path \"%s\" too long",
+ agentsocket);
+ }
+ free(homedir);
+ free(agentsocket);
+ agentsocket = NULL;
+ } 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);
}
- snprintf(socket_name, sizeof socket_name,
- "%s/agent.%ld", socket_dir,
- (long)parent_pid);
+ snprintf(socket_name, sizeof(socket_name),
+ "%s/agent.%ld", socket_dir, (long)parent_pid);
} else {
/* Try to use specified agent socket */
socket_dir[0] = '\0';
- strlcpy(socket_name, agentsocket, sizeof socket_name);
+ if (strlcpy(socket_name, agentsocket,
+ sizeof(socket_name)) >= sizeof(socket_name)) {
+ fatal_f("Socket path \"%s\" too long",
+ agentsocket);
+ }
+ }
+ /* Listen on socket */
+ prev_mask = umask(0177);
+ if ((sock = unix_listener(socket_name,
+ SSH_LISTEN_BACKLOG, 0)) < 0) {
+ *socket_name = '\0'; /* Don't unlink existing file */
+ cleanup_exit(1);
}
+ umask(prev_mask);
}
closefrom(sock == -1 ? STDERR_FILENO + 1 : sock + 1);