summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-04-06 10:54:35 +0200
committerThomas Haller <thaller@redhat.com>2020-04-09 21:51:06 +0200
commitb954aa96f7ca72aaa4e1fa25bfb19130f696a005 (patch)
tree5a802a988db46f28df2a2cb1c52534393c41ce98
parentde2c6f318de54277bcb145269c3df259d3d2743c (diff)
downloadNetworkManager-th/cli-command-cleanup.tar.gz
cli/polkit: make parsing polkit-agent-helper-1 protocol more conformingth/cli-command-cleanup
- in io_watch_have_data(), ensure that we handle incomplete lines that don't yet have a newline by waiting for more data. That means, if the current content of the in_buffer does not have a newline, we wait longer. - in io_watch_have_data(), implement (and ignore) certain commands instead of failing the request. - in io_watch_have_data(), no longer g_compress() the entire line. "polkitagenthelper-pam.c" never backslash escapes the command, it only escapes the arguments. Of course, there should be no difference in practice, except that we don't want to handle escape sequences in the commands. - in io_watch_have_data(), compare SUCCESS/FAILURE literally. "polkitagenthelper-pam.c" never appends any trailing garbage to these commands, and we shouldn't handle that (although "polkitagentsession.c" does). - when io_watch_have_data() completes with success, we cannot destroy AuthRequest right away. It probably still has data pending that we first need to write to the polkit helper. Wait longer, and let io_watch_can_write() complete the request. - ensure we always answer the GDBusMethodInvocation. Otherwise, it gets leaked. - use NMStrBuf instead of GString.
-rw-r--r--clients/common/nm-polkit-listener.c254
-rw-r--r--shared/nm-glib-aux/nm-io-utils.c44
-rw-r--r--shared/nm-glib-aux/nm-io-utils.h4
3 files changed, 162 insertions, 140 deletions
diff --git a/clients/common/nm-polkit-listener.c b/clients/common/nm-polkit-listener.c
index ae40206ca7..7691ff4e06 100644
--- a/clients/common/nm-polkit-listener.c
+++ b/clients/common/nm-polkit-listener.c
@@ -27,6 +27,7 @@
#include <fcntl.h>
#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-glib-aux/nm-str-buf.h"
#include "nm-glib-aux/nm-secret-utils.h"
#include "nm-glib-aux/nm-io-utils.h"
#include "nm-libnm-core-intern/nm-auth-subject.h"
@@ -84,19 +85,22 @@ typedef struct {
CList request_lst;
NMPolkitListener *listener;
+ GSource *child_stdout_watch_source;
+ GSource *child_stdin_watch_source;
+ GDBusMethodInvocation *dbus_invocation;
char *action_id;
char *message;
char *username;
char *cookie;
- GString *in_buffer;
- GString *out_buffer;
- size_t out_buffer_offset;
+ NMStrBuf in_buffer;
+ NMStrBuf out_buffer;
+ gsize out_buffer_offset;
int child_stdout;
int child_stdin;
- GSource *child_stdout_watch_source;
- GSource *child_stdin_watch_source;
- GDBusMethodInvocation *dbus_invocation;
+
+ bool request_any_response:1;
+ bool request_is_completed:1;
} AuthRequest;
static const GDBusInterfaceInfo interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
@@ -123,10 +127,18 @@ static const GDBusInterfaceInfo interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO_
);
static void
-remove_request (AuthRequest *request)
+auth_request_complete (AuthRequest *request, gboolean success)
{
c_list_unlink (&request->request_lst);
+ if (success)
+ g_dbus_method_invocation_return_value (request->dbus_invocation, NULL);
+ else {
+ g_dbus_method_invocation_return_dbus_error (request->dbus_invocation,
+ POLKIT_DBUS_ERROR_FAILED,
+ "");
+ }
+
nm_clear_g_free (&request->action_id);
nm_clear_g_free (&request->message);
nm_clear_g_free (&request->username);
@@ -134,10 +146,8 @@ remove_request (AuthRequest *request)
nm_clear_g_source_inst (&request->child_stdout_watch_source);
nm_clear_g_source_inst (&request->child_stdin_watch_source);
- nm_explicit_bzero (request->out_buffer->str,
- request->out_buffer->len);
- g_string_free (request->out_buffer, TRUE);
- g_string_free (request->in_buffer, TRUE);
+ nm_str_buf_destroy (&request->in_buffer);
+ nm_str_buf_destroy (&request->out_buffer);
if (request->child_stdout != -1) {
nm_close (request->child_stdout);
@@ -149,7 +159,7 @@ remove_request (AuthRequest *request)
request->child_stdin = -1;
}
- g_slice_free (AuthRequest, request);
+ nm_g_slice_free (request);
}
static gboolean
@@ -384,60 +394,42 @@ retrieve_session_id (NMPolkitListener *self)
self);
}
-static void
-complete_authentication (AuthRequest *request,
- gboolean result)
-{
- if (result) {
- g_dbus_method_invocation_return_value (request->dbus_invocation, NULL);
- } else {
- g_dbus_method_invocation_return_dbus_error (request->dbus_invocation,
- "org.freedesktop.PolicyKit1.Error.Failed",
- "");
- }
- remove_request (request);
-}
-
static gboolean
io_watch_can_write (int fd,
GIOCondition condition,
gpointer user_data)
{
AuthRequest *request = user_data;
- ssize_t n_written;
- gboolean done = FALSE;
+ gssize n_written;
- if (condition & G_IO_HUP ||
- condition & G_IO_ERR) {
- done = TRUE;
+ if (NM_FLAGS_ANY (condition, (G_IO_HUP | G_IO_ERR)))
goto done;
- }
n_written = write (request->child_stdin,
- &request->out_buffer->str[request->out_buffer_offset],
- request->out_buffer->len - request->out_buffer_offset);
+ &((nm_str_buf_get_str_unsafe (&request->out_buffer))[request->out_buffer_offset]),
+ request->out_buffer.len - request->out_buffer_offset);
- if (n_written < 0 && errno != EAGAIN) {
- done = TRUE;
+ if ( n_written < 0
+ && errno != EAGAIN)
goto done;
- }
if (n_written > 0) {
- if ((size_t) n_written == (request->out_buffer->len - request->out_buffer_offset)) {
- done = TRUE;
+ if ((gsize) n_written >= (request->out_buffer.len - request->out_buffer_offset)) {
+ nm_assert ((gsize) n_written == (request->out_buffer.len - request->out_buffer_offset));
goto done;
}
- request->out_buffer_offset += n_written;
+ request->out_buffer_offset += (gsize) n_written;
}
+ return G_SOURCE_CONTINUE;
+
done:
- if (done) {
- nm_explicit_bzero (request->out_buffer->str,
- request->out_buffer->len);
- g_string_set_size (request->out_buffer, 0);
- request->out_buffer_offset = 0;
- nm_clear_g_source_inst (&request->child_stdin_watch_source);
- }
+ nm_str_buf_set_size (&request->out_buffer, 0, TRUE, FALSE);
+ request->out_buffer_offset = 0;
+ nm_clear_g_source_inst (&request->child_stdin_watch_source);
+
+ if (request->request_is_completed)
+ auth_request_complete (request, TRUE);
return G_SOURCE_CONTINUE;
}
@@ -447,11 +439,11 @@ queue_string_to_helper (AuthRequest *request, const char *response)
{
g_return_if_fail (response);
- g_string_append (request->out_buffer, response);
+ if (!nm_str_buf_is_initalized (&request->out_buffer))
+ nm_str_buf_init (&request->out_buffer, strlen (response) + 2u, TRUE);
- if ( request->out_buffer->len == 0
- || request->out_buffer->str[request->out_buffer->len - 1] != '\n')
- g_string_append_c (request->out_buffer, '\n');
+ nm_str_buf_append (&request->out_buffer, response);
+ nm_str_buf_ensure_trailing_c (&request->out_buffer, '\n');
if (!request->child_stdin_watch_source) {
request->child_stdin_watch_source = nm_g_unix_fd_source_new (request->child_stdin,
@@ -471,74 +463,87 @@ io_watch_have_data (int fd,
gpointer user_data)
{
AuthRequest *request = user_data;
- gs_free char *unescaped = NULL;
- char *response = NULL;
- char* line_terminator = 0;
- gboolean auth_result = FALSE;
- gboolean complete_auth = FALSE;
- ssize_t n_read;
-
- if (condition & G_IO_HUP ||
- condition & G_IO_ERR) {
- complete_auth = TRUE;
- auth_result = FALSE;
- goto out;
- }
-
- n_read = nm_utils_fd_read (fd, request->in_buffer);
-
- if (n_read == -EAGAIN) {
- return G_SOURCE_CONTINUE;
- }
+ gboolean auth_result;
+ gssize n_read;
+
+ if (NM_FLAGS_ANY (condition, G_IO_HUP | G_IO_ERR))
+ n_read = -EIO;
+ else
+ n_read = nm_utils_fd_read (fd, &request->in_buffer);
+
+ if (n_read <= 0) {
+ if (n_read == -EAGAIN) {
+ /* wait longer. */
+ return G_SOURCE_CONTINUE;
+ }
- if (n_read < 0) {
- complete_auth = TRUE;
- auth_result = FALSE;
+ /* Either an error or EOF happened. The data we parsed so far was not relevant.
+ * Regardless of what we still have unprocessed in the receive buffers, we are done.
+ *
+ * We would expect that the other side completed with SUCCESS or FAILURE. Apparently
+ * it didn't. If we had any good request, we assume success. */
+ auth_result = request->request_any_response;
goto out;
}
- line_terminator = strchr (request->in_buffer->str, '\n');
- if (!line_terminator) {
- return G_SOURCE_CONTINUE;
- }
- *line_terminator = '\0';
-
- unescaped = g_strcompress (request->in_buffer->str);
+ while (TRUE) {
+ char *line_terminator;
+ const char *line;
- if (NM_STR_HAS_PREFIX (unescaped, "PAM_PROMPT_ECHO")) {
- /* FIXME(cli-async): emit signal and wait for response (blocking) */
- g_signal_emit (request->listener,
- signals[REQUEST_SYNC],
- 0,
- request->action_id,
- request->message,
- request->username,
- &response);
-
- if (response) {
- queue_string_to_helper (request, response);
- nm_free_secret (response);
+ line = nm_str_buf_get_str (&request->in_buffer);
+ line_terminator = (char *) strchr (line, '\n');
+ if (!line_terminator) {
+ /* We don't have a complete line. Wait longer. */
+ return G_SOURCE_CONTINUE;
+ }
+ line_terminator[0] = '\0';
+
+ if ( NM_STR_HAS_PREFIX (line, "PAM_PROMPT_ECHO_OFF ")
+ || NM_STR_HAS_PREFIX (line, "PAM_PROMPT_ECHO_ON ")) {
+ nm_auto_free_secret char *response = NULL;
+
+ /* FIXME(cli-async): emit signal and wait for response (blocking) */
+ g_signal_emit (request->listener,
+ signals[REQUEST_SYNC],
+ 0,
+ request->action_id,
+ request->message,
+ request->username,
+ &response);
+
+ if (response) {
+ queue_string_to_helper (request, response);
+ request->request_any_response = TRUE;
+ goto erase_line;
+ }
+ auth_result = FALSE;
+ } else if (nm_streq (line, "SUCCESS"))
+ auth_result = TRUE;
+ else if (nm_streq (line, "FAILURE"))
+ auth_result = FALSE;
+ else if ( NM_STR_HAS_PREFIX (line, "PAM_ERROR_MSG ")
+ || NM_STR_HAS_PREFIX (line, "PAM_TEXT_INFO ")) {
+ /* ignore. */
+ goto erase_line;
} else {
- complete_auth = TRUE;
+ /* unknown command. Fail. */
auth_result = FALSE;
}
- } else if (NM_STR_HAS_PREFIX (unescaped, "SUCCESS")) {
- complete_auth = TRUE;
- auth_result = TRUE;
- } else if (NM_STR_HAS_PREFIX (unescaped, "FAILURE")) {
- complete_auth = TRUE;
- auth_result = FALSE;
- } else {
- complete_auth = TRUE;
- auth_result = FALSE;
+
+ break;
+erase_line:
+ nm_str_buf_erase (&request->in_buffer, 0, line_terminator - line + 1u, TRUE);
}
out:
- g_string_set_size (request->in_buffer, 0);
+ request->request_is_completed = TRUE;
+ nm_clear_g_source_inst (&request->child_stdout_watch_source);
+ if ( auth_result
+ && request->child_stdin_watch_source) {
+ /* we need to wait for the buffer to send the response. */
+ } else
+ auth_request_complete (request, auth_result);
- if (complete_auth) {
- complete_authentication (request, auth_result);
- }
return G_SOURCE_CONTINUE;
}
@@ -553,7 +558,7 @@ begin_authentication (AuthRequest *request)
};
if (!request->username) {
- complete_authentication (request, FALSE);
+ auth_request_complete (request, FALSE);
return;
}
@@ -576,7 +581,7 @@ begin_authentication (AuthRequest *request)
0,
"The PolicyKit setuid helper 'polkit-agent-helper-1' has not been found");
- complete_authentication (request, FALSE);
+ auth_request_complete (request, FALSE);
return;
}
@@ -623,19 +628,21 @@ create_request (NMPolkitListener *listener,
char *username_take,
const char *cookie)
{
- AuthRequest *request = g_slice_new0(AuthRequest);
+ AuthRequest *request;
- request->listener = listener;
- request->dbus_invocation = invocation;
- request->action_id = g_strdup (action_id);
- request->message = g_strdup (message);
- request->username = g_steal_pointer (&username_take);
- request->cookie = g_strdup (cookie);
- request->in_buffer = g_string_new ("");
+ request = g_slice_new (AuthRequest);
+ *request = (AuthRequest) {
+ .listener = listener,
+ .dbus_invocation = invocation,
+ .action_id = g_strdup (action_id),
+ .message = g_strdup (message),
+ .username = g_steal_pointer (&username_take),
+ .cookie = g_strdup (cookie),
+ .request_any_response = FALSE,
+ .request_is_completed = FALSE,
+ };
- /* preallocate a large enough buffer so that
- * secrets don't get reallocated, thus leaked */
- request->out_buffer = g_string_sized_new (1024);
+ nm_str_buf_init (&request->in_buffer, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
c_list_link_tail (&listener->request_lst_head, &request->request_lst);
return request;
@@ -696,7 +703,7 @@ dbus_method_call_cb (GDBusConnection *connection,
}
/* Complete a cancelled request with success. */
- complete_authentication (request, TRUE);
+ auth_request_complete (request, TRUE);
return;
}
@@ -895,9 +902,8 @@ dispose (GObject *object)
nm_clear_g_cancellable (&self->cancellable);
- while ((request = c_list_first_entry (&self->request_lst_head, AuthRequest, request_lst))) {
- remove_request (request);
- }
+ while ((request = c_list_first_entry (&self->request_lst_head, AuthRequest, request_lst)))
+ auth_request_complete (request, FALSE);
if (self->dbus_connection) {
nm_clear_g_dbus_connection_signal (self->dbus_connection,
diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c
index 9620c688df..776c63e111 100644
--- a/shared/nm-glib-aux/nm-io-utils.c
+++ b/shared/nm-glib-aux/nm-io-utils.c
@@ -11,6 +11,7 @@
#include <sys/stat.h>
#include <fcntl.h>
+#include "nm-str-buf.h"
#include "nm-shared-utils.h"
#include "nm-secret-utils.h"
#include "nm-errno.h"
@@ -418,27 +419,40 @@ nm_utils_file_stat (const char *filename, struct stat *out_st)
* @fd: the fd to read from.
* @out_string: (out): output string where read bytes will be stored.
*
- * Returns: <0 on failure, which is -(errno)
- * 0 on EOF or if the call would block (if the fd is nonblocking),
- * >0 on success, which is the number of bytes read */
-ssize_t
-nm_utils_fd_read (int fd, GString *out_string)
+ * Returns: <0 on failure, which is -(errno).
+ * 0 on EOF.
+ * >0 on success, which is the number of bytes read. */
+gssize
+nm_utils_fd_read (int fd, NMStrBuf *out_string)
{
- size_t start_len;
- ssize_t n_read;
+ gsize buf_available;
+ gssize n_read;
+ int errsv;
g_return_val_if_fail (fd >= 0, -1);
g_return_val_if_fail (out_string, -1);
- start_len = out_string->len;
- g_string_set_size (out_string, start_len + 1024);
-
- n_read = read (fd, &out_string->str[start_len], 1024);
- if (n_read < 0)
- return -NM_ERRNO_NATIVE (errno);
+ /* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes)
+ * the first time. Afterwards, the buffer grows exponentially.
+ *
+ * Note that with @buf_available, we always would read as much buffer as we actually
+ * have reserved. */
+ nm_str_buf_maybe_expand (out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+
+ buf_available = out_string->allocated - out_string->len;
+
+ n_read = read (fd,
+ &((nm_str_buf_get_str_unsafe (out_string))[out_string->len]),
+ buf_available);
+ if (n_read < 0) {
+ errsv = errno;
+ return -NM_ERRNO_NATIVE (errsv);
+ }
- if (n_read > 0)
- g_string_set_size (out_string, start_len + (gsize) n_read);
+ if (n_read > 0) {
+ nm_assert ((gsize) n_read <= buf_available);
+ nm_str_buf_set_size (out_string, out_string->len + (gsize) n_read, TRUE, FALSE);
+ }
return n_read;
}
diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h
index dd2f499d6f..31326fc79a 100644
--- a/shared/nm-glib-aux/nm-io-utils.h
+++ b/shared/nm-glib-aux/nm-io-utils.h
@@ -47,7 +47,9 @@ gboolean nm_utils_file_set_contents (const char *filename,
int *out_errsv,
GError **error);
-ssize_t nm_utils_fd_read (int fd, GString *out_string);
+struct _NMStrBuf;
+
+gssize nm_utils_fd_read (int fd, struct _NMStrBuf *out_string);
struct stat;