/*
 * Copyright (C) 2008 Red Hat, Inc.
 *
 * 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 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., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author: David Zeuthen <davidz@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <sys/types.h>
#ifdef HAVE_FREEBSD
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#endif
#ifdef HAVE_NETBSD
#include <sys/param.h>
#include <sys/sysctl.h>
#endif
#ifdef HAVE_OPENBSD
#include <sys/sysctl.h>
#endif
#ifdef HAVE_PIDFD_OPEN
#include <sys/syscall.h>
#endif /* HAVE_PIDFD_OPEN */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#include "polkitunixprocess.h"
#include "polkitsubject.h"
#include "polkitprivate.h"
#include "polkiterror.h"

/**
 * SECTION:polkitunixprocess
 * @title: PolkitUnixProcess
 * @short_description: Unix processs
 *
 * An object for representing a UNIX process. In order to be reliable and
 * race-free, this requires support for PID File Descriptors in the kernel,
 * dbus-daemon/broker and systemd. With this functionality, we can reliably
 * track processes without risking PID reuse and race conditions, and compare
 * them.
 *
 * NOTE: If PID FDs are not available, this object will fall back to using
 * PIDs, and this designed is now known broken; a mechanism to exploit a delay
 * in start time in the Linux kernel was identified.  Avoid
 * calling polkit_subject_equal() to compare two processes.
 *
 * To uniquely identify processes, both the process id and the start
 * time of the process (a monotonic increasing value representing the
 * time since the kernel was started) is used.
 *
 * NOTE: This object stores, and provides access to, the real UID of the
 * process.  That value can change over time (with set*uid*(2) and exec*(2)).
 * Checks whether an operation is allowed need to take care to use the UID
 * value as of the time when the operation was made (or, following the open()
 * privilege check model, when the connection making the operation possible
 * was initiated).  That is usually done by initializing this with
 * polkit_unix_process_new_for_owner() with trusted data.
 */

/* See https://gitlab.freedesktop.org/polkit/polkit/issues/75

  But quoting the original email in full here to ensure it's preserved:

  From: Jann Horn <jannh@google.com>
  Subject: [SECURITY] polkit: temporary auth hijacking via PID reuse and non-atomic fork
  Date: Wednesday, October 10, 2018 5:34 PM

When a (non-root) user attempts to e.g. control systemd units in the system
instance from an active session over DBus, the access is gated by a polkit
policy that requires "auth_admin_keep" auth. This results in an auth prompt
being shown to the user, asking the user to confirm the action by entering the
password of an administrator account.

After the action has been confirmed, the auth decision for "auth_admin_keep" is
cached for up to five minutes. Subject to some restrictions, similar actions can
then be performed in this timespan without requiring re-auth:

 - The PID of the DBus client requesting the new action must match the PID of
   the DBus client requesting the old action (based on SO_PEERCRED information
   forwarded by the DBus daemon).
 - The "start time" of the client's PID (as seen in /proc/$pid/stat, field 22)
   must not have changed. The granularity of this timestamp is in the
   millisecond range.
 - polkit polls every two seconds whether a process with the expected start time
   still exists. If not, the temporary auth entry is purged.

Without the start time check, this would obviously be buggy because an attacker
could simply wait for the legitimate client to disappear, then create a new
client with the same PID.

Unfortunately, the start time check is bypassable because fork() is not atomic.
Looking at the source code of copy_process() in the kernel:

        p->start_time = ktime_get_ns();
        p->real_start_time = ktime_get_boot_ns();
        [...]
        retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
        if (retval)
                goto bad_fork_cleanup_io;

        if (pid != &init_struct_pid) {
                pid = alloc_pid(p->nsproxy->pid_ns_for_children);
                if (IS_ERR(pid)) {
                        retval = PTR_ERR(pid);
                        goto bad_fork_cleanup_thread;
                }
        }

The ktime_get_boot_ns() call is where the "start time" of the process is
recorded. The alloc_pid() call is where a free PID is allocated. In between
these, some time passes; and because the copy_thread_tls() call between them can
access userspace memory when sys_clone() is invoked through the 32-bit syscall
entry point, an attacker can even stall the kernel arbitrarily long at this
point (by supplying a pointer into userspace memory that is associated with a
userfaultfd or is backed by a custom FUSE filesystem).

This means that an attacker can immediately call sys_clone() when the victim
process is created, often resulting in a process that has the exact same start
time reported in procfs; and then the attacker can delay the alloc_pid() call
until after the victim process has died and the PID assignment has cycled
around. This results in an attacker process that polkit can't distinguish from
the victim process.
*/


