/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2015 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-audit-manager.h" #if HAVE_LIBAUDIT #include #endif #define NM_VALUE_TYPE_DEFINE_FUNCTIONS #include "libnm-core-aux-intern/nm-auth-subject.h" #include "libnm-glib-aux/nm-str-buf.h" #include "libnm-glib-aux/nm-value-type.h" #include "nm-config.h" #include "nm-dbus-manager.h" #include "settings/nm-settings-connection.h" /*****************************************************************************/ typedef enum _nm_packed { BACKEND_LOG = (1 << 0), BACKEND_AUDITD = (1 << 1), _BACKEND_LAST, BACKEND_ALL = ((_BACKEND_LAST - 1) << 1) - 1, } AuditBackend; typedef struct { const char * name; AuditBackend backends; bool need_encoding; NMValueType value_type; NMValueTypUnion value; } AuditField; /*****************************************************************************/ typedef struct { NMConfig *config; int auditd_fd; } NMAuditManagerPrivate; struct _NMAuditManager { GObject parent; #if HAVE_LIBAUDIT NMAuditManagerPrivate _priv; #endif }; struct _NMAuditManagerClass { GObjectClass parent; }; G_DEFINE_TYPE(NMAuditManager, nm_audit_manager, G_TYPE_OBJECT) #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 \ { \ nm_log((level), \ (domain), \ NULL, \ NULL, \ "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ G_STMT_END /*****************************************************************************/ NM_DEFINE_SINGLETON_GETTER(NMAuditManager, nm_audit_manager_get, NM_TYPE_AUDIT_MANAGER); /*****************************************************************************/ static void _audit_field_init_string(AuditField * field, const char * name, const char * str, gboolean need_encoding, AuditBackend backends) { *field = (AuditField){ .name = name, .need_encoding = need_encoding, .backends = backends, .value_type = NM_VALUE_TYPE_STRING, .value.v_string = str, }; } static void _audit_field_init_uint64(AuditField *field, const char *name, guint64 val, AuditBackend backends) { *field = (AuditField){ .name = name, .backends = backends, .value_type = NM_VALUE_TYPE_UINT64, .value.v_uint64 = val, }; } static const char * build_message(NMStrBuf *strbuf, AuditBackend backend, GPtrArray *fields) { guint i; if (strbuf->len == 0) { /* preallocate a large buffer... */ nm_str_buf_maybe_expand(strbuf, NM_UTILS_GET_NEXT_REALLOC_SIZE_232, FALSE); } else nm_str_buf_reset(strbuf); for (i = 0; i < fields->len; i++) { const AuditField *field = fields->pdata[i]; if (!NM_FLAGS_ANY(field->backends, backend)) continue; nm_str_buf_append_required_delimiter(strbuf, ' '); if (field->value_type == NM_VALUE_TYPE_STRING) { const char *str = field->value.v_string; #if HAVE_LIBAUDIT if (backend == BACKEND_AUDITD) { if (field->need_encoding) { gs_free char *value = NULL; value = audit_encode_nv_string(field->name, str, 0); nm_str_buf_append(strbuf, value); } else nm_str_buf_append_printf(strbuf, "%s=%s", field->name, str); continue; } #endif /* HAVE_LIBAUDIT */ nm_str_buf_append_printf(strbuf, "%s=\"%s\"", field->name, str); continue; } if (field->value_type == NM_VALUE_TYPE_UINT64) { nm_str_buf_append_printf(strbuf, "%s=%" G_GUINT64_FORMAT, field->name, field->value.v_uint64); continue; } g_return_val_if_reached(NULL); } return nm_str_buf_get_str(strbuf); } static void nm_audit_log(NMAuditManager *self, GPtrArray * fields, const char * file, guint line, const char * func, gboolean success) { nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(0, FALSE); #if HAVE_LIBAUDIT NMAuditManagerPrivate *priv; #endif g_return_if_fail(NM_IS_AUDIT_MANAGER(self)); #if HAVE_LIBAUDIT priv = NM_AUDIT_MANAGER_GET_PRIVATE(self); if (priv->auditd_fd >= 0) { audit_log_user_message(priv->auditd_fd, AUDIT_USYS_CONFIG, build_message(&strbuf, BACKEND_AUDITD, fields), NULL, NULL, NULL, success); } #endif if (nm_logging_enabled(AUDIT_LOG_LEVEL, LOGD_AUDIT)) { _nm_log_full(file, line, func, !(NM_THREAD_SAFE_ON_MAIN_THREAD), AUDIT_LOG_LEVEL, LOGD_AUDIT, 0, NULL, NULL, "%s%s", _NMLOG_PREFIX_NAME ": ", build_message(&strbuf, BACKEND_LOG, fields)); } } static void _audit_log_helper(NMAuditManager *self, GPtrArray * fields, const char * file, guint line, const char * func, const char * op, gboolean result, gpointer subject_context, const char * reason) { AuditField op_field; AuditField pid_field; AuditField uid_field; AuditField result_field; AuditField reason_field; gulong pid; gulong uid; NMAuthSubject * subject = NULL; gs_unref_object NMAuthSubject *subject_free = NULL; _audit_field_init_string(&op_field, "op", op, FALSE, BACKEND_ALL); g_ptr_array_insert(fields, 0, &op_field); 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_dbus_manager_new_auth_subject_from_context(context); } else g_warn_if_reached(); } if (subject && nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) { 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_uint64(&pid_field, "pid", pid, BACKEND_ALL); g_ptr_array_add(fields, &pid_field); } if (uid != G_MAXULONG) { _audit_field_init_uint64(&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, const char * func, const char * op, NMSettingsConnection *connection, gboolean result, const char * args, gpointer subject_context, const char * reason) { gs_unref_ptrarray GPtrArray *fields = NULL; AuditField uuid_field; AuditField name_field; AuditField args_field; g_return_if_fail(op); fields = g_ptr_array_new(); if (connection) { _audit_field_init_string(&uuid_field, "uuid", nm_settings_connection_get_uuid(connection), FALSE, BACKEND_ALL); g_ptr_array_add(fields, &uuid_field); _audit_field_init_string(&name_field, "name", nm_settings_connection_get_id(connection), TRUE, BACKEND_ALL); g_ptr_array_add(fields, &name_field); } if (args) { _audit_field_init_string(&args_field, "args", args, FALSE, BACKEND_ALL); g_ptr_array_add(fields, &args_field); } _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); } void _nm_audit_manager_log_generic_op(NMAuditManager *self, const char * file, guint line, const char * func, const char * op, const char * arg, gboolean result, gpointer subject_context, 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); _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); } void _nm_audit_manager_log_device_op(NMAuditManager *self, const char * file, guint line, const char * func, const char * op, NMDevice * device, gboolean result, const char * args, gpointer subject_context, const char * reason) { gs_unref_ptrarray GPtrArray *fields = NULL; AuditField interface_field; AuditField ifindex_field; AuditField args_field; 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_uint64(&ifindex_field, "ifindex", ifindex, BACKEND_ALL); g_ptr_array_add(fields, &ifindex_field); } if (args) { _audit_field_init_string(&args_field, "args", args, FALSE, BACKEND_ALL); g_ptr_array_add(fields, &args_field); } _audit_log_helper(self, fields, file, line, func, op, result, subject_context, reason); } #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); int errsv; if (nm_config_data_get_value_boolean(data, NM_CONFIG_KEYFILE_GROUP_LOGGING, NM_CONFIG_KEYFILE_KEY_LOGGING_AUDIT, NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL)) { if (priv->auditd_fd < 0) { priv->auditd_fd = audit_open(); if (priv->auditd_fd < 0) { errsv = errno; _LOGE(LOGD_CORE, "failed to open auditd socket: %s", nm_strerror_native(errsv)); } else _LOGD(LOGD_CORE, "socket created"); } } else { if (priv->auditd_fd >= 0) { audit_close(priv->auditd_fd); priv->auditd_fd = -1; _LOGD(LOGD_CORE, "socket closed"); } } } 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 /*****************************************************************************/ 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; }