// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2013 - 2014 Red Hat, Inc.
 */

/**
 * SECTION:nm-auth-subject
 * @short_description: Encapsulates authentication information about a requestor
 *
 * #NMAuthSubject encpasulates identifying information about an entity that
 * makes requests, like process identifier and user UID.
 */

#include "nm-default.h"

#include "nm-auth-subject.h"

#include <stdlib.h>

enum {
	PROP_0,
	PROP_SUBJECT_TYPE,
	PROP_UNIX_PROCESS_DBUS_SENDER,
	PROP_UNIX_PROCESS_PID,
	PROP_UNIX_PROCESS_UID,
	PROP_UNIX_SESSION_ID,

	PROP_LAST,
};

typedef struct {
	NMAuthSubjectType subject_type;
	struct {
		gulong pid;
		gulong uid;
		guint64 start_time;
		char *dbus_sender;
	} unix_process;

	struct {
		char *id;
	} unix_session;
} NMAuthSubjectPrivate;

struct _NMAuthSubject {
	GObject parent;
	NMAuthSubjectPrivate _priv;
};

struct _NMAuthSubjectClass {
	GObjectClass parent;
};

G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT)

#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT)

/*****************************************************************************/

#define CHECK_SUBJECT(self, error_value) \
	NMAuthSubjectPrivate *priv; \
	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \
	priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \

#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \
	CHECK_SUBJECT (self, error_value); \
	g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value);

const char *
nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len)
{
	CHECK_SUBJECT (self, NULL);

	switch (priv->subject_type) {
	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
		g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]",
		            (unsigned long) priv->unix_process.pid,
		            (unsigned long) priv->unix_process.uid,
		            (unsigned long long) priv->unix_process.start_time);
		break;
	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
		g_strlcpy (buf, "internal", buf_len);
		break;
	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
		g_snprintf (buf, buf_len, "unix-session[id=%s]",
		            priv->unix_session.id);
		break;
	default:
		g_strlcpy (buf, "invalid", buf_len);
		break;
	}
	return buf;
}

/* returns a floating variant */
GVariant *
nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self)
{
	GVariantBuilder builder;
	CHECK_SUBJECT (self, NULL);

	switch (priv->subject_type) {

	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
		g_variant_builder_add (&builder, "{sv}", "session-id",
		                       g_variant_new_string (priv->unix_session.id));
		return g_variant_new ("(sa{sv})", "unix-session", &builder);

	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
		g_variant_builder_add (&builder, "{sv}", "pid",
		                       g_variant_new_uint32 (priv->unix_process.pid));
		g_variant_builder_add (&builder, "{sv}", "start-time",
		                       g_variant_new_uint64 (priv->unix_process.start_time));
		g_variant_builder_add (&builder, "{sv}", "uid",
		                       g_variant_new_int32 (priv->unix_process.uid));
		return g_variant_new ("(sa{sv})", "unix-process", &builder);

	default:
		g_return_val_if_reached (NULL);
	}
}

NMAuthSubjectType
nm_auth_subject_get_subject_type (NMAuthSubject *subject)
{
	CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID);

	return priv->subject_type;
}

gulong
nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject)
{
	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);

	return priv->unix_process.pid;
}

gulong
nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject)
{
	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);

	return priv->unix_process.uid;
}

const char *
nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject)
{
	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);

	return priv->unix_process.dbus_sender;
}

const char *
nm_auth_subject_get_unix_session_id (NMAuthSubject *subject)
{
	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, NULL);

	return priv->unix_session.id;
}

/*****************************************************************************/

/**
 * nm_auth_subject_new_internal():
 *
 * Creates a new auth subject representing the NetworkManager process itself.
 *
 * Returns: the new #NMAuthSubject
 */
NMAuthSubject *
nm_auth_subject_new_internal (void)
{
	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
                                          NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
                                          NULL));
}

/**
 * nm_auth_subject_new_unix_session():
 *
 * Creates a new auth subject representing a given unix session.
 *
 * Returns: the new #NMAuthSubject
 */
NMAuthSubject *
nm_auth_subject_new_unix_session (const char *session_id)
{
	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
	                                      NM_AUTH_SUBJECT_UNIX_SESSION_ID, session_id,
	                                      NULL));
}

/**
 * nm_auth_subject_new_unix_process():
 *
 * Creates a new auth subject representing a given unix process.
 *
 * Returns: the new #NMAuthSubject
 */
NMAuthSubject *
nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid)
{
	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender,
	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_PID, pid,
	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_UID, uid,
	                                      NULL));
}

/**
 * nm_auth_subject_new_unix_process_self():
 *
 * Creates a new auth subject representing the current executing process.
 *
 * Returns: the new #NMAuthSubject
 */
NMAuthSubject *
nm_auth_subject_new_unix_process_self (void)
{
	return nm_auth_subject_new_unix_process (NULL, getpid(), getuid());
}

/*****************************************************************************/