/**
 * PolkitUnixProcess:
 *
 * The #PolkitUnixProcess struct should not be accessed directly.
 */
struct _PolkitUnixProcess
{
  GObject parent_instance;

  gint pid;
  guint64 start_time;
  gint uid;
  gint pidfd;
  gboolean pidfd_is_safe;
  GArray *gids;
};

struct _PolkitUnixProcessClass
{
  GObjectClass parent_class;
};

enum
{
  PROP_0,
  PROP_PID,
  PROP_START_TIME,
  PROP_UID,
  PROP_PIDFD,
  PROP_PIDFD_IS_SAFE,
  PROP_GIDS,
};

static void subject_iface_init (PolkitSubjectIface *subject_iface);

static guint64 get_start_time_for_pid (gint    pid,
                                       GError **error);

#if defined(HAVE_FREEBSD) || defined(HAVE_NETBSD) || defined(HAVE_OPENBSD)
static gboolean get_kinfo_proc (gint pid,
#if defined(HAVE_NETBSD)
                                struct kinfo_proc2 *p);
#else
                                struct kinfo_proc *p);
#endif
#endif

G_DEFINE_TYPE_WITH_CODE (PolkitUnixProcess, polkit_unix_process, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (POLKIT_TYPE_SUBJECT, subject_iface_init)
                         );

static void
polkit_unix_process_init (PolkitUnixProcess *unix_process)
{
  unix_process->uid = -1;
  unix_process->pidfd = -1;
}

