nm-audit-manager.c 12 KB
Newer Older
Beniamino Galvani's avatar
Beniamino Galvani committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager audit support
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright 2015 Red Hat, Inc.
 */

21
#include "nm-default.h"
22

23 24
#include "nm-audit-manager.h"

Beniamino Galvani's avatar
Beniamino Galvani committed
25 26 27 28 29 30
#if HAVE_LIBAUDIT
#include <libaudit.h>
#endif

#include "nm-auth-subject.h"
#include "nm-config.h"
31
#include "settings/nm-settings-connection.h"
Beniamino Galvani's avatar
Beniamino Galvani committed
32

33
/*****************************************************************************/
Beniamino Galvani's avatar
Beniamino Galvani committed
34

Beniamino Galvani's avatar
Beniamino Galvani committed
35
typedef enum {
36 37 38 39
	BACKEND_LOG    = (1 << 0),
	BACKEND_AUDITD = (1 << 1),
	_BACKEND_LAST,
	BACKEND_ALL    = ((_BACKEND_LAST - 1) << 1) - 1,
Beniamino Galvani's avatar
Beniamino Galvani committed
40 41 42
} AuditBackend;

typedef struct {
43 44 45 46
	const char *name;
	GValue value;
	gboolean need_encoding;
	AuditBackend backends;
Beniamino Galvani's avatar
Beniamino Galvani committed
47 48
} AuditField;

49 50
/*****************************************************************************/

51
typedef struct {
Beniamino Galvani's avatar
Beniamino Galvani committed
52 53 54 55
	NMConfig *config;
	int auditd_fd;
} NMAuditManagerPrivate;

56 57 58 59
struct _NMAuditManager {
	GObject parent;
#if HAVE_LIBAUDIT
	NMAuditManagerPrivate _priv;
60
#endif
61 62 63 64 65
};

struct _NMAuditManagerClass {
	GObjectClass parent;
};
Beniamino Galvani's avatar
Beniamino Galvani committed
66 67 68

G_DEFINE_TYPE (NMAuditManager, nm_audit_manager, G_TYPE_OBJECT)

69 70 71 72 73 74 75 76 77
#define NM_AUDIT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMAuditManager, NM_IS_AUDIT_MANAGER)

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

#define AUDIT_LOG_LEVEL LOGL_INFO