static void
get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);

	switch (prop_id) {
	case PROP_SUBJECT_TYPE:
		g_value_set_int (value, priv->subject_type);
		break;
	case PROP_UNIX_PROCESS_DBUS_SENDER:
		g_value_set_string (value, priv->unix_process.dbus_sender);
		break;
	case PROP_UNIX_PROCESS_PID:
		g_value_set_ulong (value, priv->unix_process.pid);
		break;
	case PROP_UNIX_PROCESS_UID:
		g_value_set_ulong (value, priv->unix_process.uid);
		break;
	case PROP_UNIX_SESSION_ID:
		g_value_set_string (value, priv->unix_session.id);
		break;
	default:
		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		 break;
	}
}

static void
set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);
	NMAuthSubjectType subject_type;
	int i;
	const char *str;
	gulong id;

	switch (prop_id) {
	case PROP_SUBJECT_TYPE:
		/* construct-only */
		i = g_value_get_int (value);
		g_return_if_fail (NM_IN_SET (i,
		                             (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION));
		subject_type = i;
		priv->subject_type |= subject_type;
		g_return_if_fail (priv->subject_type == subject_type);
		break;
	case PROP_UNIX_PROCESS_DBUS_SENDER:
		/* construct-only */
		if ((str = g_value_get_string (value))) {
			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
			priv->unix_process.dbus_sender = g_strdup (str);
		}
		break;
	case PROP_UNIX_PROCESS_PID:
		/* construct-only */
		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
			priv->unix_process.pid = id;
		}
		break;
	case PROP_UNIX_PROCESS_UID:
		/* construct-only */
		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
			priv->unix_process.uid = id;
		}
		break;
	case PROP_UNIX_SESSION_ID:
		/* construct-only */
		if ((str = g_value_get_string (value))) {
			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_SESSION;
			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_SESSION);
			priv->unix_session.id = g_strdup (str);
		}
		break;
	default:
		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		 break;
	}
}

static void
_clear_private (NMAuthSubject *self)
{
	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);

	priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID;
	priv->unix_process.pid = G_MAXULONG;
	priv->unix_process.uid = G_MAXULONG;
	nm_clear_g_free (&priv->unix_process.dbus_sender);

	nm_clear_g_free (&priv->unix_session.id);
}

static void
nm_auth_subject_init (NMAuthSubject *self)
{
	_clear_private (self);
}

static void
constructed (GObject *object)
{
	NMAuthSubject *self = NM_AUTH_SUBJECT (object);
	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);

	/* validate that the created instance. */

	switch (priv->subject_type) {
	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
		priv->unix_process.pid = G_MAXULONG;
		priv->unix_process.uid = 0;  /* internal uses 'root' user */
		return;
	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
		/* Ensure pid and uid to be representable as int32.
		 * DBUS treats them as uint32, polkit library as int. */
		if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32))
			break;
		if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) {
			/* for uid==-1, libpolkit-gobject-1 detects the user based on the process id.
			 * Don't bother and require the user id as parameter. */
			break;
		}

		priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL);

		if (!priv->unix_process.start_time) {
			/* Is the process already gone? Then fail creation of the auth subject
			 * by clearing the type. */
			if (kill (priv->unix_process.pid, 0) != 0)
				_clear_private (self);

			/* Otherwise, although we didn't detect a start_time, the process is still around.
			 * That could be due to procfs mounted with hidepid. So just accept the request.
			 *
			 * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat
			 * itself (and if it fails to do so, assume a start-time of 0 and proceed).
			 * The only combination that would fail here, is when NM is able to read the
			 * start-time, but polkit is not. */
		}
		return;
	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
		return;
	default:
		break;
	}

	_clear_private (self);
	g_return_if_reached ();
}

static void
finalize (GObject *object)
{
	_clear_private ((NMAuthSubject *) object);

	G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object);
}

static void
nm_auth_subject_class_init (NMAuthSubjectClass *config_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (config_class);

	object_class->get_property = get_property;
	object_class->set_property = set_property;
	object_class->constructed = constructed;
	object_class->finalize = finalize;

	g_object_class_install_property
	    (object_class, PROP_SUBJECT_TYPE,
	     g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "",
	                       NM_AUTH_SUBJECT_TYPE_INVALID,
	                       NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
	                       NM_AUTH_SUBJECT_TYPE_INVALID,
	                       G_PARAM_READWRITE |
	                       G_PARAM_CONSTRUCT_ONLY |
	                       G_PARAM_STATIC_STRINGS));

	g_object_class_install_property
	    (object_class, PROP_UNIX_PROCESS_DBUS_SENDER,
	     g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "",
	                          NULL,
	                          G_PARAM_READWRITE |
	                          G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS));

	g_object_class_install_property
	     (object_class, PROP_UNIX_PROCESS_PID,
	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "",
	                          0, G_MAXULONG, G_MAXULONG,
	                          G_PARAM_READWRITE |
	                          G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS));

	g_object_class_install_property
	     (object_class, PROP_UNIX_PROCESS_UID,
	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "",
	                          0, G_MAXULONG, G_MAXULONG,
	                          G_PARAM_READWRITE |
	                          G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS));

	g_object_class_install_property
	     (object_class, PROP_UNIX_SESSION_ID,
	      g_param_spec_string (NM_AUTH_SUBJECT_UNIX_SESSION_ID, "", "",
	                           NULL,
	                           G_PARAM_READWRITE |
	                           G_PARAM_CONSTRUCT_ONLY |
	                           G_PARAM_STATIC_STRINGS));
}