static void
polkit_unix_process_get_property (GObject    *object,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  PolkitUnixProcess *unix_process = POLKIT_UNIX_PROCESS (object);

  switch (prop_id)
    {
    case PROP_PID:
      g_value_set_int (value, polkit_unix_process_get_pid (unix_process));
      break;

    case PROP_UID:
      g_value_set_int (value, unix_process->uid);
      break;

    case PROP_GIDS:
      g_value_set_boxed (value, unix_process->gids);
      break;

    case PROP_PIDFD:
      g_value_set_int (value, unix_process->pidfd);
      break;

    case PROP_PIDFD_IS_SAFE:
      g_value_set_boolean (value, unix_process->pidfd_is_safe);
      break;

    case PROP_START_TIME:
      g_value_set_uint64 (value, unix_process->start_time);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
polkit_unix_process_set_property (GObject      *object,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  PolkitUnixProcess *unix_process = POLKIT_UNIX_PROCESS (object);

  switch (prop_id)
    {
    case PROP_PID:
      polkit_unix_process_set_pid (unix_process, g_value_get_int (value));
      break;

    case PROP_UID:
      polkit_unix_process_set_uid (unix_process, g_value_get_int (value));
      break;

    case PROP_GIDS:
      polkit_unix_process_set_gids (unix_process, g_value_get_boxed (value));
      break;

    case PROP_PIDFD:
      polkit_unix_process_set_pidfd (unix_process, g_value_get_int (value));
      break;

    case PROP_START_TIME:
      polkit_unix_process_set_start_time (unix_process, g_value_get_uint64 (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static gint
polkit_unix_process_get_pid_from_pidfd (PolkitUnixProcess  *process,
                                        GError            **error)
{
  gint result;
  gchar *contents;
  gchar **lines;
  gchar filename[64];
  guint n;

  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1);
  g_return_val_if_fail (error == NULL || *error == NULL, -1);
  g_return_val_if_fail (process->pidfd >= 0, -1);

  result = -1;
  lines = NULL;
  contents = NULL;

  g_snprintf (filename, sizeof filename, "/proc/self/fdinfo/%d", process->pidfd);
  if (!g_file_get_contents (filename,
                            &contents,
                            NULL,
                            error))
    goto out;

  lines = g_strsplit (contents, "\n", -1);
  for (n = 0; lines != NULL && lines[n] != NULL; n++)
    {
      gint pid;
      if (!g_str_has_prefix (lines[n], "Pid:"))
        continue;
      if (sscanf (lines[n] + 4, "%d", &pid) != 1)
        g_set_error (error,
                      POLKIT_ERROR,
                      POLKIT_ERROR_FAILED,
                      "Unexpected line `%s' in file %s",
                      lines[n],
                      filename);
      else
        result = pid;
      goto out;
    }

  g_set_error (error,
               POLKIT_ERROR,
               POLKIT_ERROR_FAILED,
               "Didn't find any line starting with `Pid:' in file %s",
               filename);

out:
  g_strfreev (lines);
  g_free (contents);
  return result;
}

static void
polkit_unix_process_constructed (GObject *object)
{
  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (object);

  /* sets pidfd, start_time and uid in case they are unset */

  /* We didn't open it ourselves here, so we must have got it
   * from D-Bus, mark it as safe to use */
  if (process->pidfd >= 0)
    process->pidfd_is_safe = TRUE;

#ifdef HAVE_PIDFD_OPEN
  if (process->pid > 0 && process->pidfd < 0)
    {
      gint pidfd = (int) syscall (SYS_pidfd_open, process->pid, 0);
      if (pidfd >= 0)
        {
          process->pidfd = pidfd;
          process->pid = 0;
        }
    }
#endif /* HAVE_PIDFD_OPEN */

  if (process->start_time == 0)
    process->start_time = get_start_time_for_pid (polkit_unix_process_get_pid (process), NULL);

  if (process->uid == -1)
    {
      GError *error;
      error = NULL;
      process->uid = polkit_unix_process_get_racy_uid__ (process, &error);
      if (error != NULL)
        {
          process->uid = -1;
          g_error_free (error);
        }
    }

  if (G_OBJECT_CLASS (polkit_unix_process_parent_class)->constructed != NULL)
    G_OBJECT_CLASS (polkit_unix_process_parent_class)->constructed (object);
}

static void
polkit_unix_process_finalize (GObject *object)
{
  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (object);

  if (process->pidfd >= 0)
    {
      close (process->pidfd);
      process->pidfd = -1;
    }

  if (process->gids)
    g_array_unref (process->gids);

  if (G_OBJECT_CLASS (polkit_unix_process_parent_class)->finalize != NULL)
    G_OBJECT_CLASS (polkit_unix_process_parent_class)->finalize (object);
}

static void
polkit_unix_process_class_init (PolkitUnixProcessClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = polkit_unix_process_get_property;
  gobject_class->set_property = polkit_unix_process_set_property;
  gobject_class->constructed =  polkit_unix_process_constructed;
  gobject_class->finalize =     polkit_unix_process_finalize;

  /**
   * PolkitUnixProcess:pid:
   *
   * The UNIX process id.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_PID,
                                   g_param_spec_int ("pid",
                                                     "Process ID",
                                                     "The UNIX process ID",
                                                     0,
                                                     G_MAXINT,
                                                     0,
                                                     G_PARAM_CONSTRUCT |
                                                     G_PARAM_READWRITE |
                                                     G_PARAM_STATIC_NAME |
                                                     G_PARAM_STATIC_BLURB |
                                                     G_PARAM_STATIC_NICK));

  /**
   * PolkitUnixProcess:uid:
   *
   * The UNIX user id of the process or -1 if unknown.
   *
   * Note that this is the real user-id, not the effective user-id.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_UID,
                                   g_param_spec_int ("uid",
                                                     "User ID",
                                                     "The UNIX user ID",
                                                     G_MININT,
                                                     G_MAXINT,
                                                     -1,
                                                     G_PARAM_CONSTRUCT |
                                                     G_PARAM_READWRITE |
                                                     G_PARAM_STATIC_NAME |
                                                     G_PARAM_STATIC_BLURB |
                                                     G_PARAM_STATIC_NICK));

  /**
   * PolkitUnixProcess:start-time:
   *
   * The start time of the process.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_START_TIME,
                                   g_param_spec_uint64 ("start-time",
                                                        "Start Time",
                                                        "The start time of the process, since the machine booted",
                                                        0,
                                                        G_MAXUINT64,
                                                        0,
                                                        G_PARAM_CONSTRUCT |
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_STATIC_NAME |
                                                        G_PARAM_STATIC_BLURB |
                                                        G_PARAM_STATIC_NICK));

  /**
   * PolkitUnixProcess:pidfd:
   *
   * The UNIX process id file descriptor.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_PIDFD,
                                   g_param_spec_int ("pidfd",
                                                     "Process ID FD",
                                                     "The UNIX process ID file descriptor",
                                                     -1,
                                                     G_MAXINT,
                                                     -1,
                                                     G_PARAM_CONSTRUCT |
                                                     G_PARAM_READWRITE |
                                                     G_PARAM_STATIC_NAME |
                                                     G_PARAM_STATIC_BLURB |
                                                     G_PARAM_STATIC_NICK));

  /**
   * PolkitUnixProcess:pidfd_is_safe:
   *
   * Whether the UNIX process id file descriptor is safe end-to-end
   * or it was opened locally.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_PIDFD_IS_SAFE,
                                   g_param_spec_boolean ("pidfd-is-safe",
                                                         "Process ID FD",
                                                         "Whether the UNIX process ID file descriptor is safe",
                                                         FALSE,
                                                         G_PARAM_READABLE |
                                                         G_PARAM_STATIC_NAME |
                                                         G_PARAM_STATIC_BLURB |
                                                         G_PARAM_STATIC_NICK));

  /**
   * PolkitUnixProcess:gids:
   *
   * The UNIX group ids of the process.
   */
  g_object_class_install_property (gobject_class,
                                   PROP_GIDS,
                                   g_param_spec_boxed ("gids",
                                                       "Group IDs",
                                                       "The UNIX group IDs",
                                                       G_TYPE_ARRAY,
                                                       G_PARAM_CONSTRUCT |
                                                       G_PARAM_READWRITE |
                                                       G_PARAM_STATIC_NAME |
                                                       G_PARAM_STATIC_BLURB |
                                                       G_PARAM_STATIC_NICK));
}

/**
 * polkit_unix_process_get_uid:
 * @process: A #PolkitUnixProcess.
 *
 * Gets the user id for @process. Note that this is the real user-id,
 * not the effective user-id.
 *
 * NOTE: The UID may change over time, so the returned value may not match the
 * current state of the underlying process; or the UID may have been set by
 * polkit_unix_process_new_for_owner() or polkit_unix_process_set_uid(),
 * in which case it may not correspond to the actual UID of the referenced
 * process at all (at any point in time).
 *
 * Returns: The user id for @process or -1 if unknown.
 */
gint
polkit_unix_process_get_uid (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1);
  return process->uid;
}

/**
 * polkit_unix_process_set_uid:
 * @process: A #PolkitUnixProcess.
 * @uid: The user id to set for @process or -1 to unset it.
 *
 * Sets the (real, not effective) user id for @process.
 */
void
polkit_unix_process_set_uid (PolkitUnixProcess *process,
                             gint               uid)
{
  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
  process->uid = uid;
}

/**
 * polkit_unix_process_get_gids:
 * @process: A #PolkitUnixProcess.
 *
 * Gets the group ids for @process. Note that this is the real group-ids,
 * not the effective group-ids.
 *
 * Returns: (element-type GArray) (transfer full) (allow-none): a #GArray
 *          of #gid_t containing the group ids for @process or NULL if unknown,
 *          as a new reference to the array, caller must deref it when done.
 */
GArray *
polkit_unix_process_get_gids (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), NULL);
  return process->gids ? g_array_ref (process->gids) : NULL;
}

/**
 * polkit_unix_process_set_gids:
 * @process: A #PolkitUnixProcess.
 * @gids: (element-type GArray): A #GList of #gid_t containing the group
 *        ids to set for @process or NULL to unset them.
 *        A reference to @gids is taken.
 *
 * Sets the (real, not effective) group ids for @process.
 */
void
polkit_unix_process_set_gids (PolkitUnixProcess *process,
                              GArray            *gids)
{
  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
  if (process->gids)
    g_array_unref (g_steal_pointer (&process->gids));
  if (gids)
    process->gids = g_array_ref (gids);
}

/**
 * polkit_unix_process_get_pid:
 * @process: A #PolkitUnixProcess.
 *
 * Gets the process id for @process.
 *
 * Returns: The process id for @process.
 */
gint
polkit_unix_process_get_pid (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);

  if (process->pidfd >= 0)
    {
      GError *error = NULL;
      gint pid = polkit_unix_process_get_pid_from_pidfd(process, &error);

      if (pid > 0)
        return pid;

      g_error_free (error);
      return -1;
    }

  return process->pid;
}

/**
 * polkit_unix_process_get_start_time:
 * @process: A #PolkitUnixProcess.
 *
 * Gets the start time of @process.
 *
 * Returns: The start time of @process.
 */
guint64
polkit_unix_process_get_start_time (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);
  return process->start_time;
}