#define _NMLOG_PREFIX_NAME    "audit"
#define _NMLOG(level, domain, ...) \
    G_STMT_START { \
78
        nm_log ((level), (domain), NULL, NULL, \
79 80 81 82 83 84 85
                "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                _NMLOG_PREFIX_NAME": " \
                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
    } G_STMT_END

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

Beniamino Galvani's avatar
Beniamino Galvani committed
86 87
NM_DEFINE_SINGLETON_GETTER (NMAuditManager, nm_audit_manager_get, NM_TYPE_AUDIT_MANAGER);

88 89
/*****************************************************************************/

Beniamino Galvani's avatar
Beniamino Galvani committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
static void
_audit_field_init_string (AuditField *field, const char *name, const char *str,
                          gboolean need_encoding, AuditBackend backends)
{
	field->name = name;
	field->need_encoding = need_encoding;
	field->backends = backends;
	g_value_init (&field->value, G_TYPE_STRING);
	g_value_set_static_string (&field->value, str);
}

static void
_audit_field_init_uint (AuditField *field, const char *name, uint val,
                        AuditBackend backends)
{
	field->name = name;
	field->backends = backends;
	g_value_init (&field->value, G_TYPE_UINT);
	g_value_set_uint (&field->value, val);
}

static char *
build_message (GPtrArray *fields, AuditBackend backend)
{
	GString *string;
	AuditField *field;
	gboolean first = TRUE;
	guint i;

	string = g_string_new (NULL);

	for (i = 0; i < fields->len; i++) {
		field = fields->pdata[i];

124
		if (!NM_FLAGS_ANY (field->backends, backend))
Beniamino Galvani's avatar
Beniamino Galvani committed
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
			continue;

		if (first)
			first = FALSE;
		else
			g_string_append_c (string, ' ');

		if (G_VALUE_HOLDS_STRING (&field->value)) {
			const char *str = g_value_get_string (&field->value);

#if HAVE_LIBAUDIT
			if (backend == BACKEND_AUDITD) {
				if (field->need_encoding) {
					char *value;

					value = audit_encode_nv_string (field->name, str, 0);
					g_string_append (string, value);
					g_free (value);
				} else
					g_string_append_printf (string, "%s=%s", field->name, str);
				continue;
			}
#endif /* HAVE_LIBAUDIT */
			g_string_append_printf (string, "%s=\"%s\"", field->name, str);
		} else if (G_VALUE_HOLDS_UINT (&field->value)) {
			g_string_append_printf (string, "%s=%u", field->name,
			                        g_value_get_uint (&field->value));
		} else
			g_assert_not_reached ();
	}
	return g_string_free (string, FALSE);
}

static void
nm_audit_log (NMAuditManager *self, GPtrArray *fields, const char *file,
              guint line, const char *func, gboolean success)
{
162
#if HAVE_LIBAUDIT
Beniamino Galvani's avatar
Beniamino Galvani committed
163
	NMAuditManagerPrivate *priv;
164
#endif
Beniamino Galvani's avatar
Beniamino Galvani committed
165 166 167 168 169
	char *msg;

	g_return_if_fail (NM_IS_AUDIT_MANAGER (self));

#if HAVE_LIBAUDIT
170 171
	priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);

Beniamino Galvani's avatar
Beniamino Galvani committed
172 173 174 175 176 177 178 179 180 181
	if (priv->auditd_fd >= 0) {
		msg = build_message (fields, BACKEND_AUDITD);
		audit_log_user_message (priv->auditd_fd, AUDIT_USYS_CONFIG, msg,
		                        NULL, NULL, NULL, success);
		g_free (msg);
	}
#endif

	if (nm_logging_enabled (AUDIT_LOG_LEVEL, LOGD_AUDIT)) {
		msg = build_message (fields, BACKEND_LOG);
Beniamino Galvani's avatar
Beniamino Galvani committed
182
		_NMLOG (AUDIT_LOG_LEVEL, LOGD_AUDIT, "%s", msg);
Beniamino Galvani's avatar
Beniamino Galvani committed
183 184 185 186 187 188 189
		g_free (msg);
	}
}

static void
_audit_log_helper (NMAuditManager *self, GPtrArray *fields, const char *file,
                   guint line, const char *func, const char *op, gboolean result,
190
                   gpointer subject_context, const char *reason)
Beniamino Galvani's avatar
Beniamino Galvani committed
191 192 193 194
{
	AuditField op_field = { }, pid_field = { }, uid_field = { };
	AuditField result_field = { }, reason_field = { };
	gulong pid, uid;
195 196
	NMAuthSubject *subject = NULL;
	gs_unref_object NMAuthSubject *subject_free = NULL;
Beniamino Galvani's avatar
Beniamino Galvani committed
197 198 199 200

	_audit_field_init_string (&op_field, "op", op, FALSE, BACKEND_ALL);
	g_ptr_array_insert (fields, 0, &op_field);

201 202 203 204 205 206 207 208 209 210
	if (subject_context) {
		if (NM_IS_AUTH_SUBJECT (subject_context))
			subject = subject_context;
		else if (G_IS_DBUS_METHOD_INVOCATION (subject_context)) {
			GDBusMethodInvocation *context = subject_context;

			subject = subject_free = nm_auth_subject_new_unix_process_from_context (context);
		} else
			g_warn_if_reached ();
	}
Beniamino Galvani's avatar
Beniamino Galvani committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
	if (subject && nm_auth_subject_is_unix_process (subject)) {
		pid = nm_auth_subject_get_unix_process_pid (subject);
		uid = nm_auth_subject_get_unix_process_uid (subject);
		if (pid != G_MAXULONG) {
			_audit_field_init_uint (&pid_field, "pid", pid, BACKEND_ALL);
			g_ptr_array_add (fields, &pid_field);
		}
		if (uid != G_MAXULONG) {
			_audit_field_init_uint (&uid_field, "uid", uid, BACKEND_ALL);
			g_ptr_array_add (fields, &uid_field);
		}
	}

	_audit_field_init_string (&result_field, "result", result ? "success" : "fail",
	                          FALSE, BACKEND_ALL);
	g_ptr_array_add (fields, &result_field);

	if (reason) {
		_audit_field_init_string (&reason_field, "reason", reason, FALSE, BACKEND_LOG);
		g_ptr_array_add (fields, &reason_field);
	}

	nm_audit_log (self, fields, file, line, func, result);
}

