/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 *
 * Copyright © 2018 Endless Mobile, Inc.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors:
 *  - Philip Withnall <withnall@endlessm.com>
 */

#include "config.h"

#include <glib.h>
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include <libgsystemservice/peer-manager.h>


/**
 * SECTION:peer-manager
 * @short_description: D-Bus peer management and notification
 * @stability: Stable
 * @include: libgsystemservice/peer-manager.h
 *
 * A peer manager is an abstraction over the management of peers on a D-Bus
 * connection, monitoring when they disappear, and allowing querying and caching
 * of their credentials.
 *
 * Currently, the only credential stored is the path to the peer’s executable,
 * which can be used to identify that peer. In future, other credentials may be
 * added (and the API will change accordingly).
 *
 * The default implementation for production use is #GssPeerManagerDBus,
 * which uses the D-Bus daemon to get credentials. Unit tests will use a
 * different implementation, allowing them to provide fake data to the service
 * easily.
 *
 * Since: 0.1.0
 */

G_DEFINE_QUARK (GssPeerManagerError, gss_peer_manager_error)

/**
 * GssPeerManager:
 *
 * An interface for D-Bus peer management. The typical production implementation
 * of this is #GssPeerManagerDBus; other implementations can be written to be
 * used during testing, returning mock results.
 *
 * Since: 0.1.0
 */

G_DEFINE_INTERFACE (GssPeerManager, gss_peer_manager, G_TYPE_OBJECT)

static void
gss_peer_manager_default_init (GssPeerManagerInterface *iface)
{
  /**
   * GssPeerManager::peer-vanished:
   * @self: a #GssPeerManager
   * @name: unique name of the D-Bus peer which vanished
   *
   * Emitted when a peer disappears off the bus. The peer’s unique name will be
   * given as @name.
   *
   * Since: 0.1.0
   */
  g_signal_new ("peer-vanished", G_TYPE_FROM_INTERFACE (iface),
                G_SIGNAL_RUN_LAST,
                0, NULL, NULL, NULL,
                G_TYPE_NONE, 1,
                G_TYPE_STRING);
}

/**
 * gss_peer_manager_ensure_peer_credentials_async:
 * @self: a #GssPeerManager
 * @sender: D-Bus unique name for the peer
 * @cancellable: (nullable): a #GCancellable, or %NULL
 * @callback: callback to call once the asynchronous operation is complete
 * @user_data: data to pass to @callback
 *
 * Ensure the credentials for a peer are in the peer manager, querying them from
 * the D-Bus daemon if needed.
 *
 * Also start watching the @sender, so that if it disappears from the bus, a
 * #GssPeerManager::peer-vanished signal will be emitted.
 *
 * On failure, a #GDBusError is returned if querying the D-Bus daemon failed,
 * or %GSS_PEER_MANAGER_ERROR_IDENTIFYING_PEER is returned if interpreting the
 * information it returned failed.
 *
 * Since: 0.1.0
 */
void
gss_peer_manager_ensure_peer_credentials_async (GssPeerManager      *self,
                                                const gchar         *sender,
                                                GCancellable        *cancellable,
                                                GAsyncReadyCallback  callback,
                                                gpointer             user_data)
{
  g_return_if_fail (GSS_IS_PEER_MANAGER (self));
  g_return_if_fail (g_dbus_is_unique_name (sender));
  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

  GssPeerManagerInterface *iface = GSS_PEER_MANAGER_GET_IFACE (self);
  g_assert (iface->ensure_peer_credentials_async != NULL);
  iface->ensure_peer_credentials_async (self, sender, cancellable, callback, user_data);
}

/**
 * gss_peer_manager_ensure_peer_credentials_finish:
 * @self: a #GssPeerManager
 * @result: asynchronous operation result
 * @error: return location for a #GError
 *
 * Finish ensuring the credentials for a peer are in the peer manager. See
 * gss_peer_manager_ensure_peer_credentials_async().
 *
 * If this returns %TRUE, the credentials can be queried from the cache using
 * the accessor methods on #GssPeerManager.
 *
 * Returns: %TRUE on success, %FALSE otherwise
 * Since: 0.3.0
 */