/**
 * polkit_unix_process_set_start_time:
 * @process: A #PolkitUnixProcess.
 * @start_time: The start time for @pid.
 *
 * Set the start time of @process.
 */
void
polkit_unix_process_set_start_time (PolkitUnixProcess *process,
                                    guint64            start_time)
{
  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
  process->start_time = start_time;
}

/**
 * polkit_unix_process_set_pid:
 * @process: A #PolkitUnixProcess.
 * @pid: A process id.
 *
 * Sets @pid for @process.
 */
void
polkit_unix_process_set_pid (PolkitUnixProcess *process,
                             gint              pid)
{
  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));

#ifdef HAVE_PIDFD_OPEN
  if (process->pidfd >= 0)
    {
      close (process->pidfd);
      process->pidfd = -1;
      process->pidfd_is_safe = FALSE;
    }
  if (pid > 0)
    {
      gint pidfd = (int) syscall (SYS_pidfd_open, process->pid, 0);
      if (pidfd >= 0)
        {
          process->pidfd_is_safe = FALSE;
          process->pidfd = pidfd;
          process->pid = 0;
          return;
        }
    }
#endif /* HAVE_PIDFD_OPEN */

  process->pid = pid;
}

/**
 * polkit_unix_process_get_pidfd:
 * @process: A #PolkitUnixProcess.
 *
 * Gets the process id file descriptor for @process.
 *
 * Returns: The process id file descriptor for @process.
 */
