From 0f89a0d2f0218c26d3764f4987e8d2e5c3df9238 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Tue, 29 Sep 2020 09:56:58 +0100 Subject: [PATCH] Use pkttyagent to request user passwords if running without GUI This change will allow it to use pkcon over remote-shells (ssh) or to use it witout a running GUI desktop environment in the background. Should fix https://github.com/fwupd/fwupd/issues/2429 --- src/fu-polkit-agent.c | 237 ++++++++++++++++++++++++++++++++++++++++++ src/fu-polkit-agent.h | 13 +++ src/fu-progressbar.c | 6 +- src/fu-util.c | 14 +++ src/meson.build | 1 + 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/fu-polkit-agent.c create mode 100644 src/fu-polkit-agent.h diff --git a/src/fu-polkit-agent.c b/src/fu-polkit-agent.c new file mode 100644 index 000000000..28991320a --- /dev/null +++ b/src/fu-polkit-agent.c @@ -0,0 +1,237 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Lennart Poettering + * Copyright (C) 2012 Matthias Klumpp + * Copyright (C) 2015-2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include +#include +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fu-common.h" +#include "fu-polkit-agent.h" + +static pid_t agent_pid = 0; + +static int +fork_agent (pid_t *pid, const char *path, ...) +{ + char **l; + gboolean stderr_is_tty; + gboolean stdout_is_tty; + int fd; + pid_t n_agent_pid; + pid_t parent_pid; + unsigned n, i; + va_list ap; + + g_return_val_if_fail (pid != 0, 0); + g_assert (path); + + parent_pid = getpid (); + + /* spawns a temporary TTY agent, making sure it goes away when + * we go away */ + n_agent_pid = fork (); + if (n_agent_pid < 0) + return -errno; + + if (n_agent_pid != 0) { + *pid = n_agent_pid; + return 0; + } + +#ifdef __linux__ + /* make sure the agent goes away when the parent dies */ + if (prctl (PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit (EXIT_FAILURE); +#endif + /* check whether our parent died before we were able + * to set the death signal */ + if (getppid () != parent_pid) + _exit (EXIT_SUCCESS); + + /* TODO: it might be more clean to close all FDs so we don't leak them to the agent */ + stdout_is_tty = isatty (STDOUT_FILENO); + stderr_is_tty = isatty (STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + g_error ("Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + if (!stdout_is_tty) + dup2 (fd, STDOUT_FILENO); + if (!stderr_is_tty) + dup2 (fd, STDERR_FILENO); + if (fd > 2) + close (fd); + } + + /* count arguments */ + va_start (ap, path); + for (n = 0; va_arg (ap, char*); n++) + ; + va_end(ap); + + /* allocate strv */ + l = alloca (sizeof(char *) * (n + 1)); + + /* fill in arguments */ + va_start (ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg (ap, char*); + va_end (ap); + + execv (path, l); + _exit (EXIT_FAILURE); +} + +static int +close_nointr (int fd) +{ + g_assert (fd >= 0); + for (;;) { + int r; + r = close (fd); + if (r >= 0) + return r; + if (errno != EINTR) + return -errno; + } +} + +static void +close_nointr_nofail (int fd) +{ + int saved_errno = errno; + /* cannot fail, and guarantees errno is unchanged */ + g_assert (close_nointr (fd) == 0); + errno = saved_errno; +} + +static int +fd_wait_for_event (int fd, int event, uint64_t t) +{ + struct pollfd pollfd = { 0 }; + int r; + + pollfd.fd = fd; + pollfd.events = event; + r = poll (&pollfd, 1, t == (uint64_t) -1 ? -1 : (int) (t / 1000)); + if (r < 0) + return -errno; + if (r == 0) + return 0; + + return pollfd.revents; +} + +static int +wait_for_terminate (pid_t pid) +{ + g_return_val_if_fail (pid >= 1, 0); + + for (;;) { + int status; + if (waitpid (pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -errno; + } + return 0; + } +} + +gboolean +fu_polkit_agent_open (GError **error) +{ + int r; + int pipe_fd[2]; + g_autofree gchar *notify_fd = NULL; + g_autofree gchar *pkttyagent_fn = NULL; + + if (agent_pid > 0) + return TRUE; + + /* find binary */ + pkttyagent_fn = fu_common_find_program_in_path ("pkttyagent", error); + if (pkttyagent_fn == NULL) + return FALSE; + + /* check STDIN here, not STDOUT, since this is about input, not output */ + if (!isatty (STDIN_FILENO)) + return TRUE; + if (pipe (pipe_fd) < 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to create pipe: %s", + strerror (-errno)); + return FALSE; + } + + /* fork pkttyagent */ + notify_fd = g_strdup_printf ("%i", pipe_fd[1]); + r = fork_agent (&agent_pid, + pkttyagent_fn, + pkttyagent_fn, + "--notify-fd", notify_fd, + "--fallback", NULL); + if (r < 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to fork TTY ask password agent: %s", + strerror (-r)); + close_nointr_nofail (pipe_fd[1]); + close_nointr_nofail (pipe_fd[0]); + return FALSE; + } + + /* close the writing side, because that is the one for the agent */ + close_nointr_nofail (pipe_fd[1]); + + /* wait until the agent closes the fd */ + fd_wait_for_event (pipe_fd[0], POLLHUP, (uint64_t) -1); + + close_nointr_nofail (pipe_fd[0]); + return TRUE; +} + +void +fu_polkit_agent_close (void) { + + if (agent_pid <= 0) + return; + + /* inform agent that we are done */ + kill (agent_pid, SIGTERM); + kill (agent_pid, SIGCONT); + wait_for_terminate (agent_pid); + agent_pid = 0; +} diff --git a/src/fu-polkit-agent.h b/src/fu-polkit-agent.h new file mode 100644 index 000000000..15f3b831e --- /dev/null +++ b/src/fu-polkit-agent.h @@ -0,0 +1,13 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2011 Lennart Poettering + * Copyright (C) 2012 Matthias Klumpp + * Copyright (C) 2015-2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +gboolean fu_polkit_agent_open (GError **error); +void fu_polkit_agent_close (void); diff --git a/src/fu-progressbar.c b/src/fu-progressbar.c index db0bd6834..9a7378c5a 100644 --- a/src/fu-progressbar.c +++ b/src/fu-progressbar.c @@ -162,6 +162,8 @@ fu_progressbar_refresh (FuProgressbar *self, FwupdStatus status, guint percentag percentage = 100; status = self->status; is_idle_newline = TRUE; + } else if (status == FWUPD_STATUS_WAITING_FOR_AUTH) { + is_idle_newline = TRUE; } title = fu_progressbar_status_to_string (status); g_string_append (str, title); @@ -243,7 +245,8 @@ fu_progressbar_spin_cb (gpointer user_data) FuProgressbar *self = FU_PROGRESSBAR (user_data); /* ignore */ - if (self->status == FWUPD_STATUS_IDLE) + if (self->status == FWUPD_STATUS_IDLE || + self->status == FWUPD_STATUS_WAITING_FOR_AUTH) return G_SOURCE_CONTINUE; /* move the spinner index up to down */ @@ -310,6 +313,7 @@ fu_progressbar_update (FuProgressbar *self, FwupdStatus status, guint percentage * execute the callback just do the refresh now manually */ if (percentage == 0 && status != FWUPD_STATUS_IDLE && + status != FWUPD_STATUS_WAITING_FOR_AUTH && self->status != FWUPD_STATUS_UNKNOWN) { if ((g_get_monotonic_time () - self->last_animated) / 1000 > 40) { fu_progressbar_spin_inc (self); diff --git a/src/fu-util.c b/src/fu-util.c index 617d2c366..c582a5178 100644 --- a/src/fu-util.c +++ b/src/fu-util.c @@ -25,6 +25,7 @@ #include "fu-history.h" #include "fu-plugin-private.h" +#include "fu-polkit-agent.h" #include "fu-progressbar.h" #include "fu-security-attrs.h" #include "fu-util-common.h" @@ -2507,6 +2508,7 @@ main (int argc, char *argv[]) gboolean version = FALSE; g_autoptr(FuUtilPrivate) priv = g_new0 (FuUtilPrivate, 1); g_autoptr(GError) error = NULL; + g_autoptr(GError) error_polkit = NULL; g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new (); g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *filter = NULL; @@ -2845,6 +2847,14 @@ main (int argc, char *argv[]) if (no_history) priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; + /* start polkit tty agent to listen for password requests */ + if (is_interactive) { + if (!fu_polkit_agent_open (&error_polkit)) { + g_printerr ("Failed to open polkit agent: %s\n", + error_polkit->message); + } + } + /* connect to the daemon */ priv->client = fwupd_client_new (); g_signal_connect (priv->client, "notify::percentage", @@ -2930,6 +2940,10 @@ main (int argc, char *argv[]) return EXIT_FAILURE; } + + /* stop listening for polkit questions */ + fu_polkit_agent_close (); + /* success */ return EXIT_SUCCESS; } diff --git a/src/meson.build b/src/meson.build index 8a07de033..953d4705f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,7 @@ fwupdmgr = executable( 'fu-progressbar.c', 'fu-security-attr.c', 'fu-util-common.c', + 'fu-polkit-agent.c', systemd_src ], include_directories : [