gboolean
gss_peer_manager_ensure_peer_credentials_finish (GssPeerManager  *self,
                                                 GAsyncResult    *result,
                                                 GError         **error)
{
  g_return_val_if_fail (GSS_IS_PEER_MANAGER (self), FALSE);
  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  GssPeerManagerInterface *iface = GSS_PEER_MANAGER_GET_IFACE (self);
  g_assert (iface->ensure_peer_credentials_finish != NULL);

  g_autoptr(GError) local_error = NULL;
  gboolean success = iface->ensure_peer_credentials_finish (self, result,
                                                            &local_error);
  g_return_val_if_fail (success == (local_error == NULL), FALSE);

  if (local_error != NULL)
    g_propagate_error (error, g_steal_pointer (&local_error));
  return success;
}

/**
 * gss_peer_manager_get_peer_argv0:
 * @self: a #GssPeerManager
 * @sender: D-Bus unique name for the peer
 *
 * Get the first argument from `/proc/${pid}/cmdline` for the given peer.
 *
 * If no credentials are in the cache for @sender, %NULL will be returned.
 *
 * Returns: (nullable): path to the argv0 for the peer, or %NULL if it’s
 *    unknown
 * Since: 0.3.0
 */
const gchar *
gss_peer_manager_get_peer_argv0 (GssPeerManager *self,
                                 const gchar    *sender)
{
  g_return_val_if_fail (GSS_IS_PEER_MANAGER (self), NULL);
  g_return_val_if_fail (g_dbus_is_unique_name (sender), NULL);

  GssPeerManagerInterface *iface = GSS_PEER_MANAGER_GET_IFACE (self);
  g_assert (iface->get_peer_argv0 != NULL);

  return iface->get_peer_argv0 (self, sender);
}

/**
 * gss_peer_manager_get_peer_pidfd:
 * @self: a #GssPeerManager
 * @sender: D-Bus unique name for the peer
 *
 * Get a pidfd which references the given peer’s process.
 *
 * If no credentials are in the cache for @sender, `-1` will be returned.
 *
 * Returns: (nullable): pidfd for the peer’s process, or `-1` if it’s unknown
 * Since: 0.3.0
 */
int
gss_peer_manager_get_peer_pidfd (GssPeerManager *self,
                                 const gchar    *sender)
{
  g_return_val_if_fail (GSS_IS_PEER_MANAGER (self), -1);
  g_return_val_if_fail (g_dbus_is_unique_name (sender), -1);

  GssPeerManagerInterface *iface = GSS_PEER_MANAGER_GET_IFACE (self);
  g_assert (iface->get_peer_pidfd != NULL);

  return iface->get_peer_pidfd (self, sender);
}

/**
 * gss_peer_manager_get_peer_uid:
 * @self: a #GssPeerManager
 * @sender: D-Bus unique name for the peer
 *
 * Get the Unix user ID running the given peer’s process.
 *
 * If no credentials are in the cache for @sender, `(uid_t) -1` will be returned.
 *
 * Returns: (nullable): Unix user ID running the peer’s process, or `(uid_t) -1`
 *    if it’s unknown
 * Since: 0.3.0
 */
uid_t
gss_peer_manager_get_peer_uid (GssPeerManager *self,
                               const gchar    *sender)
{
  g_return_val_if_fail (GSS_IS_PEER_MANAGER (self), (uid_t) -1);
  g_return_val_if_fail (g_dbus_is_unique_name (sender), (uid_t) -1);

  GssPeerManagerInterface *iface = GSS_PEER_MANAGER_GET_IFACE (self);
  g_assert (iface->get_peer_uid != NULL);

  return iface->get_peer_uid (self, sender);
}