gint
polkit_unix_process_get_pidfd (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), -1);
  return process->pidfd;
}

/**
 * polkit_unix_process_get_pidfd_is_safe:
 * @process: A #PolkitUnixProcess.
 *
 * Checks if the process id file descriptor for @process is safe
 * or if it was opened locally and thus vulnerable to reuse.
 *
 * Returns: TRUE or FALSE.
 */
gboolean
polkit_unix_process_get_pidfd_is_safe (PolkitUnixProcess *process)
{
  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), FALSE);
  return process->pidfd_is_safe;
}

/**
 * polkit_unix_process_set_pidfd:
 * @process: A #PolkitUnixProcess.
 * @pidfd: A process id file descriptor.
 *
 * Sets @pidfd for @process.
 */
void
polkit_unix_process_set_pidfd (PolkitUnixProcess *process,
                               gint               pidfd)
{
  g_return_if_fail (POLKIT_IS_UNIX_PROCESS (process));
  if (process->pidfd >= 0)
    {
      close (process->pidfd);
      process->pidfd_is_safe = FALSE;
    }
  process->pidfd = pidfd;
}

/**
 * polkit_unix_process_new:
 * @pid: The process id.
 *
 * Creates a new #PolkitUnixProcess for @pid.
 *
 * The uid and start time of the process will be looked up in using
 * e.g. the <filename>/proc</filename> filesystem depending on the
 * platform in use.
 *
 * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
 */
PolkitSubject *
polkit_unix_process_new (gint pid)
{
  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
                                       "pid", pid,
                                       NULL));
}

/**
 * polkit_unix_process_new_full:
 * @pid: The process id.
 * @start_time: The start time for @pid.
 *
 * Creates a new #PolkitUnixProcess object for @pid and @start_time.
 *
 * The uid of the process will be looked up in using e.g. the
 * <filename>/proc</filename> filesystem depending on the platform in
 * use.
 *
 * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
 */
PolkitSubject *
polkit_unix_process_new_full (gint pid,
                              guint64 start_time)
{
  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
                                       "pid", pid,
                                       "start_time", start_time,
                                       NULL));
}

/**
 * polkit_unix_process_new_for_owner:
 * @pid: The process id.
 * @start_time: The start time for @pid or 0 to look it up in e.g. <filename>/proc</filename>.
 * @uid: The (real, not effective) uid of the owner of @pid or -1 to look it up in e.g. <filename>/proc</filename>.
 *
 * Creates a new #PolkitUnixProcess object for @pid, @start_time and @uid.
 *
 * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
 */
