]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
Support systemd-style socket activation in agent
authorDamien Miller <djm@mindrot.org>
Wed, 4 Dec 2024 13:01:33 +0000 (00:01 +1100)
committerDamien Miller <djm@mindrot.org>
Wed, 4 Dec 2024 13:01:33 +0000 (00:01 +1100)
Adds support for systemd LISTEN_PID/LISTEN_FDS socket activation to
ssh-agent. Activated when these environment variables are set and
the agent is started with the -d or -D option and no socket path
is set.

Based on GHPR502 by Daniel Kahn Gillmor, ok dtucker

ssh-agent.1
ssh-agent.c

index 2f5b091ee04fb672d05a3cc68e5c91e693367ef7..062e87bbf6b9fe8d63994c55537eff9a8f02abd9 100644 (file)
@@ -190,7 +190,7 @@ The agent exits automatically when the command given on the command
 line terminates.
 .El
 .Pp
-There are two main ways to get an agent set up.
+There are three main ways to get an agent set up.
 The first is at the start of an X session,
 where all other windows or programs are started as children of the
 .Nm
@@ -208,11 +208,33 @@ it prints the shell commands required to set its environment variables,
 which in turn can be evaluated in the calling shell, for example
 .Cm eval `ssh-agent -s` .
 .Pp
-In both cases,
+In both of these cases,
 .Xr ssh 1
 looks at these environment variables
 and uses them to establish a connection to the agent.
 .Pp
+The third way to run
+.Nm
+is via socket activation from a supervising process, such as systemd.
+In this mode, the supervising process creates the listening socket and
+is responsible for starting
+.Nm
+as needed, and also for communicating the location of the socket listener
+to other programs in the user's session.
+Socket activation is used when
+.Nm
+is started with either of the
+.Fl d
+or
+.Fl D
+flags, so socket listening address specified by the
+.Fl a
+flag, and both the
+.Ev LISTEN_FDS
+and
+.Ev LISTEN_PID
+environment variables correctly supplied by the supervising process.
+.Pp
 The agent initially does not have any private keys.
 Keys are added using
 .Xr ssh-add 1
index 96c25b9d5718134230c5ba8a86ea765dfd4879e1..48973b2c142addaf9f091876973584bd799d0b10 100644 (file)
@@ -2215,8 +2215,9 @@ int
 main(int ac, char **av)
 {
        int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0;
-       int sock, ch, result, saved_errno;
-       char *shell, *format, *pidstr, *agentsocket = NULL;
+       int sock = -1, ch, result, saved_errno;
+       char *shell, *format, *fdstr, *pidstr, *agentsocket = NULL;
+       const char *errstr = NULL;
        const char *ccp;
 #ifdef HAVE_SETRLIMIT
        struct rlimit rlim;
@@ -2329,8 +2330,6 @@ main(int ac, char **av)
                        c_flag = 1;
        }
        if (k_flag) {
-               const char *errstr = NULL;
-
                pidstr = getenv(SSH_AGENTPID_ENV_NAME);
                if (pidstr == NULL) {
                        fprintf(stderr, "%s not set, cannot kill agent\n",
@@ -2368,33 +2367,59 @@ main(int ac, char **av)
 
        parent_pid = getpid();
 
-       if (agentsocket == NULL) {
-               /* Create private directory for agent socket */
-               mktemp_proto(socket_dir, sizeof(socket_dir));
-               if (mkdtemp(socket_dir) == NULL) {
-                       perror("mkdtemp: private socket dir");
-                       exit(1);
+       /* Has the socket been provided via socket activation? */
+       if (agentsocket == NULL && ac == 0 && (d_flag || D_flag) &&
+           (pidstr = getenv("LISTEN_PID")) != NULL &&
+           (fdstr = getenv("LISTEN_FDS")) != NULL) {
+               if (strcmp(fdstr, "1") != 0) {
+                       fatal("unexpected LISTEN_FDS contents "
+                           "(want: \"1\" got\"%s\"", fdstr);
                }
-               snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir,
+               if (fcntl(3, F_GETFL) == -1)
+                       fatal("LISTEN_FDS set but fd 3 unavailable");
+               pid = (int)strtonum(pidstr, 1, INT_MAX, &errstr);
+               if (errstr != NULL)
+                       fatal("invalid LISTEN_PID: %s", errstr);
+               if (pid != getpid())
+                       fatal("bad LISTEN_PID: %d vs pid %d", pid, getpid());
+               debug("using socket activation on fd=3");
+               sock = 3;
+       }
+
+       /* Otherwise, create private directory for agent socket */
+       if (sock == -1) {
+               if (agentsocket == NULL) {
+                       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);
-       } else {
-               /* Try to use specified agent socket */
-               socket_dir[0] = '\0';
-               strlcpy(socket_name, agentsocket, sizeof socket_name);
+               } else {
+                       /* Try to use specified agent socket */
+                       socket_dir[0] = '\0';
+                       strlcpy(socket_name, agentsocket, sizeof socket_name);
+               }
        }
 
+       closefrom(sock == -1 ? STDERR_FILENO + 1 : sock + 1);
+
        /*
         * Create socket early so it will exist before command gets run from
         * the parent.
         */
-       prev_mask = umask(0177);
-       sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0);
-       if (sock < 0) {
-               /* XXX - unix_listener() calls error() not perror() */
-               *socket_name = '\0'; /* Don't unlink any existing file */
-               cleanup_exit(1);
+       if (sock == -1) {
+               prev_mask = umask(0177);
+               sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0);
+               if (sock < 0) {
+                       /* XXX - unix_listener() calls error() not perror() */
+                       *socket_name = '\0'; /* Don't unlink existing file */
+                       cleanup_exit(1);
+               }
+               umask(prev_mask);
        }
-       umask(prev_mask);
 
        /*
         * Fork, and have the parent execute the command, if any, or present
@@ -2404,11 +2429,14 @@ main(int ac, char **av)
                log_init(__progname,
                    d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO,
                    SYSLOG_FACILITY_AUTH, 1);
-               format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n";
-               printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
-                   SSH_AUTHSOCKET_ENV_NAME);
-               printf("echo Agent pid %ld;\n", (long)parent_pid);
-               fflush(stdout);
+               if (socket_name[0] != '\0') {
+                       format = c_flag ?
+                           "setenv %s %s;\n" : "%s=%s; export %s;\n";
+                       printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
+                           SSH_AUTHSOCKET_ENV_NAME);
+                       printf("echo Agent pid %ld;\n", (long)parent_pid);
+                       fflush(stdout);
+               }
                goto skip;
        }
        pid = fork();