gboolean
nm_audit_manager_audit_enabled (NMAuditManager *self)
{
#if HAVE_LIBAUDIT
	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);

	if (priv->auditd_fd >= 0)
		return TRUE;
#endif

	return nm_logging_enabled (AUDIT_LOG_LEVEL, LOGD_AUDIT);
}

void
_nm_audit_manager_log_connection_op (NMAuditManager *self, const char *file, guint line,
251
                                     const char *func, const char *op, NMSettingsConnection *connection,
252
                                     gboolean result, const char *args, gpointer subject_context, const char *reason)
Beniamino Galvani's avatar
Beniamino Galvani committed
253 254
{
	gs_unref_ptrarray GPtrArray *fields = NULL;
255
	AuditField uuid_field = { }, name_field = { }, args_field = { };
Beniamino Galvani's avatar
Beniamino Galvani committed
256 257 258 259 260 261

	g_return_if_fail (op);

	fields = g_ptr_array_new ();

	if (connection) {
262
		_audit_field_init_string (&uuid_field, "uuid", nm_settings_connection_get_uuid (connection),
Beniamino Galvani's avatar
Beniamino Galvani committed
263 264 265
		                          FALSE, BACKEND_ALL);
		g_ptr_array_add (fields, &uuid_field);

266
		_audit_field_init_string (&name_field, "name", nm_settings_connection_get_id (connection),
Beniamino Galvani's avatar
Beniamino Galvani committed
267 268 269 270
		                          TRUE, BACKEND_ALL);
		g_ptr_array_add (fields, &name_field);
	}

271 272 273 274 275
	if (args) {
		_audit_field_init_string (&args_field, "args", args, FALSE, BACKEND_ALL);
		g_ptr_array_add (fields, &args_field);
	}

276
	_audit_log_helper (self, fields, file, line, func, op, result, subject_context, reason);
Beniamino Galvani's avatar
Beniamino Galvani committed
277 278 279
}

void
280
_nm_audit_manager_log_generic_op (NMAuditManager *self, const char *file, guint line,
Beniamino Galvani's avatar
Beniamino Galvani committed
281
                                  const char *func, const char *op, const char *arg,
282
                                  gboolean result, gpointer subject_context,
Beniamino Galvani's avatar
Beniamino Galvani committed
283 284 285 286 287 288 289 290 291 292 293 294 295
                                  const char *reason)
{
	gs_unref_ptrarray GPtrArray *fields = NULL;
	AuditField arg_field = { };

	g_return_if_fail (op);
	g_return_if_fail (arg);

	fields = g_ptr_array_new ();

	_audit_field_init_string (&arg_field, "arg", arg, TRUE, BACKEND_ALL);
	g_ptr_array_add (fields, &arg_field);

296
	_audit_log_helper (self, fields, file, line, func, op, result, subject_context, reason);
Beniamino Galvani's avatar
Beniamino Galvani committed
297 298 299 300 301
}