PolkitSubject *
polkit_unix_process_new_for_owner (gint    pid,
                                   guint64 start_time,
                                   gint    uid)
{
  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
                                       "pid", pid,
                                       "start_time", start_time,
                                       "uid", uid,
                                       NULL));
}

/**
 * polkit_unix_process_new_pidfd:
 * @pidfd: The process id file descriptor.
 * @uid: The (real, not effective) uid of the owner of @pid or -1 to look it up in e.g. <filename>/proc</filename>.
 * @gids: (element-type gint) (allow-none): The (real, not effective) gids of the owner of @pid or %NULL.
 *
 * Creates a new #PolkitUnixProcess object for @pidfd and @uid.
 *
 * Returns: (transfer full): A #PolkitSubject. Free with g_object_unref().
 */
PolkitSubject *
polkit_unix_process_new_pidfd (gint    pidfd,
                               gint    uid,
                               GArray *gids)
{
  return POLKIT_SUBJECT (g_object_new (POLKIT_TYPE_UNIX_PROCESS,
                                       "pidfd", pidfd,
                                       "uid", uid,
                                       "gids", gids,
                                       NULL));
}

static guint
polkit_unix_process_hash (PolkitSubject *subject)
{
  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (subject);

  return g_direct_hash (GSIZE_TO_POINTER ((polkit_unix_process_get_pid(process) + process->start_time))) ;
}

static gboolean
polkit_unix_process_equal (PolkitSubject *a,
                        PolkitSubject *b)
{
  PolkitUnixProcess *process_a;
  PolkitUnixProcess *process_b;
  gint pid_a, pid_b;
  gint pidfd_a, pidfd_b;

  process_a = POLKIT_UNIX_PROCESS (a);
  process_b = POLKIT_UNIX_PROCESS (b);

  pid_a = polkit_unix_process_get_pid(process_a);
  pid_b = polkit_unix_process_get_pid(process_b);

  pidfd_a = polkit_unix_process_get_pidfd(process_a);
  pidfd_b = polkit_unix_process_get_pidfd(process_b);

  return
    (pid_a > 0) &&
    (pid_b > 0) &&
    (pid_a == pid_b) &&
    ((pidfd_a >= 0 && pidfd_b >= 0) ||
     (process_a->start_time == process_b->start_time));
}

static gchar *
polkit_unix_process_to_string (PolkitSubject *subject)
{
  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (subject);
  gint pid = polkit_unix_process_get_pid(process);
  if (pid <= 0)
    return g_strdup_printf ("unix-process:unknown");

  return g_strdup_printf ("unix-process:%d:%" G_GUINT64_FORMAT, pid, process->start_time);
}

static gboolean
polkit_unix_process_exists_sync (PolkitSubject   *subject,
                                 GCancellable    *cancellable,
                                 GError         **error)
{
  PolkitUnixProcess *process = POLKIT_UNIX_PROCESS (subject);
  GError *local_error;
  guint64 start_time;
  gboolean ret;
  gint pid;

  ret = TRUE;

  pid = polkit_unix_process_get_pid(process);
  if (pid <= 0)
    return FALSE;

  /* If we have both a valid PID and a PID FD then we know the process is still the
   * same and it hasn't exited. */
  if (polkit_unix_process_get_pidfd(process) >= 0)
    return TRUE;

  local_error = NULL;
  start_time = get_start_time_for_pid (pid, &local_error);
  if (local_error != NULL)
    {
      /* Don't propagate the error - it just means there is no process with this pid */
      g_error_free (local_error);
      ret = FALSE;
    }
  else
    {
      if (start_time != process->start_time)
        {
          ret = FALSE;
        }
    }

  return ret;
}

static void
polkit_unix_process_exists (PolkitSubject       *subject,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
  GSimpleAsyncResult *simple;
  simple = g_simple_async_result_new (G_OBJECT (subject),
                                      callback,
                                      user_data,
                                      polkit_unix_process_exists);
  g_simple_async_result_complete (simple);
  g_object_unref (simple);
}

