mirror of
git://git.gnupg.org/gnupg.git
synced 2025-04-17 15:44:34 +02:00
agent: Implement --supervised command (for systemd, etc).
* agent/gpg-agent.c (get_socket_path): New function for POSIX systems to return the path for a provided unix-domain socket. (map_supervised_sockets): New function to inspect $LISTEN_FDS and $LISTEN_FDNAMES and map them to the specific functionality offered by the agent. (main): Add --supervised command. When used, listen on already-open file descriptors instead of opening our own. * doc/gpg-agent.texi: Document --supervised option. -- "gpg-agent --supervised" is a way to invoke gpg-agent such that a system supervisor like systemd can provide socket-activated startup, log management, and scheduled shutdown. When running in this mode, gpg-agent: * Does not open its own listening socket; rather, it expects to be given a listening socket on incoming file descriptors. * Does not detach from the invoking process, staying in the foreground instead. Unless otherwise specified, logs are sent to stderr. Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
This commit is contained in:
parent
4a232d23a8
commit
9f92b62a51
@ -90,6 +90,7 @@ enum cmd_and_opt_values
|
|||||||
oLogFile,
|
oLogFile,
|
||||||
oServer,
|
oServer,
|
||||||
oDaemon,
|
oDaemon,
|
||||||
|
oSupervised,
|
||||||
oBatch,
|
oBatch,
|
||||||
|
|
||||||
oPinentryProgram,
|
oPinentryProgram,
|
||||||
@ -152,6 +153,7 @@ static ARGPARSE_OPTS opts[] = {
|
|||||||
|
|
||||||
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
|
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
|
||||||
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
|
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
|
||||||
|
ARGPARSE_s_n (oSupervised, "supervised", N_("run supervised (e.g., systemd)")),
|
||||||
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
|
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
|
||||||
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
|
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
|
||||||
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
|
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
|
||||||
@ -730,6 +732,7 @@ thread_init_once (void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
initialize_modules (void)
|
initialize_modules (void)
|
||||||
{
|
{
|
||||||
@ -742,6 +745,180 @@ initialize_modules (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* return a malloc'ed string that is the path to the passed unix-domain socket
|
||||||
|
(or return NULL if this is not a valid unix-domain socket) */
|
||||||
|
static char *
|
||||||
|
get_socket_path (gnupg_fd_t fd)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_W32_SYSTEM
|
||||||
|
return NULL;
|
||||||
|
#else
|
||||||
|
struct sockaddr_un un;
|
||||||
|
socklen_t len = sizeof(un);
|
||||||
|
char *ret = NULL;
|
||||||
|
|
||||||
|
if (fd == GNUPG_INVALID_FD)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (getsockname (fd, (struct sockaddr*)&un, &len) != 0)
|
||||||
|
log_error ("could not getsockname(%d) -- error %d (%s)\n", fd,
|
||||||
|
errno, strerror(errno));
|
||||||
|
else if (un.sun_family != AF_UNIX)
|
||||||
|
log_error ("file descriptor %d is not a unix-domain socket\n", fd);
|
||||||
|
else if (len <= offsetof (struct sockaddr_un, sun_path))
|
||||||
|
log_error ("socket path not present for file descriptor %d\n", fd);
|
||||||
|
else if (len > sizeof(un))
|
||||||
|
log_error ("socket path for file descriptor %d was truncated "
|
||||||
|
"(passed %lu bytes, wanted %u)\n", fd, sizeof(un), len);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log_debug ("file descriptor %d has path %s (%lu octets)\n", fd,
|
||||||
|
un.sun_path, len - offsetof (struct sockaddr_un, sun_path));
|
||||||
|
ret = malloc(len - offsetof (struct sockaddr_un, sun_path));
|
||||||
|
if (ret == NULL)
|
||||||
|
log_error ("failed to allocate memory for path to file "
|
||||||
|
"descriptor %d\n", fd);
|
||||||
|
else
|
||||||
|
memcpy (ret, un.sun_path, len);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
#endif /* HAVE_W32_SYSTEM */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Discover which inherited file descriptors correspond to which
|
||||||
|
services/sockets offered by gpg-agent, using the LISTEN_FDS and
|
||||||
|
LISTEN_FDNAMES convention. The understood labels are "ssh",
|
||||||
|
"extra", and "browser". Any other label will be interpreted as the
|
||||||
|
standard socket.
|
||||||
|
|
||||||
|
This function is designed to log errors when the expected file
|
||||||
|
descriptors don't make sense, but to do its best to continue to
|
||||||
|
work even in the face of minor misconfigurations.
|
||||||
|
|
||||||
|
For more information on the LISTEN_FDS convention, see
|
||||||
|
sd_listen_fds(3).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
map_supervised_sockets (gnupg_fd_t *fd,
|
||||||
|
gnupg_fd_t *fd_extra,
|
||||||
|
gnupg_fd_t *fd_browser,
|
||||||
|
gnupg_fd_t *fd_ssh)
|
||||||
|
{
|
||||||
|
const char *listen_pid = NULL;
|
||||||
|
const char *listen_fds = NULL;
|
||||||
|
const char *listen_fdnames = NULL;
|
||||||
|
int listen_fd_count = -1;
|
||||||
|
int listen_fdnames_colons = 0;
|
||||||
|
const char *fdnamep = NULL;
|
||||||
|
|
||||||
|
listen_pid = getenv ("LISTEN_PID");
|
||||||
|
listen_fds = getenv ("LISTEN_FDS");
|
||||||
|
listen_fdnames = getenv ("LISTEN_FDNAMES");
|
||||||
|
|
||||||
|
if (!listen_pid)
|
||||||
|
log_error ("no $LISTEN_PID environment variable found in "
|
||||||
|
"--supervised mode (ignoring).\n");
|
||||||
|
else if (atoi (listen_pid) != getpid ())
|
||||||
|
log_error ("$LISTEN_PID (%d) does not match process ID (%d) "
|
||||||
|
"in --supervised mode (ignoring).\n",
|
||||||
|
atoi (listen_pid), getpid ());
|
||||||
|
else
|
||||||
|
log_debug ("$LISTEN_PID matches process ID (%d)\n",
|
||||||
|
getpid());
|
||||||
|
|
||||||
|
if (listen_fdnames)
|
||||||
|
for (fdnamep = listen_fdnames; *fdnamep; fdnamep++)
|
||||||
|
if (*fdnamep == ':')
|
||||||
|
listen_fdnames_colons++;
|
||||||
|
log_debug ("%d colon(s) in $LISTEN_FDNAMES: (%s)\n", listen_fdnames_colons, listen_fdnames);
|
||||||
|
|
||||||
|
if (!listen_fds)
|
||||||
|
{
|
||||||
|
if (!listen_fdnames)
|
||||||
|
{
|
||||||
|
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
|
||||||
|
"found in --supervised mode (assuming 1 active descriptor).\n");
|
||||||
|
listen_fd_count = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log_error ("no LISTEN_FDS environment variable found in --supervised "
|
||||||
|
" mode (relying on colons in LISTEN_FDNAMES instead)\n");
|
||||||
|
listen_fd_count = listen_fdnames_colons + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
listen_fd_count = atoi (listen_fds);
|
||||||
|
|
||||||
|
if (listen_fd_count < 1)
|
||||||
|
{
|
||||||
|
log_error ("--supervised mode expects at least one file descriptor (was told %d) "
|
||||||
|
"(carrying on as though it were 1)\n", listen_fd_count);
|
||||||
|
listen_fd_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listen_fdnames)
|
||||||
|
{
|
||||||
|
if (listen_fd_count != 1)
|
||||||
|
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1 in --supervised mode. "
|
||||||
|
"(ignoring all sockets but the first one)\n", listen_fd_count);
|
||||||
|
*fd = 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (listen_fd_count != listen_fdnames_colons + 1)
|
||||||
|
{
|
||||||
|
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
|
||||||
|
"LISTEN_FDS (%d) in --supervised mode\n",
|
||||||
|
listen_fdnames_colons + 1, listen_fd_count);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 3; i < 3 + listen_fd_count; i++)
|
||||||
|
{
|
||||||
|
int found = 0;
|
||||||
|
char *next = strchrnul(listen_fdnames, ':');
|
||||||
|
*next = '\0';
|
||||||
|
#define match_socket(var) if (!found && strcmp (listen_fdnames, #var) == 0) \
|
||||||
|
{ \
|
||||||
|
found = 1; \
|
||||||
|
if (*fd_ ## var == GNUPG_INVALID_FD) \
|
||||||
|
{ \
|
||||||
|
*fd_ ## var = i; \
|
||||||
|
log_info (#var " socket on fd %d\n", i); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
log_error ("cannot listen on more than one " #var " socket. (closing fd %d)\n", i); \
|
||||||
|
close (i); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
match_socket(ssh);
|
||||||
|
match_socket(browser);
|
||||||
|
match_socket(extra);
|
||||||
|
#undef match_socket
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
if (*fd == GNUPG_INVALID_FD)
|
||||||
|
{
|
||||||
|
*fd = i;
|
||||||
|
log_info ("standard socket (\"%s\") on fd %d\n",
|
||||||
|
listen_fdnames, i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log_error ("cannot listen on more than one standard socket. (closing fd %d)\n", i);
|
||||||
|
close (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listen_fdnames = next + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* The main entry point. */
|
/* The main entry point. */
|
||||||
int
|
int
|
||||||
main (int argc, char **argv )
|
main (int argc, char **argv )
|
||||||
@ -757,6 +934,7 @@ main (int argc, char **argv )
|
|||||||
int default_config =1;
|
int default_config =1;
|
||||||
int pipe_server = 0;
|
int pipe_server = 0;
|
||||||
int is_daemon = 0;
|
int is_daemon = 0;
|
||||||
|
int is_supervised = 0;
|
||||||
int nodetach = 0;
|
int nodetach = 0;
|
||||||
int csh_style = 0;
|
int csh_style = 0;
|
||||||
char *logfile = NULL;
|
char *logfile = NULL;
|
||||||
@ -954,6 +1132,7 @@ main (int argc, char **argv )
|
|||||||
case oSh: csh_style = 0; break;
|
case oSh: csh_style = 0; break;
|
||||||
case oServer: pipe_server = 1; break;
|
case oServer: pipe_server = 1; break;
|
||||||
case oDaemon: is_daemon = 1; break;
|
case oDaemon: is_daemon = 1; break;
|
||||||
|
case oSupervised: is_supervised = 1; break;
|
||||||
|
|
||||||
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
|
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
|
||||||
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
|
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
|
||||||
@ -1053,9 +1232,9 @@ main (int argc, char **argv )
|
|||||||
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
|
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!pipe_server && !is_daemon && !gpgconf_list)
|
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
|
||||||
{
|
{
|
||||||
/* We have been called without any options and thus we merely
|
/* We have been called without any command and thus we merely
|
||||||
check whether an agent is already running. We do this right
|
check whether an agent is already running. We do this right
|
||||||
here so that we don't clobber a logfile with this check but
|
here so that we don't clobber a logfile with this check but
|
||||||
print the status directly to stderr. */
|
print the status directly to stderr. */
|
||||||
@ -1234,6 +1413,54 @@ main (int argc, char **argv )
|
|||||||
agent_deinit_default_ctrl (ctrl);
|
agent_deinit_default_ctrl (ctrl);
|
||||||
xfree (ctrl);
|
xfree (ctrl);
|
||||||
}
|
}
|
||||||
|
else if (is_supervised)
|
||||||
|
{
|
||||||
|
gnupg_fd_t fd = GNUPG_INVALID_FD;
|
||||||
|
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
|
||||||
|
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
|
||||||
|
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
|
||||||
|
|
||||||
|
/* when supervised and sending logs to stderr, the process
|
||||||
|
supervisor should handle log entry metadata (pid, name,
|
||||||
|
timestamp) */
|
||||||
|
if (!logfile)
|
||||||
|
log_set_prefix (NULL, 0);
|
||||||
|
|
||||||
|
log_info ("%s %s starting in supervised mode.\n",
|
||||||
|
strusage(11), strusage(13) );
|
||||||
|
|
||||||
|
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
|
||||||
|
if (fd == GNUPG_INVALID_FD)
|
||||||
|
{
|
||||||
|
log_fatal ("no standard socket provided\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
/* record socket names where possible: */
|
||||||
|
socket_name = get_socket_path (fd);
|
||||||
|
socket_name_extra = get_socket_path (fd_extra);
|
||||||
|
if (socket_name_extra)
|
||||||
|
opt.extra_socket = 2;
|
||||||
|
socket_name_browser = get_socket_path (fd_browser);
|
||||||
|
if (socket_name_browser)
|
||||||
|
opt.browser_socket = 2;
|
||||||
|
socket_name_ssh = get_socket_path (fd_ssh);
|
||||||
|
|
||||||
|
#ifdef HAVE_SIGPROCMASK
|
||||||
|
if (startup_signal_mask_valid)
|
||||||
|
{
|
||||||
|
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
|
||||||
|
log_error ("error restoring signal mask: %s\n",
|
||||||
|
strerror (errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
log_info ("no saved signal mask\n");
|
||||||
|
#endif /*HAVE_SIGPROCMASK*/
|
||||||
|
|
||||||
|
log_debug ("FDs: std: %d extra: %d browser: %d ssh: %d\n",
|
||||||
|
fd, fd_extra, fd_browser, fd_ssh);
|
||||||
|
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
|
||||||
|
assuan_sock_close (fd);
|
||||||
|
}
|
||||||
else if (!is_daemon)
|
else if (!is_daemon)
|
||||||
; /* NOTREACHED */
|
; /* NOTREACHED */
|
||||||
else
|
else
|
||||||
@ -1246,6 +1473,8 @@ main (int argc, char **argv )
|
|||||||
pid_t pid;
|
pid_t pid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
initialize_modules ();
|
||||||
|
|
||||||
/* Remove the DISPLAY variable so that a pinentry does not
|
/* Remove the DISPLAY variable so that a pinentry does not
|
||||||
default to a specific display. There is still a default
|
default to a specific display. There is still a default
|
||||||
display when gpg-agent was started using --display or a
|
display when gpg-agent was started using --display or a
|
||||||
|
@ -158,6 +158,18 @@ As an alternative you may create a new process as a child of
|
|||||||
gpg-agent: @code{gpg-agent --daemon /bin/sh}. This way you get a new
|
gpg-agent: @code{gpg-agent --daemon /bin/sh}. This way you get a new
|
||||||
shell with the environment setup properly; after you exit from this
|
shell with the environment setup properly; after you exit from this
|
||||||
shell, gpg-agent terminates within a few seconds.
|
shell, gpg-agent terminates within a few seconds.
|
||||||
|
|
||||||
|
@item --supervised
|
||||||
|
@opindex supervised
|
||||||
|
Run in the foreground, sending logs by default to stderr, and
|
||||||
|
listening on provided file descriptors, which must already be bound to
|
||||||
|
listening sockets. This command is useful when running under systemd
|
||||||
|
or other similar process supervision schemes.
|
||||||
|
|
||||||
|
In --supervised mode, different file descriptors can be provided for
|
||||||
|
use as different socket types (e.g. ssh, extra) as long as they are
|
||||||
|
identified in the environment variable $LISTEN_FDNAMES (see
|
||||||
|
sd_listen_fds(3) for more information on this convention).
|
||||||
@end table
|
@end table
|
||||||
|
|
||||||
@mansect options
|
@mansect options
|
||||||
|
Loading…
x
Reference in New Issue
Block a user