void
_nm_audit_manager_log_device_op (NMAuditManager *self, const char *file, guint line,
                                 const char *func, const char *op, NMDevice *device,
302
                                 gboolean result, const char *args, gpointer subject_context,
Beniamino Galvani's avatar
Beniamino Galvani committed
303 304 305
                                 const char *reason)
{
	gs_unref_ptrarray GPtrArray *fields = NULL;
306
	AuditField interface_field = { }, ifindex_field = { }, args_field = { };
Beniamino Galvani's avatar
Beniamino Galvani committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	int ifindex;

	g_return_if_fail (op);
	g_return_if_fail (device);

	fields = g_ptr_array_new ();

	_audit_field_init_string (&interface_field, "interface", nm_device_get_ip_iface (device),
	                          TRUE, BACKEND_ALL);
	g_ptr_array_add (fields, &interface_field);

	ifindex = nm_device_get_ip_ifindex (device);
	if (ifindex > 0) {
		_audit_field_init_uint (&ifindex_field, "ifindex", ifindex, BACKEND_ALL);
		g_ptr_array_add (fields, &ifindex_field);
	}

324 325 326 327 328
	if (args) {
		_audit_field_init_string (&args_field, "args", args, FALSE, BACKEND_ALL);
		g_ptr_array_add (fields, &args_field);
	}

329
	_audit_log_helper (self, fields, file, line, func, op, result, subject_context, reason);
Beniamino Galvani's avatar
Beniamino Galvani committed
330 331 332 333 334 335 336 337
}

#if HAVE_LIBAUDIT
static void
init_auditd (NMAuditManager *self)
{
	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);
	NMConfigData *data = nm_config_get_data (priv->config);
338
	int errsv;
Beniamino Galvani's avatar
Beniamino Galvani committed
339 340

	if (nm_config_data_get_value_boolean (data, NM_CONFIG_KEYFILE_GROUP_LOGGING,
341
	                                      NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT,
342
	                                      NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL)) {
Beniamino Galvani's avatar
Beniamino Galvani committed
343 344
		if (priv->auditd_fd < 0) {
			priv->auditd_fd = audit_open ();
345 346 347 348
			if (priv->auditd_fd < 0) {
				errsv = errno;
				_LOGE (LOGD_CORE, "failed to open auditd socket: %s", strerror (errsv));
			} else
Beniamino Galvani's avatar
Beniamino Galvani committed
349
				_LOGD (LOGD_CORE, "socket created");
Beniamino Galvani's avatar
Beniamino Galvani committed
350 351 352 353 354
		}
	} else {
		if (priv->auditd_fd >= 0) {
			audit_close (priv->auditd_fd);
			priv->auditd_fd = -1;
Beniamino Galvani's avatar
Beniamino Galvani committed
355
			_LOGD (LOGD_CORE, "socket closed");
Beniamino Galvani's avatar
Beniamino Galvani committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
		}
	}
}

static void
config_changed_cb (NMConfig *config,
                   NMConfigData *config_data,
                   NMConfigChangeFlags changes,
                   NMConfigData *old_data,
                   NMAuditManager *self)
{
	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_VALUES))
		init_auditd (self);
}
#endif

372 373
/*****************************************************************************/

Beniamino Galvani's avatar
Beniamino Galvani committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
static void
nm_audit_manager_init (NMAuditManager *self)
{
#if HAVE_LIBAUDIT
	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);

	priv->config = g_object_ref (nm_config_get ());
	g_signal_connect (G_OBJECT (priv->config),
	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
	                  G_CALLBACK (config_changed_cb),
	                  self);
	priv->auditd_fd = -1;

	init_auditd (self);
#endif
}

static void
dispose (GObject *object)
{
#if HAVE_LIBAUDIT
	NMAuditManager *self = NM_AUDIT_MANAGER (object);
	NMAuditManagerPrivate *priv = NM_AUDIT_MANAGER_GET_PRIVATE (self);

	if (priv->config) {
		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
		g_clear_object (&priv->config);
	}

	 if (priv->auditd_fd >= 0) {
		audit_close (priv->auditd_fd);
		priv->auditd_fd = -1;
	}
#endif

	G_OBJECT_CLASS (nm_audit_manager_parent_class)->dispose (object);
}

static void
nm_audit_manager_class_init (NMAuditManagerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->dispose = dispose;
}