static gboolean
polkit_unix_process_exists_finish (PolkitSubject  *subject,
                                   GAsyncResult   *res,
                                   GError        **error)
{
  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);

  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_unix_process_exists);

  return polkit_unix_process_exists_sync (subject,
                                          NULL,
                                          error);
}


static void
subject_iface_init (PolkitSubjectIface *subject_iface)
{
  subject_iface->hash          = polkit_unix_process_hash;
  subject_iface->equal         = polkit_unix_process_equal;
  subject_iface->to_string     = polkit_unix_process_to_string;
  subject_iface->exists        = polkit_unix_process_exists;
  subject_iface->exists_finish = polkit_unix_process_exists_finish;
  subject_iface->exists_sync   = polkit_unix_process_exists_sync;
}

#ifdef HAVE_SOLARIS
static int
get_pid_psinfo (pid_t pid, struct psinfo *ps)
{
  char pname[32];
  int  procfd;

  (void) snprintf(pname, sizeof(pname), "/proc/%d/psinfo", pid);
  if ((procfd = open(pname, O_RDONLY)) == -1)
    {
      return -1;
    }
  if (read(procfd, ps, sizeof(struct psinfo)) < 0)
    {
      (void) close(procfd);
      return -1;
    }
  (void) close(procfd);
  return 0;
}
#endif

#ifdef HAVE_FREEBSD
static gboolean
get_kinfo_proc (pid_t pid, struct kinfo_proc *p)
{
  int mib[4];
  size_t len;

  len = 4;
  sysctlnametomib ("kern.proc.pid", mib, &len);

  len = sizeof (struct kinfo_proc);
  mib[3] = pid;

  if (sysctl (mib, 4, p, &len, NULL, 0) == -1)
    return FALSE;

  return TRUE;
}
#endif

#if defined(HAVE_NETBSD) || defined(HAVE_OPENBSD)
static gboolean
get_kinfo_proc (gint pid,
#ifdef HAVE_NETBSD
                struct kinfo_proc2 *p)
#else
                struct kinfo_proc *p)
#endif
{
  int name[6];
  u_int namelen;
  size_t sz;

  sz = sizeof(*p);
  namelen = 0;
  name[namelen++] = CTL_KERN;
#ifdef HAVE_NETBSD
  name[namelen++] = KERN_PROC2;
#else
  name[namelen++] = KERN_PROC;
#endif
  name[namelen++] = KERN_PROC_PID;
  name[namelen++] = pid;
  name[namelen++] = sz;
  name[namelen++] = 1;

  if (sysctl (name, namelen, p, &sz, NULL, 0) == -1)
    return FALSE;

  return TRUE;
}
#endif

static guint64
get_start_time_for_pid (pid_t    pid,
                        GError **error)
{
  guint64 start_time;
#if !defined(HAVE_FREEBSD) && !defined(HAVE_NETBSD) && !defined(HAVE_OPENBSD)
  gchar *filename;
  gchar *contents;
  size_t length;
  gchar **tokens;
  guint num_tokens;
  gchar *p;
  gchar *endp;

  start_time = 0;
  contents = NULL;

  filename = g_strdup_printf ("/proc/%d/stat", pid);

  if (!g_file_get_contents (filename, &contents, &length, error))
    goto out;

  /* start time is the token at index 19 after the '(process name)' entry - since only this
   * field can contain the ')' character, search backwards for this to avoid malicious
   * processes trying to fool us
   */
  p = strrchr (contents, ')');
  if (p == NULL)
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Error parsing file %s",
                   filename);
      goto out;
    }
  p += 2; /* skip ') ' */
  if (p - contents >= (int) length)
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Error parsing file %s",
                   filename);
      goto out;
    }

  tokens = g_strsplit (p, " ", 0);

  num_tokens = g_strv_length (tokens);

  if (num_tokens < 20)
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Error parsing file %s",
                   filename);
      goto out;
    }

  start_time = strtoull (tokens[19], &endp, 10);
  if (endp == tokens[19])
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Error parsing file %s",
                   filename);
      goto out;
    }

  g_strfreev (tokens);

 out:
  g_free (filename);
  g_free (contents);
#else
#ifdef HAVE_NETBSD
  struct kinfo_proc2 p;
#else
  struct kinfo_proc p;
#endif

  start_time = 0;

  if (! get_kinfo_proc (pid, &p))
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Error obtaining start time for %d (%s)",
                   (gint) pid,
                   g_strerror (errno));
      goto out;
    }

#ifdef HAVE_FREEBSD
  start_time = (guint64) p.ki_start.tv_sec;
#else
  start_time = (guint64) p.p_ustart_sec;
#endif

out:
#endif

  return start_time;
}

/*
 * Private: Return the "current" UID.  Note that this is inherently racy,
 * and the value may already be obsolete by the time this function returns;
 * this function only guarantees that the UID was valid at some point during
 * its execution.
 */
gint
polkit_unix_process_get_racy_uid__ (PolkitUnixProcess  *process,
                                    GError            **error)
{
  gint result, pid;
  gchar *contents;
  gchar **lines;
  guint64 start_time;
#if defined(HAVE_FREEBSD) || defined(HAVE_OPENBSD)
  struct kinfo_proc p;
#elif defined(HAVE_NETBSD)
  struct kinfo_proc2 p;
#else
  gchar filename[64];
  guint n;
  GError *local_error;
#endif

  g_return_val_if_fail (POLKIT_IS_UNIX_PROCESS (process), 0);
  g_return_val_if_fail (error == NULL || *error == NULL, 0);

  result = 0;
  lines = NULL;
  contents = NULL;

  pid = polkit_unix_process_get_pid(process);
  if (pid <= 0)
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "Process not found");
      goto out;
    }

#if defined(HAVE_FREEBSD) || defined(HAVE_NETBSD) || defined(HAVE_OPENBSD)
  if (get_kinfo_proc (pid, &p) == 0)
    {
      g_set_error (error,
                   POLKIT_ERROR,
                   POLKIT_ERROR_FAILED,
                   "get_kinfo_proc() failed for pid %d: %s",
                   pid,
                   g_strerror (errno));
      goto out;
    }

#if defined(HAVE_FREEBSD)
  result = p.ki_uid;
  start_time = (guint64) p.ki_start.tv_sec;
#else
  result = p.p_uid;
  start_time = (guint64) p.p_ustart_sec;
#endif
#else

  /* see 'man proc' for layout of the status file
   *
   * Uid, Gid: Real, effective, saved set,  and  file  system  UIDs (GIDs).
   */
  g_snprintf (filename, sizeof filename, "/proc/%d/status", pid);
  if (!g_file_get_contents (filename,
                            &contents,
                            NULL,
                            error))
    {
      goto out;
    }
  lines = g_strsplit (contents, "\n", -1);
  for (n = 0; lines != NULL && lines[n] != NULL; n++)
    {
      gint real_uid, effective_uid;
      if (!g_str_has_prefix (lines[n], "Uid:"))
        continue;
      if (sscanf (lines[n] + 4, "%d %d", &real_uid, &effective_uid) != 2)
        {
          g_set_error (error,
                       POLKIT_ERROR,
                       POLKIT_ERROR_FAILED,
                       "Unexpected line `%s' in file %s",
                       lines[n],
                       filename);
          goto out;
        }
      else
        {
          result = real_uid;
          goto found;
        }
    }
  g_set_error (error,
               POLKIT_ERROR,
               POLKIT_ERROR_FAILED,
               "Didn't find any line starting with `Uid:' in file %s",
               filename);
  goto out;

found:
  /* The UID and start time are, sadly, not available in a single file.  So,
   * read the UID first, and then the start time; if the start time is the same
   * before and after reading the UID, it couldn't have changed.
   */
  local_error = NULL;
  start_time = get_start_time_for_pid (pid, &local_error);
  if (local_error != NULL)
    {
      g_propagate_error (error, local_error);
      goto out;
    }
#endif

  if (process->start_time != start_time)
    {
      g_set_error (error, POLKIT_ERROR, POLKIT_ERROR_FAILED,
		   "process with PID %d has been replaced", pid);
      goto out;
    }

out:
  g_strfreev (lines);
  g_free (contents);
  return result;
}

/* deprecated public method */
/**
 * polkit_unix_process_get_owner:
 * @process: A #PolkitUnixProcess.
 * @error: Return location for error.
 *
 * (deprecated)
 */
gint
polkit_unix_process_get_owner (PolkitUnixProcess  *process,
                               GError            **error)
{
  return polkit_unix_process_get_racy_uid__ (process, error);
}
