aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS15
-rw-r--r--Makefile10
-rw-r--r--Makefile.objs8
-rw-r--r--Makefile.target2
-rw-r--r--authz/Makefile.objs7
-rw-r--r--authz/base.c82
-rw-r--r--authz/list.c271
-rw-r--r--authz/listfile.c283
-rw-r--r--authz/pamacct.c148
-rw-r--r--authz/simple.c115
-rw-r--r--authz/trace-events18
-rwxr-xr-xconfigure54
-rw-r--r--crypto/tlssession.c35
-rw-r--r--crypto/trace-events2
-rw-r--r--hw/usb/dev-mtp.c281
-rw-r--r--hw/usb/trace-events2
-rw-r--r--include/authz/base.h112
-rw-r--r--include/authz/list.h106
-rw-r--r--include/authz/listfile.h111
-rw-r--r--include/authz/pamacct.h100
-rw-r--r--include/authz/simple.h84
-rw-r--r--include/qemu/acl.h66
-rw-r--r--include/qemu/filemonitor.h128
-rw-r--r--monitor.c179
-rw-r--r--qapi/Makefile.objs2
-rw-r--r--qapi/authz.json58
-rw-r--r--qapi/qapi-schema.json1
-rw-r--r--qemu-options.hx105
-rw-r--r--qom/object.c12
-rw-r--r--qom/object_interfaces.c16
-rw-r--r--tests/Makefile.include16
-rw-r--r--tests/test-authz-list.c159
-rw-r--r--tests/test-authz-listfile.c195
-rw-r--r--tests/test-authz-pam.c124
-rw-r--r--tests/test-authz-simple.c50
-rw-r--r--tests/test-crypto-tlssession.c15
-rw-r--r--tests/test-io-channel-tls.c16
-rw-r--r--tests/test-util-filemonitor.c685
-rw-r--r--ui/vnc-auth-sasl.c23
-rw-r--r--ui/vnc-auth-sasl.h5
-rw-r--r--ui/vnc-auth-vencrypt.c2
-rw-r--r--ui/vnc-ws.c2
-rw-r--r--ui/vnc.c37
-rw-r--r--ui/vnc.h4
-rw-r--r--util/Makefile.objs4
-rw-r--r--util/acl.c179
-rw-r--r--util/filemonitor-inotify.c339
-rw-r--r--util/filemonitor-stub.c59
-rw-r--r--util/trace-events9
49 files changed, 3773 insertions, 563 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 738580884f..7be8c578ea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2079,6 +2079,14 @@ F: io/
F: include/io/
F: tests/test-io-*
+User authorization
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Maintained
+F: authz/
+F: qapi/authz.json
+F: include/authz/
+F: tests/test-authz-*
+
Sockets
M: Daniel P. Berrange <berrange@redhat.com>
M: Gerd Hoffmann <kraxel@redhat.com>
@@ -2087,6 +2095,13 @@ F: include/qemu/sockets.h
F: util/qemu-sockets.c
F: qapi/sockets.json
+File monitor
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Odd fixes
+F: util/filemonitor*.c
+F: include/qemu/filemonitor.h
+F: tests/test-util-filemonitor.c
+
Throttling infrastructure
M: Alberto Garcia <berto@igalia.com>
S: Supported
diff --git a/Makefile b/Makefile
index a6de28677f..7fa04e0821 100644
--- a/Makefile
+++ b/Makefile
@@ -359,6 +359,7 @@ endif
dummy := $(call unnest-vars,, \
stub-obj-y \
+ authz-obj-y \
chardev-obj-y \
util-obj-y \
qga-obj-y \
@@ -423,6 +424,7 @@ qemu-options.def: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
+$(SOFTMMU_SUBDIR_RULES): $(authz-obj-y)
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
$(SOFTMMU_SUBDIR_RULES): $(io-obj-y)
@@ -485,9 +487,9 @@ COMMON_LDADDS = libqemuutil.a
qemu-img.o: qemu-img-cmds.h
-qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-img$(EXESUF): qemu-img.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-nbd$(EXESUF): qemu-nbd.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-io$(EXESUF): qemu-io.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
@@ -498,7 +500,7 @@ qemu-edid$(EXESUF): qemu-edid.o hw/display/edid-generate.o $(COMMON_LDADDS)
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
-scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(authz-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
ifdef CONFIG_MPATH
scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist
endif
diff --git a/Makefile.objs b/Makefile.objs
index 5fb022d7ad..6e91ee5674 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -1,12 +1,17 @@
#######################################################################
# Common libraries for tools and emulators
-stub-obj-y = stubs/ crypto/
+stub-obj-y = stubs/ util/ crypto/
util-obj-y = util/ qobject/ qapi/
chardev-obj-y = chardev/
slirp-obj-$(CONFIG_SLIRP) = slirp/
#######################################################################
+# authz-obj-y is code used by both qemu system emulation and qemu-img
+
+authz-obj-y = authz/
+
+#######################################################################
# block-obj-y is code used by both qemu system emulation and qemu-img
block-obj-y += nbd/
@@ -125,6 +130,7 @@ trace-events-subdirs =
trace-events-subdirs += accel/kvm
trace-events-subdirs += accel/tcg
trace-events-subdirs += audio
+trace-events-subdirs += authz
trace-events-subdirs += block
trace-events-subdirs += chardev
trace-events-subdirs += crypto
diff --git a/Makefile.target b/Makefile.target
index d6ce549388..3b79e7074c 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -179,6 +179,7 @@ include $(SRC_PATH)/Makefile.objs
dummy := $(call unnest-vars,,target-obj-y)
target-obj-y-save := $(target-obj-y)
dummy := $(call unnest-vars,.., \
+ authz-obj-y \
block-obj-y \
block-obj-m \
chardev-obj-y \
@@ -193,6 +194,7 @@ target-obj-y := $(target-obj-y-save)
all-obj-y += $(common-obj-y)
all-obj-y += $(target-obj-y)
all-obj-y += $(qom-obj-y)
+all-obj-$(CONFIG_SOFTMMU) += $(authz-obj-y)
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
new file mode 100644
index 0000000000..ed7b273596
--- /dev/null
+++ b/authz/Makefile.objs
@@ -0,0 +1,7 @@
+authz-obj-y += base.o
+authz-obj-y += simple.o
+authz-obj-y += list.o
+authz-obj-y += listfile.o
+authz-obj-$(CONFIG_AUTH_PAM) += pamacct.o
+
+pamacct.o-libs = -lpam
diff --git a/authz/base.c b/authz/base.c
new file mode 100644
index 0000000000..110dfa4195
--- /dev/null
+++ b/authz/base.c
@@ -0,0 +1,82 @@
+/*
+ * QEMU authorization framework base class
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/base.h"
+#include "authz/trace.h"
+
+bool qauthz_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZClass *cls = QAUTHZ_GET_CLASS(authz);
+ bool allowed;
+
+ allowed = cls->is_allowed(authz, identity, errp);
+ trace_qauthz_is_allowed(authz, identity, allowed);
+
+ return allowed;
+}
+
+
+bool qauthz_is_allowed_by_id(const char *authzid,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZ *authz;
+ Object *obj;
+ Object *container;
+
+ container = object_get_objects_root();
+ obj = object_resolve_path_component(container,
+ authzid);
+ if (!obj) {
+ error_setg(errp, "Cannot find QAuthZ object ID %s",
+ authzid);
+ return false;
+ }
+
+ if (!object_dynamic_cast(obj, TYPE_QAUTHZ)) {
+ error_setg(errp, "Object '%s' is not a QAuthZ subclass",
+ authzid);
+ return false;
+ }
+
+ authz = QAUTHZ(obj);
+
+ return qauthz_is_allowed(authz, identity, errp);
+}
+
+
+static const TypeInfo authz_info = {
+ .parent = TYPE_OBJECT,
+ .name = TYPE_QAUTHZ,
+ .instance_size = sizeof(QAuthZ),
+ .class_size = sizeof(QAuthZClass),
+ .abstract = true,
+};
+
+static void qauthz_register_types(void)
+{
+ type_register_static(&authz_info);
+}
+
+type_init(qauthz_register_types)
+
diff --git a/authz/list.c b/authz/list.c
new file mode 100644
index 0000000000..dc6b0fec13
--- /dev/null
+++ b/authz/list.c
@@ -0,0 +1,271 @@
+/*
+ * QEMU access control list authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/list.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+#include "qapi/qapi-visit-authz.h"
+
+static bool qauthz_list_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(authz);
+ QAuthZListRuleList *rules = lauthz->rules;
+
+ while (rules) {
+ QAuthZListRule *rule = rules->value;
+ QAuthZListFormat format = rule->has_format ? rule->format :
+ QAUTHZ_LIST_FORMAT_EXACT;
+
+ trace_qauthz_list_check_rule(authz, rule->match, identity,
+ format, rule->policy);
+ switch (format) {
+ case QAUTHZ_LIST_FORMAT_EXACT:
+ if (g_str_equal(rule->match, identity)) {
+ return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
+ }
+ break;
+ case QAUTHZ_LIST_FORMAT_GLOB:
+ if (g_pattern_match_simple(rule->match, identity)) {
+ return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
+ }
+ break;
+ default:
+ g_warn_if_reached();
+ return false;
+ }
+ rules = rules->next;
+ }
+
+ trace_qauthz_list_default_policy(authz, identity, lauthz->policy);
+ return lauthz->policy == QAUTHZ_LIST_POLICY_ALLOW;
+}
+
+
+static void
+qauthz_list_prop_set_policy(Object *obj,
+ int value,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+ lauthz->policy = value;
+}
+
+
+static int
+qauthz_list_prop_get_policy(Object *obj,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+ return lauthz->policy;
+}
+
+
+static void
+qauthz_list_prop_get_rules(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+ visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
+}
+
+static void
+qauthz_list_prop_set_rules(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(obj);
+ QAuthZListRuleList *oldrules;
+
+ oldrules = lauthz->rules;
+ visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
+
+ qapi_free_QAuthZListRuleList(oldrules);
+}
+
+
+static void
+qauthz_list_finalize(Object *obj)
+{
+ QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+ qapi_free_QAuthZListRuleList(lauthz->rules);
+}
+
+
+static void
+qauthz_list_class_init(ObjectClass *oc, void *data)
+{
+ QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+ object_class_property_add_enum(oc, "policy",
+ "QAuthZListPolicy",
+ &QAuthZListPolicy_lookup,
+ qauthz_list_prop_get_policy,
+ qauthz_list_prop_set_policy,
+ NULL);
+
+ object_class_property_add(oc, "rules", "QAuthZListRule",
+ qauthz_list_prop_get_rules,
+ qauthz_list_prop_set_rules,
+ NULL, NULL, NULL);
+
+ authz->is_allowed = qauthz_list_is_allowed;
+}
+
+
+QAuthZList *qauthz_list_new(const char *id,
+ QAuthZListPolicy policy,
+ Error **errp)
+{
+ return QAUTHZ_LIST(
+ object_new_with_props(TYPE_QAUTHZ_LIST,
+ object_get_objects_root(),
+ id, errp,
+ "policy", QAuthZListPolicy_str(policy),
+ NULL));
+}
+
+ssize_t qauthz_list_append_rule(QAuthZList *auth,
+ const char *match,
+ QAuthZListPolicy policy,
+ QAuthZListFormat format,
+ Error **errp)
+{
+ QAuthZListRule *rule;
+ QAuthZListRuleList *rules, *tmp;
+ size_t i = 0;
+
+ rule = g_new0(QAuthZListRule, 1);
+ rule->policy = policy;
+ rule->match = g_strdup(match);
+ rule->format = format;
+ rule->has_format = true;
+
+ tmp = g_new0(QAuthZListRuleList, 1);
+ tmp->value = rule;
+
+ rules = auth->rules;
+ if (rules) {
+ while (rules->next) {
+ i++;
+ rules = rules->next;
+ }
+ rules->next = tmp;
+ return i + 1;
+ } else {
+ auth->rules = tmp;
+ return 0;
+ }
+}
+
+
+ssize_t qauthz_list_insert_rule(QAuthZList *auth,
+ const char *match,
+ QAuthZListPolicy policy,
+ QAuthZListFormat format,
+ size_t index,
+ Error **errp)
+{
+ QAuthZListRule *rule;
+ QAuthZListRuleList *rules, *tmp;
+ size_t i = 0;
+
+ rule = g_new0(QAuthZListRule, 1);
+ rule->policy = policy;
+ rule->match = g_strdup(match);
+ rule->format = format;
+ rule->has_format = true;
+
+ tmp = g_new0(QAuthZListRuleList, 1);
+ tmp->value = rule;
+
+ rules = auth->rules;
+ if (rules && index > 0) {
+ while (rules->next && i < (index - 1)) {
+ i++;
+ rules = rules->next;
+ }
+ tmp->next = rules->next;
+ rules->next = tmp;
+ return i + 1;
+ } else {
+ tmp->next = auth->rules;
+ auth->rules = tmp;
+ return 0;
+ }
+}
+
+
+ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
+{
+ QAuthZListRule *rule;
+ QAuthZListRuleList *rules, *prev;
+ size_t i = 0;
+
+ prev = NULL;
+ rules = auth->rules;
+ while (rules) {
+ rule = rules->value;
+ if (g_str_equal(rule->match, match)) {
+ if (prev) {
+ prev->next = rules->next;
+ } else {
+ auth->rules = rules->next;
+ }
+ rules->next = NULL;
+ qapi_free_QAuthZListRuleList(rules);
+ return i;
+ }
+ prev = rules;
+ rules = rules->next;
+ i++;
+ }
+
+ return -1;
+}
+
+
+static const TypeInfo qauthz_list_info = {
+ .parent = TYPE_QAUTHZ,
+ .name = TYPE_QAUTHZ_LIST,
+ .instance_size = sizeof(QAuthZList),
+ .instance_finalize = qauthz_list_finalize,
+ .class_size = sizeof(QAuthZListClass),
+ .class_init = qauthz_list_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+
+static void
+qauthz_list_register_types(void)
+{
+ type_register_static(&qauthz_list_info);
+}
+
+
+type_init(qauthz_list_register_types);
diff --git a/authz/listfile.c b/authz/listfile.c
new file mode 100644
index 0000000000..d4579767e7
--- /dev/null
+++ b/authz/listfile.c
@@ -0,0 +1,283 @@
+/*
+ * QEMU access control list file authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/listfile.h"
+#include "authz/trace.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/sockets.h"
+#include "qemu/filemonitor.h"
+#include "qom/object_interfaces.h"
+#include "qapi/qapi-visit-authz.h"
+#include "qapi/qmp/qjson.h"
+#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/qobject-input-visitor.h"
+
+
+static bool
+qauthz_list_file_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(authz);
+ if (fauthz->list) {
+ return qauthz_is_allowed(fauthz->list, identity, errp);
+ }
+
+ return false;
+}
+
+
+static QAuthZ *
+qauthz_list_file_load(QAuthZListFile *fauthz, Error **errp)
+{
+ GError *err = NULL;
+ gchar *content = NULL;
+ gsize len;
+ QObject *obj = NULL;
+ QDict *pdict;
+ Visitor *v = NULL;
+ QAuthZ *ret = NULL;
+
+ trace_qauthz_list_file_load(fauthz, fauthz->filename);
+ if (!g_file_get_contents(fauthz->filename, &content, &len, &err)) {
+ error_setg(errp, "Unable to read '%s': %s",
+ fauthz->filename, err->message);
+ goto cleanup;
+ }
+
+ obj = qobject_from_json(content, errp);
+ if (!obj) {
+ goto cleanup;
+ }
+
+ pdict = qobject_to(QDict, obj);
+ if (!pdict) {
+ error_setg(errp, QERR_INVALID_PARAMETER_TYPE, "obj", "dict");
+ goto cleanup;
+ }
+
+ v = qobject_input_visitor_new(obj);
+
+ ret = (QAuthZ *)user_creatable_add_type(TYPE_QAUTHZ_LIST,
+ NULL, pdict, v, errp);
+
+ cleanup:
+ visit_free(v);
+ qobject_unref(obj);
+ if (err) {
+ g_error_free(err);
+ }
+ g_free(content);
+ return ret;
+}
+
+
+static void
+qauthz_list_file_event(int wd G_GNUC_UNUSED,
+ QFileMonitorEvent ev G_GNUC_UNUSED,
+ const char *name G_GNUC_UNUSED,
+ void *opaque)
+{
+ QAuthZListFile *fauthz = opaque;
+ Error *err = NULL;
+
+ if (ev != QFILE_MONITOR_EVENT_MODIFIED &&
+ ev != QFILE_MONITOR_EVENT_CREATED) {
+ return;
+ }
+
+ object_unref(OBJECT(fauthz->list));
+ fauthz->list = qauthz_list_file_load(fauthz, &err);
+ trace_qauthz_list_file_refresh(fauthz,
+ fauthz->filename, fauthz->list ? 1 : 0);
+ if (!fauthz->list) {
+ error_report_err(err);
+ }
+}
+
+static void
+qauthz_list_file_complete(UserCreatable *uc, Error **errp)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(uc);
+ gchar *dir = NULL, *file = NULL;
+
+ fauthz->list = qauthz_list_file_load(fauthz, errp);
+
+ if (!fauthz->refresh) {
+ return;
+ }
+
+ fauthz->file_monitor = qemu_file_monitor_new(errp);
+ if (!fauthz->file_monitor) {
+ return;
+ }
+
+ dir = g_path_get_dirname(fauthz->filename);
+ if (g_str_equal(dir, ".")) {
+ error_setg(errp, "Filename must be an absolute path");
+ goto cleanup;
+ }
+ file = g_path_get_basename(fauthz->filename);
+ if (g_str_equal(file, ".")) {
+ error_setg(errp, "Path has no trailing filename component");
+ goto cleanup;
+ }
+
+ fauthz->file_watch = qemu_file_monitor_add_watch(
+ fauthz->file_monitor, dir, file,
+ qauthz_list_file_event, fauthz, errp);
+ if (fauthz->file_watch < 0) {
+ goto cleanup;
+ }
+
+ cleanup:
+ g_free(file);
+ g_free(dir);
+}
+
+
+static void
+qauthz_list_file_prop_set_filename(Object *obj,
+ const char *value,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+ g_free(fauthz->filename);
+ fauthz->filename = g_strdup(value);
+}
+
+
+static char *
+qauthz_list_file_prop_get_filename(Object *obj,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+ return g_strdup(fauthz->filename);
+}
+
+
+static void
+qauthz_list_file_prop_set_refresh(Object *obj,
+ bool value,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+ fauthz->refresh = value;
+}
+
+
+static bool
+qauthz_list_file_prop_get_refresh(Object *obj,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+ return fauthz->refresh;
+}
+
+
+static void
+qauthz_list_file_finalize(Object *obj)
+{
+ QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+ object_unref(OBJECT(fauthz->list));
+ g_free(fauthz->filename);
+ qemu_file_monitor_free(fauthz->file_monitor);
+}
+
+
+static void
+qauthz_list_file_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+ QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+ ucc->complete = qauthz_list_file_complete;
+
+ object_class_property_add_str(oc, "filename",
+ qauthz_list_file_prop_get_filename,
+ qauthz_list_file_prop_set_filename,
+ NULL);
+ object_class_property_add_bool(oc, "refresh",
+ qauthz_list_file_prop_get_refresh,
+ qauthz_list_file_prop_set_refresh,
+ NULL);
+
+ authz->is_allowed = qauthz_list_file_is_allowed;
+}
+
+
+static void
+qauthz_list_file_init(Object *obj)
+{
+ QAuthZListFile *authz = QAUTHZ_LIST_FILE(obj);
+
+ authz->file_watch = -1;
+#ifdef CONFIG_INOTIFY1
+ authz->refresh = TRUE;
+#endif
+}
+
+
+QAuthZListFile *qauthz_list_file_new(const char *id,
+ const char *filename,
+ bool refresh,
+ Error **errp)
+{
+ return QAUTHZ_LIST_FILE(
+ object_new_with_props(TYPE_QAUTHZ_LIST_FILE,
+ object_get_objects_root(),
+ id, errp,
+ "filename", filename,
+ "refresh", refresh ? "yes" : "no",
+ NULL));
+}
+
+
+static const TypeInfo qauthz_list_file_info = {
+ .parent = TYPE_QAUTHZ,
+ .name = TYPE_QAUTHZ_LIST_FILE,
+ .instance_init = qauthz_list_file_init,
+ .instance_size = sizeof(QAuthZListFile),
+ .instance_finalize = qauthz_list_file_finalize,
+ .class_size = sizeof(QAuthZListFileClass),
+ .class_init = qauthz_list_file_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+
+static void
+qauthz_list_file_register_types(void)
+{
+ type_register_static(&qauthz_list_file_info);
+}
+
+
+type_init(qauthz_list_file_register_types);
diff --git a/authz/pamacct.c b/authz/pamacct.c
new file mode 100644
index 0000000000..5038358cdc
--- /dev/null
+++ b/authz/pamacct.c
@@ -0,0 +1,148 @@
+/*
+ * QEMU PAM authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/pamacct.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+
+#include <security/pam_appl.h>
+
+
+static bool qauthz_pam_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZPAM *pauthz = QAUTHZ_PAM(authz);
+ const struct pam_conv pam_conversation = { 0 };
+ pam_handle_t *pamh = NULL;
+ int ret;
+
+ trace_qauthz_pam_check(authz, identity, pauthz->service);
+ ret = pam_start(pauthz->service,
+ identity,
+ &pam_conversation,
+ &pamh);
+ if (ret != PAM_SUCCESS) {
+ error_setg(errp, "Unable to start PAM transaction: %s",
+ pam_strerror(NULL, ret));
+ return false;
+ }
+
+ ret = pam_acct_mgmt(pamh, PAM_SILENT);
+ pam_end(pamh, ret);
+ if (ret != PAM_SUCCESS) {
+ error_setg(errp, "Unable to authorize user '%s': %s",
+ identity, pam_strerror(pamh, ret));
+ return false;
+ }
+
+ return true;
+}
+
+
+static void
+qauthz_pam_prop_set_service(Object *obj,
+ const char *service,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZPAM *pauthz = QAUTHZ_PAM(obj);
+
+ g_free(pauthz->service);
+ pauthz->service = g_strdup(service);
+}
+
+
+static char *
+qauthz_pam_prop_get_service(Object *obj,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZPAM *pauthz = QAUTHZ_PAM(obj);
+
+ return g_strdup(pauthz->service);
+}
+
+
+static void
+qauthz_pam_complete(UserCreatable *uc, Error **errp)
+{
+}
+
+
+static void
+qauthz_pam_finalize(Object *obj)
+{
+ QAuthZPAM *pauthz = QAUTHZ_PAM(obj);
+
+ g_free(pauthz->service);
+}
+
+
+static void
+qauthz_pam_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+ QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+ ucc->complete = qauthz_pam_complete;
+ authz->is_allowed = qauthz_pam_is_allowed;
+
+ object_class_property_add_str(oc, "service",
+ qauthz_pam_prop_get_service,
+ qauthz_pam_prop_set_service,
+ NULL);
+}
+
+
+QAuthZPAM *qauthz_pam_new(const char *id,
+ const char *service,
+ Error **errp)
+{
+ return QAUTHZ_PAM(
+ object_new_with_props(TYPE_QAUTHZ_PAM,
+ object_get_objects_root(),
+ id, errp,
+ "service", service,
+ NULL));
+}
+
+
+static const TypeInfo qauthz_pam_info = {
+ .parent = TYPE_QAUTHZ,
+ .name = TYPE_QAUTHZ_PAM,
+ .instance_size = sizeof(QAuthZPAM),
+ .instance_finalize = qauthz_pam_finalize,
+ .class_size = sizeof(QAuthZPAMClass),
+ .class_init = qauthz_pam_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+
+static void
+qauthz_pam_register_types(void)
+{
+ type_register_static(&qauthz_pam_info);
+}
+
+
+type_init(qauthz_pam_register_types);
diff --git a/authz/simple.c b/authz/simple.c
new file mode 100644
index 0000000000..8ab718803e
--- /dev/null
+++ b/authz/simple.c
@@ -0,0 +1,115 @@
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/simple.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+
+static bool qauthz_simple_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp)
+{
+ QAuthZSimple *sauthz = QAUTHZ_SIMPLE(authz);
+
+ trace_qauthz_simple_is_allowed(authz, sauthz->identity, identity);
+ return g_str_equal(identity, sauthz->identity);
+}
+
+static void
+qauthz_simple_prop_set_identity(Object *obj,
+ const char *value,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+ g_free(sauthz->identity);
+ sauthz->identity = g_strdup(value);
+}
+
+
+static char *
+qauthz_simple_prop_get_identity(Object *obj,
+ Error **errp G_GNUC_UNUSED)
+{
+ QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+ return g_strdup(sauthz->identity);
+}
+
+
+static void
+qauthz_simple_finalize(Object *obj)
+{
+ QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+ g_free(sauthz->identity);
+}
+
+
+static void
+qauthz_simple_class_init(ObjectClass *oc, void *data)
+{
+ QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+ authz->is_allowed = qauthz_simple_is_allowed;
+
+ object_class_property_add_str(oc, "identity",
+ qauthz_simple_prop_get_identity,
+ qauthz_simple_prop_set_identity,
+ NULL);
+}
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+ const char *identity,
+ Error **errp)
+{
+ return QAUTHZ_SIMPLE(
+ object_new_with_props(TYPE_QAUTHZ_SIMPLE,
+ object_get_objects_root(),
+ id, errp,
+ "identity", identity,
+ NULL));
+}
+
+
+static const TypeInfo qauthz_simple_info = {
+ .parent = TYPE_QAUTHZ,
+ .name = TYPE_QAUTHZ_SIMPLE,
+ .instance_size = sizeof(QAuthZSimple),
+ .instance_finalize = qauthz_simple_finalize,
+ .class_size = sizeof(QAuthZSimpleClass),
+ .class_init = qauthz_simple_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+
+static void
+qauthz_simple_register_types(void)
+{
+ type_register_static(&qauthz_simple_info);
+}
+
+
+type_init(qauthz_simple_register_types);
diff --git a/authz/trace-events b/authz/trace-events
new file mode 100644
index 0000000000..72c411927d
--- /dev/null
+++ b/authz/trace-events
@@ -0,0 +1,18 @@
+# See docs/devel/tracing.txt for syntax documentation.
+
+# authz/base.c
+qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
+
+# auth/simple.c
+qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
+
+# auth/list.c
+qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
+qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
+
+# auth/listfile.c
+qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
+qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
+
+# auth/pam.c
+qauthz_pam_check(void *authz, const char *identity, const char *service) "AuthZ PAM %p identity=%s service=%s"
diff --git a/configure b/configure
index 05d72f1c56..694088a4ec 100755
--- a/configure
+++ b/configure
@@ -463,6 +463,7 @@ gnutls=""
nettle=""
gcrypt=""
gcrypt_hmac="no"
+auth_pam=""
vte=""
virglrenderer=""
tpm="yes"
@@ -1381,6 +1382,10 @@ for opt do
;;
--enable-gcrypt) gcrypt="yes"
;;
+ --disable-auth-pam) auth_pam="no"
+ ;;
+ --enable-auth-pam) auth_pam="yes"
+ ;;
--enable-rdma) rdma="yes"
;;
--disable-rdma) rdma="no"
@@ -1707,6 +1712,7 @@ disabled with --disable-FEATURE, default is enabled if available:
gnutls GNUTLS cryptography support
nettle nettle cryptography support
gcrypt libgcrypt cryptography support
+ auth-pam PAM access control
sdl SDL UI
sdl_image SDL Image support for icons
gtk gtk UI
@@ -2865,6 +2871,33 @@ fi
##########################################
+# PAM probe
+
+if test "$auth_pam" != "no"; then
+ cat > $TMPC <<EOF
+#include <security/pam_appl.h>
+#include <stdio.h>
+int main(void) {
+ const char *service_name = "qemu";
+ const char *user = "frank";
+ const struct pam_conv *pam_conv = NULL;
+ pam_handle_t *pamh = NULL;
+ pam_start(service_name, user, pam_conv, &pamh);
+ return 0;
+}
+EOF
+ if compile_prog "" "-lpam" ; then
+ auth_pam=yes
+ else
+ if test "$auth_pam" = "yes"; then
+ feature_not_found "PAM" "Install PAM development package"
+ else
+ auth_pam=no
+ fi
+ fi
+fi
+
+##########################################
# getifaddrs (for tests/test-io-channel-socket )
have_ifaddrs_h=yes
@@ -3172,20 +3205,6 @@ if test "$xkbcommon" != "no" ; then
fi
fi
-##########################################
-# fnmatch() probe, used for ACL routines
-fnmatch="no"
-cat > $TMPC << EOF
-#include <fnmatch.h>
-int main(void)
-{
- fnmatch("foo", "foo", 0);
- return 0;
-}
-EOF
-if compile_prog "" "" ; then
- fnmatch="yes"
-fi
##########################################
# xfsctl() probe, used for file-posix.c
@@ -6091,6 +6110,7 @@ echo "GNUTLS support $gnutls"
echo "libgcrypt $gcrypt"
echo "nettle $nettle $(echo_version $nettle $nettle_version)"
echo "libtasn1 $tasn1"
+echo "PAM $auth_pam"
echo "curses support $curses"
echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)"
echo "curl support $curl"
@@ -6382,9 +6402,6 @@ if test "$xkbcommon" = "yes" ; then
echo "XKBCOMMON_CFLAGS=$xkbcommon_cflags" >> $config_host_mak
echo "XKBCOMMON_LIBS=$xkbcommon_libs" >> $config_host_mak
fi
-if test "$fnmatch" = "yes" ; then
- echo "CONFIG_FNMATCH=y" >> $config_host_mak
-fi
if test "$xfs" = "yes" ; then
echo "CONFIG_XFS=y" >> $config_host_mak
fi
@@ -6550,6 +6567,9 @@ fi
if test "$tasn1" = "yes" ; then
echo "CONFIG_TASN1=y" >> $config_host_mak
fi
+if test "$auth_pam" = "yes" ; then
+ echo "CONFIG_AUTH_PAM=y" >> $config_host_mak
+fi
if test "$have_ifaddrs_h" = "yes" ; then
echo "HAVE_IFADDRS_H=y" >> $config_host_mak
fi
diff --git a/crypto/tlssession.c b/crypto/tlssession.c
index 0dedd4af52..c3a920dfe8 100644
--- a/crypto/tlssession.c
+++ b/crypto/tlssession.c
@@ -24,7 +24,7 @@
#include "crypto/tlscredspsk.h"
#include "crypto/tlscredsx509.h"
#include "qapi/error.h"
-#include "qemu/acl.h"
+#include "authz/base.h"
#include "trace.h"
#ifdef CONFIG_GNUTLS
@@ -37,7 +37,7 @@ struct QCryptoTLSSession {
QCryptoTLSCreds *creds;
gnutls_session_t handle;
char *hostname;
- char *aclname;
+ char *authzid;
bool handshakeComplete;
QCryptoTLSSessionWriteFunc writeFunc;
QCryptoTLSSessionReadFunc readFunc;
@@ -56,7 +56,7 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
gnutls_deinit(session->handle);
g_free(session->hostname);
g_free(session->peername);
- g_free(session->aclname);
+ g_free(session->authzid);
object_unref(OBJECT(session->creds));
g_free(session);
}
@@ -95,7 +95,7 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
QCryptoTLSSession *
qcrypto_tls_session_new(QCryptoTLSCreds *creds,
const char *hostname,
- const char *aclname,
+ const char *authzid,
QCryptoTLSCredsEndpoint endpoint,
Error **errp)
{
@@ -105,13 +105,13 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds,
session = g_new0(QCryptoTLSSession, 1);
trace_qcrypto_tls_session_new(
session, creds, hostname ? hostname : "<none>",
- aclname ? aclname : "<none>", endpoint);
+ authzid ? authzid : "<none>", endpoint);
if (hostname) {
session->hostname = g_strdup(hostname);
}
- if (aclname) {
- session->aclname = g_strdup(aclname);
+ if (authzid) {
+ session->authzid = g_strdup(authzid);
}
session->creds = creds;
object_ref(OBJECT(creds));
@@ -262,6 +262,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
unsigned int nCerts, i;
time_t now;
gnutls_x509_crt_t cert = NULL;
+ Error *err = NULL;
now = time(NULL);
if (now == ((time_t)-1)) {
@@ -349,19 +350,17 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
gnutls_strerror(ret));
goto error;
}
- if (session->aclname) {
- qemu_acl *acl = qemu_acl_find(session->aclname);
- int allow;
- if (!acl) {
- error_setg(errp, "Cannot find ACL %s",
- session->aclname);
+ if (session->authzid) {
+ bool allow;
+
+ allow = qauthz_is_allowed_by_id(session->authzid,
+ session->peername, &err);
+ if (err) {
+ error_propagate(errp, err);
goto error;
}
-
- allow = qemu_acl_party_is_allowed(acl, session->peername);
-
if (!allow) {
- error_setg(errp, "TLS x509 ACL check for %s is denied",
+ error_setg(errp, "TLS x509 authz check for %s is denied",
session->peername);
goto error;
}
@@ -555,7 +554,7 @@ qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
QCryptoTLSSession *
qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
const char *hostname G_GNUC_UNUSED,
- const char *aclname G_GNUC_UNUSED,
+ const char *authzid G_GNUC_UNUSED,
QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
Error **errp)
{
diff --git a/crypto/trace-events b/crypto/trace-events
index 597389b73c..a38ad7b787 100644
--- a/crypto/trace-events
+++ b/crypto/trace-events
@@ -19,5 +19,5 @@ qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "T
qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
# crypto/tlssession.c
-qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"
+qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *authzid, int endpoint) "TLS session new session=%p creds=%p hostname=%s authzid=%s endpoint=%d"
qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
index f1d20fa1b9..4ee4fc5a89 100644
--- a/hw/usb/dev-mtp.c
+++ b/hw/usb/dev-mtp.c
@@ -11,17 +11,16 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
#include <wchar.h>
#include <dirent.h>
#include <sys/statvfs.h>
-#ifdef CONFIG_INOTIFY1
-#include <sys/inotify.h>
-#include "qemu/main-loop.h"
-#endif
+
#include "qemu-common.h"
#include "qemu/iov.h"
+#include "qemu/filemonitor.h"
#include "trace.h"
#include "hw/usb.h"
#include "desc.h"
@@ -132,7 +131,6 @@ enum {
EP_EVENT,
};
-#ifdef CONFIG_INOTIFY1
typedef struct MTPMonEntry MTPMonEntry;
struct MTPMonEntry {
@@ -141,7 +139,6 @@ struct MTPMonEntry {
QTAILQ_ENTRY(MTPMonEntry) next;
};
-#endif
struct MTPControl {
uint16_t code;
@@ -172,10 +169,8 @@ struct MTPObject {
char *name;
char *path;
struct stat stat;
-#ifdef CONFIG_INOTIFY1
- /* inotify watch cookie */
- int watchfd;
-#endif
+ /* file monitor watch id */
+ int watchid;
MTPObject *parent;
uint32_t nchildren;
QLIST_HEAD(, MTPObject) children;
@@ -198,11 +193,8 @@ struct MTPState {
bool readonly;
QTAILQ_HEAD(, MTPObject) objects;
-#ifdef CONFIG_INOTIFY1
- /* inotify descriptor */
- int inotifyfd;
+ QFileMonitor *file_monitor;
QTAILQ_HEAD(, MTPMonEntry) events;
-#endif
/* Responder is expecting a write operation */
bool write_pending;
struct {
@@ -383,7 +375,7 @@ static const USBDesc desc = {
/* ----------------------------------------------------------------------- */
static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
- MTPObject *parent, char *name)
+ MTPObject *parent, const char *name)
{
MTPObject *o = g_new0(MTPObject, 1);
@@ -391,6 +383,7 @@ static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
goto ignore;
}
+ o->watchid = -1;
o->handle = handle;
o->parent = parent;
o->name = g_strdup(name);
@@ -437,6 +430,10 @@ static void usb_mtp_object_free(MTPState *s, MTPObject *o)
trace_usb_mtp_object_free(s->dev.addr, o->handle, o->path);
+ if (o->watchid != -1 && s->file_monitor) {
+ qemu_file_monitor_remove_watch(s->file_monitor, o->path, o->watchid);
+ }
+
QTAILQ_REMOVE(&s->objects, o, next);
if (o->parent) {
QLIST_REMOVE(o, list);
@@ -465,7 +462,7 @@ static MTPObject *usb_mtp_object_lookup(MTPState *s, uint32_t handle)
}
static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
- char *name)
+ const char *name)
{
MTPObject *child =
usb_mtp_object_alloc(s, s->next_handle++, o, name);
@@ -484,10 +481,14 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
}
static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
- char *name, int len)
+ const char *name, int len)
{
MTPObject *iter;
+ if (len == -1) {
+ len = strlen(name);
+ }
+
QLIST_FOREACH(iter, &parent->children, list) {
if (strncmp(iter->name, name, len) == 0) {
return iter;
@@ -497,13 +498,12 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
return NULL;
}
-#ifdef CONFIG_INOTIFY1
-static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
+static MTPObject *usb_mtp_object_lookup_id(MTPState *s, int id)
{
MTPObject *iter;
QTAILQ_FOREACH(iter, &s->objects, next) {
- if (iter->watchfd == wd) {
+ if (iter->watchid == id) {
return iter;
}
}
@@ -511,160 +511,103 @@ static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
return NULL;
}
-static void inotify_watchfn(void *arg)
+static void file_monitor_event(int id,
+ QFileMonitorEvent ev,
+ const char *name,
+ void *opaque)
{
- MTPState *s = arg;
- ssize_t bytes;
- /* From the man page: atleast one event can be read */
- int pos;
- char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
-
- for (;;) {
- bytes = read(s->inotifyfd, buf, sizeof(buf));
- pos = 0;
-
- if (bytes <= 0) {
- /* Better luck next time */
+ MTPState *s = opaque;
+ MTPObject *parent = usb_mtp_object_lookup_id(s, id);
+ MTPMonEntry *entry = NULL;
+ MTPObject *o;
+
+ if (!parent) {
+ return;
+ }
+
+ switch (ev) {
+ case QFILE_MONITOR_EVENT_CREATED:
+ if (usb_mtp_object_lookup_name(parent, name, -1)) {
+ /* Duplicate create event */
return;
}
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = s->next_handle;
+ entry->event = EVT_OBJ_ADDED;
+ o = usb_mtp_add_child(s, parent, name);
+ if (!o) {
+ g_free(entry);
+ return;
+ }
+ trace_usb_mtp_file_monitor_event(s->dev.addr, name, "Obj Added");
+ break;
+ case QFILE_MONITOR_EVENT_DELETED:
/*
- * TODO: Ignore initiator initiated events.
- * For now we are good because the store is RO
+ * The kernel issues a IN_IGNORED event
+ * when a dir containing a watchpoint is
+ * deleted, so we don't have to delete the
+ * watchpoint
*/
- while (bytes > 0) {
- char *p = buf + pos;
- struct inotify_event *event = (struct inotify_event *)p;
- int watchfd = 0;
- uint32_t mask = event->mask & (IN_CREATE | IN_DELETE |
- IN_MODIFY | IN_IGNORED);
- MTPObject *parent = usb_mtp_object_lookup_wd(s, event->wd);
- MTPMonEntry *entry = NULL;
- MTPObject *o;
-
- pos = pos + sizeof(struct inotify_event) + event->len;
- bytes = bytes - pos;
-
- if (!parent) {
- continue;
- }
-
- switch (mask) {
- case IN_CREATE:
- if (usb_mtp_object_lookup_name
- (parent, event->name, event->len)) {
- /* Duplicate create event */
- continue;
- }
- entry = g_new0(MTPMonEntry, 1);
- entry->handle = s->next_handle;
- entry->event = EVT_OBJ_ADDED;
- o = usb_mtp_add_child(s, parent, event->name);
- if (!o) {
- g_free(entry);
- continue;
- }
- o->watchfd = watchfd;
- trace_usb_mtp_inotify_event(s->dev.addr, event->name,
- event->mask, "Obj Added");
- break;
-
- case IN_DELETE:
- /*
- * The kernel issues a IN_IGNORED event
- * when a dir containing a watchpoint is
- * deleted, so we don't have to delete the
- * watchpoint
- */
- o = usb_mtp_object_lookup_name(parent, event->name, event->len);
- if (!o) {
- continue;
- }
- entry = g_new0(MTPMonEntry, 1);
- entry->handle = o->handle;
- entry->event = EVT_OBJ_REMOVED;
- trace_usb_mtp_inotify_event(s->dev.addr, o->path,
- event->mask, "Obj Deleted");
- usb_mtp_object_free(s, o);
- break;
-
- case IN_MODIFY:
- o = usb_mtp_object_lookup_name(parent, event->name, event->len);
- if (!o) {
- continue;
- }
- entry = g_new0(MTPMonEntry, 1);
- entry->handle = o->handle;
- entry->event = EVT_OBJ_INFO_CHANGED;
- trace_usb_mtp_inotify_event(s->dev.addr, o->path,
- event->mask, "Obj Modified");
- break;
-
- case IN_IGNORED:
- trace_usb_mtp_inotify_event(s->dev.addr, parent->path,
- event->mask, "Obj parent dir ignored");
- break;
-
- default:
- fprintf(stderr, "usb-mtp: failed to parse inotify event\n");
- continue;
- }
-
- if (entry) {
- QTAILQ_INSERT_HEAD(&s->events, entry, next);
- }
+ o = usb_mtp_object_lookup_name(parent, name, -1);
+ if (!o) {
+ return;
}
- }
-}
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = o->handle;
+ entry->event = EVT_OBJ_REMOVED;
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Deleted");
+ usb_mtp_object_free(s, o);
+ break;
-static int usb_mtp_inotify_init(MTPState *s)
-{
- int fd;
+ case QFILE_MONITOR_EVENT_MODIFIED:
+ o = usb_mtp_object_lookup_name(parent, name, -1);
+ if (!o) {
+ return;
+ }
+ entry = g_new0(MTPMonEntry, 1);
+ entry->handle = o->handle;
+ entry->event = EVT_OBJ_INFO_CHANGED;
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Modified");
+ break;
- fd = inotify_init1(IN_NONBLOCK);
- if (fd == -1) {
- return 1;
- }
+ case QFILE_MONITOR_EVENT_IGNORED:
+ trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path,
+ "Obj parent dir ignored");
+ break;
- QTAILQ_INIT(&s->events);
- s->inotifyfd = fd;
+ case QFILE_MONITOR_EVENT_ATTRIBUTES:
+ break;
- qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
+ default:
+ g_assert_not_reached();
+ }
- return 0;
+ if (entry) {
+ QTAILQ_INSERT_HEAD(&s->events, entry, next);
+ }
}
-static void usb_mtp_inotify_cleanup(MTPState *s)
+static void usb_mtp_file_monitor_cleanup(MTPState *s)
{
MTPMonEntry *e, *p;
- if (!s->inotifyfd) {
- return;
- }
-
- qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
- close(s->inotifyfd);
-
QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
QTAILQ_REMOVE(&s->events, e, next);
g_free(e);
}
-}
-static int usb_mtp_add_watch(int inotifyfd, char *path)
-{
- uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY |
- IN_ISDIR;
-
- return inotify_add_watch(inotifyfd, path, mask);
+ qemu_file_monitor_free(s->file_monitor);
+ s->file_monitor = NULL;
}
-#endif
+
static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
{
struct dirent *entry;
DIR *dir;
int fd;
+ Error *err = NULL;
if (o->have_children) {
return;
@@ -680,16 +623,21 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
close(fd);
return;
}
-#ifdef CONFIG_INOTIFY1
- int watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
- if (watchfd == -1) {
- fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
- } else {
- trace_usb_mtp_inotify_event(s->dev.addr, o->path,
- 0, "Watch Added");
- o->watchfd = watchfd;
+
+ if (s->file_monitor) {
+ int id = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
+ file_monitor_event, s, &err);
+ if (id == -1) {
+ error_report("usb-mtp: failed to add watch for %s: %s", o->path,
+ error_get_pretty(err));
+ error_free(err);
+ } else {
+ trace_usb_mtp_file_monitor_event(s->dev.addr, o->path,
+ "Watch Added");
+ o->watchid = id;
+ }
}
-#endif
+
while ((entry = readdir(dir)) != NULL) {
usb_mtp_add_child(s, o, entry->d_name);
}
@@ -1197,13 +1145,11 @@ enum {
/* Assumes that children, if any, have been already freed */
static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
{
-#ifndef CONFIG_INOTIFY1
assert(o->nchildren == 0);
QTAILQ_REMOVE(&s->objects, o, next);
g_free(o->name);
g_free(o->path);
g_free(o);
-#endif
}
static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans)
@@ -1302,6 +1248,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
MTPData *data_in = NULL;
MTPObject *o = NULL;
uint32_t nres = 0, res0 = 0;
+ Error *err = NULL;
/* sanity checks */
if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
@@ -1329,19 +1276,21 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
trace_usb_mtp_op_open_session(s->dev.addr);
s->session = c->argv[0];
usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
-#ifdef CONFIG_INOTIFY1
- if (usb_mtp_inotify_init(s)) {
- fprintf(stderr, "usb-mtp: file monitoring init failed\n");
+
+ s->file_monitor = qemu_file_monitor_new(&err);
+ if (err) {
+ error_report("usb-mtp: file monitoring init failed: %s",
+ error_get_pretty(err));
+ error_free(err);
+ } else {
+ QTAILQ_INIT(&s->events);
}
-#endif
break;
case CMD_CLOSE_SESSION:
trace_usb_mtp_op_close_session(s->dev.addr);
s->session = 0;
s->next_handle = 0;
-#ifdef CONFIG_INOTIFY1
- usb_mtp_inotify_cleanup(s);
-#endif
+ usb_mtp_file_monitor_cleanup(s);
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
assert(QTAILQ_EMPTY(&s->objects));
break;
@@ -1554,9 +1503,7 @@ static void usb_mtp_handle_reset(USBDevice *dev)
trace_usb_mtp_reset(s->dev.addr);
-#ifdef CONFIG_INOTIFY1
- usb_mtp_inotify_cleanup(s);
-#endif
+ usb_mtp_file_monitor_cleanup(s);
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
s->session = 0;
usb_mtp_data_free(s->data_in);
@@ -2027,7 +1974,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
}
break;
case EP_EVENT:
-#ifdef CONFIG_INOTIFY1
if (!QTAILQ_EMPTY(&s->events)) {
struct MTPMonEntry *e = QTAILQ_LAST(&s->events);
uint32_t handle;
@@ -2051,7 +1997,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
g_free(e);
return;
}
-#endif
p->status = USB_RET_NAK;
return;
default:
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 2c18770ca5..99b1e8b8ce 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -237,7 +237,7 @@ usb_mtp_op_unknown(int dev, uint32_t code) "dev %d, command code 0x%x"
usb_mtp_object_alloc(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
usb_mtp_object_free(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
usb_mtp_add_child(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
-usb_mtp_inotify_event(int dev, const char *path, uint32_t mask, const char *s) "dev %d, path %s mask 0x%x event %s"
+usb_mtp_file_monitor_event(int dev, const char *path, const char *s) "dev %d, path %s event %s"
# hw/usb/host-libusb.c
usb_host_open_started(int bus, int addr) "dev %d:%d"
diff --git a/include/authz/base.h b/include/authz/base.h
new file mode 100644
index 0000000000..77dcd54c4c
--- /dev/null
+++ b/include/authz/base.h
@@ -0,0 +1,112 @@
+/*
+ * QEMU authorization framework base class
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_BASE_H__
+#define QAUTHZ_BASE_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+
+#define TYPE_QAUTHZ "authz"
+
+#define QAUTHZ_CLASS(klass) \
+ OBJECT_CLASS_CHECK(QAuthZClass, (klass), \
+ TYPE_QAUTHZ)
+#define QAUTHZ_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(QAuthZClass, (obj), \
+ TYPE_QAUTHZ)
+#define QAUTHZ(obj) \
+ INTERFACE_CHECK(QAuthZ, (obj), \
+ TYPE_QAUTHZ)
+
+typedef struct QAuthZ QAuthZ;
+typedef struct QAuthZClass QAuthZClass;
+
+/**
+ * QAuthZ:
+ *
+ * The QAuthZ class defines an API contract to be used
+ * for providing an authorization driver for services
+ * with user identities.
+ */
+
+struct QAuthZ {
+ Object parent_obj;
+};
+
+
+struct QAuthZClass {
+ ObjectClass parent_class;
+
+ bool (*is_allowed)(QAuthZ *authz,
+ const char *identity,
+ Error **errp);
+};
+
+
+/**
+ * qauthz_is_allowed:
+ * @authz: the authorization object
+ * @identity: the user identity to authorize
+ * @errp: pointer to a NULL initialized error object
+ *
+ * Check if a user @identity is authorized. If an error
+ * occurs this method will return false to indicate
+ * denial, as well as setting @errp to contain the details.
+ * Callers are recommended to treat the denial and error
+ * scenarios identically. Specifically the error info in
+ * @errp should never be fed back to the user being
+ * authorized, it is merely for benefit of administrator
+ * debugging.
+ *
+ * Returns: true if @identity is authorized, false if denied or if
+ * an error occurred.
+ */
+bool qauthz_is_allowed(QAuthZ *authz,
+ const char *identity,
+ Error **errp);
+
+
+/**
+ * qauthz_is_allowed_by_id:
+ * @authzid: ID of the authorization object
+ * @identity: the user identity to authorize
+ * @errp: pointer to a NULL initialized error object
+ *
+ * Check if a user @identity is authorized. If an error
+ * occurs this method will return false to indicate
+ * denial, as well as setting @errp to contain the details.
+ * Callers are recommended to treat the denial and error
+ * scenarios identically. Specifically the error info in
+ * @errp should never be fed back to the user being
+ * authorized, it is merely for benefit of administrator
+ * debugging.
+ *
+ * Returns: true if @identity is authorized, false if denied or if
+ * an error occurred.
+ */
+bool qauthz_is_allowed_by_id(const char *authzid,
+ const char *identity,
+ Error **errp);
+
+#endif /* QAUTHZ_BASE_H__ */
+
diff --git a/include/authz/list.h b/include/authz/list.h
new file mode 100644
index 0000000000..a7225a747c
--- /dev/null
+++ b/include/authz/list.h
@@ -0,0 +1,106 @@
+/*
+ * QEMU list authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_LIST_H__
+#define QAUTHZ_LIST_H__
+
+#include "authz/base.h"
+#include "qapi/qapi-types-authz.h"
+
+#define TYPE_QAUTHZ_LIST "authz-list"
+
+#define QAUTHZ_LIST_CLASS(klass) \
+ OBJECT_CLASS_CHECK(QAuthZListClass, (klass), \
+ TYPE_QAUTHZ_LIST)
+#define QAUTHZ_LIST_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(QAuthZListClass, (obj), \
+ TYPE_QAUTHZ_LIST)
+#define QAUTHZ_LIST(obj) \
+ INTERFACE_CHECK(QAuthZList, (obj), \
+ TYPE_QAUTHZ_LIST)
+
+typedef struct QAuthZList QAuthZList;
+typedef struct QAuthZListClass QAuthZListClass;
+
+
+/**
+ * QAuthZList:
+ *
+ * This authorization driver provides a list mechanism
+ * for granting access by matching user names against a
+ * list of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ * {
+ * "execute": "object-add",
+ * "arguments": {
+ * "qom-type": "authz-list",
+ * "id": "authz0",
+ * "props": {
+ * "rules": [
+ * { "match": "fred", "policy": "allow", "format": "exact" },
+ * { "match": "bob", "policy": "allow", "format": "exact" },
+ * { "match": "danb", "policy": "deny", "format": "exact" },
+ * { "match": "dan*", "policy": "allow", "format": "glob" }
+ * ],
+ * "policy": "deny"
+ * }
+ * }
+ * }
+ *
+ */
+struct QAuthZList {
+ QAuthZ parent_obj;
+
+ QAuthZListPolicy policy;
+ QAuthZListRuleList *rules;
+};
+
+
+struct QAuthZListClass {
+ QAuthZClass parent_class;
+};
+
+
+QAuthZList *qauthz_list_new(const char *id,
+ QAuthZListPolicy policy,
+ Error **errp);
+
+ssize_t qauthz_list_append_rule(QAuthZList *auth,
+ const char *match,
+ QAuthZListPolicy policy,
+ QAuthZListFormat format,
+ Error **errp);
+
+ssize_t qauthz_list_insert_rule(QAuthZList *auth,
+ const char *match,
+ QAuthZListPolicy policy,
+ QAuthZListFormat format,
+ size_t index,
+ Error **errp);
+
+ssize_t qauthz_list_delete_rule(QAuthZList *auth,
+ const char *match);
+
+
+#endif /* QAUTHZ_LIST_H__ */
+
diff --git a/include/authz/listfile.h b/include/authz/listfile.h
new file mode 100644
index 0000000000..bcc8d80743
--- /dev/null
+++ b/include/authz/listfile.h
@@ -0,0 +1,111 @@
+/*
+ * QEMU list file authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_LIST_FILE_H__
+#define QAUTHZ_LIST_FILE_H__
+
+#include "authz/list.h"
+#include "qapi/qapi-types-authz.h"
+#include "qemu/filemonitor.h"
+
+#define TYPE_QAUTHZ_LIST_FILE "authz-list-file"
+
+#define QAUTHZ_LIST_FILE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(QAuthZListFileClass, (klass), \
+ TYPE_QAUTHZ_LIST_FILE)
+#define QAUTHZ_LIST_FILE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(QAuthZListFileClass, (obj), \
+ TYPE_QAUTHZ_LIST_FILE)
+#define QAUTHZ_LIST_FILE(obj) \
+ INTERFACE_CHECK(QAuthZListFile, (obj), \
+ TYPE_QAUTHZ_LIST_FILE)
+
+typedef struct QAuthZListFile QAuthZListFile;
+typedef struct QAuthZListFileClass QAuthZListFileClass;
+
+
+/**
+ * QAuthZListFile:
+ *
+ * This authorization driver provides a file mechanism
+ * for granting access by matching user names against a
+ * file of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ * {
+ * "execute": "object-add",
+ * "arguments": {
+ * "qom-type": "authz-list-file",
+ * "id": "authz0",
+ * "props": {
+ * "filename": "/etc/qemu/myvm-vnc.acl",
+ * "refresh": true
+ * }
+ * }
+ * }
+ *
+ * If 'refresh' is 'yes', inotify is used to monitor for changes
+ * to the file and auto-reload the rules.
+ *
+ * The myvm-vnc.acl file should contain the parameters for
+ * the QAuthZList object in JSON format:
+ *
+ * {
+ * "rules": [
+ * { "match": "fred", "policy": "allow", "format": "exact" },
+ * { "match": "bob", "policy": "allow", "format": "exact" },
+ * { "match": "danb", "policy": "deny", "format": "exact" },
+ * { "match": "dan*", "policy": "allow", "format": "glob" }
+ * ],
+ * "policy": "deny"
+ * }
+ *
+ * The object can be created on the command line using
+ *
+ * -object authz-list-file,id=authz0,\
+ * filename=/etc/qemu/myvm-vnc.acl,refresh=yes
+ *
+ */
+struct QAuthZListFile {
+ QAuthZ parent_obj;
+
+ QAuthZ *list;
+ char *filename;
+ bool refresh;
+ QFileMonitor *file_monitor;
+ int file_watch;
+};
+
+
+struct QAuthZListFileClass {
+ QAuthZClass parent_class;
+};
+
+
+QAuthZListFile *qauthz_list_file_new(const char *id,
+ const char *filename,
+ bool refresh,
+ Error **errp);
+
+
+#endif /* QAUTHZ_LIST_FILE_H__ */
+
diff --git a/include/authz/pamacct.h b/include/authz/pamacct.h
new file mode 100644
index 0000000000..6e3046e528
--- /dev/null
+++ b/include/authz/pamacct.h
@@ -0,0 +1,100 @@
+/*
+ * QEMU PAM authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_PAM_H__
+#define QAUTHZ_PAM_H__
+
+#include "authz/base.h"
+
+
+#define TYPE_QAUTHZ_PAM "authz-pam"
+
+#define QAUTHZ_PAM_CLASS(klass) \
+ OBJECT_CLASS_CHECK(QAuthZPAMClass, (klass), \
+ TYPE_QAUTHZ_PAM)
+#define QAUTHZ_PAM_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(QAuthZPAMClass, (obj), \
+ TYPE_QAUTHZ_PAM)
+#define QAUTHZ_PAM(obj) \
+ INTERFACE_CHECK(QAuthZPAM, (obj), \
+ TYPE_QAUTHZ_PAM)
+
+typedef struct QAuthZPAM QAuthZPAM;
+typedef struct QAuthZPAMClass QAuthZPAMClass;
+
+
+/**
+ * QAuthZPAM:
+ *
+ * This authorization driver provides a PAM mechanism
+ * for granting access by matching user names against a
+ * list of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ * {
+ * "execute": "object-add",
+ * "arguments": {
+ * "qom-type": "authz-pam",
+ * "id": "authz0",
+ * "parameters": {
+ * "service": "qemu-vnc-tls"
+ * }
+ * }
+ * }
+ *
+ * The driver only uses the PAM "account" verification
+ * subsystem. The above config would require a config
+ * file /etc/pam.d/qemu-vnc-tls. For a simple file
+ * lookup it would contain
+ *
+ * account requisite pam_listfile.so item=user sense=allow \
+ * file=/etc/qemu/vnc.allow
+ *
+ * The external file would then contain a list of usernames.
+ * If x509 cert was being used as the username, a suitable
+ * entry would match the distinguish name:
+ *
+ * CN=laptop.berrange.com,O=Berrange Home,L=London,ST=London,C=GB
+ *
+ * On the command line it can be created using
+ *
+ * -object authz-pam,id=authz0,service=qemu-vnc-tls
+ *
+ */
+struct QAuthZPAM {
+ QAuthZ parent_obj;
+
+ char *service;
+};
+
+
+struct QAuthZPAMClass {
+ QAuthZClass parent_class;
+};
+
+
+QAuthZPAM *qauthz_pam_new(const char *id,
+ const char *service,
+ Error **errp);
+
+
+#endif /* QAUTHZ_PAM_H__ */
diff --git a/include/authz/simple.h b/include/authz/simple.h
new file mode 100644
index 0000000000..ef13958269
--- /dev/null
+++ b/include/authz/simple.h
@@ -0,0 +1,84 @@
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_SIMPLE_H__
+#define QAUTHZ_SIMPLE_H__
+
+#include "authz/base.h"
+
+#define TYPE_QAUTHZ_SIMPLE "authz-simple"
+
+#define QAUTHZ_SIMPLE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(QAuthZSimpleClass, (klass), \
+ TYPE_QAUTHZ_SIMPLE)
+#define QAUTHZ_SIMPLE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(QAuthZSimpleClass, (obj), \
+ TYPE_QAUTHZ_SIMPLE)
+#define QAUTHZ_SIMPLE(obj) \
+ INTERFACE_CHECK(QAuthZSimple, (obj), \
+ TYPE_QAUTHZ_SIMPLE)
+
+typedef struct QAuthZSimple QAuthZSimple;
+typedef struct QAuthZSimpleClass QAuthZSimpleClass;
+
+
+/**
+ * QAuthZSimple:
+ *
+ * This authorization driver provides a simple mechanism
+ * for granting access based on an exact matched username.
+ *
+ * To create an instance of this class via QMP:
+ *
+ * {
+ * "execute": "object-add",
+ * "arguments": {
+ * "qom-type": "authz-simple",
+ * "id": "authz0",
+ * "props": {
+ * "identity": "fred"
+ * }
+ * }
+ * }
+ *
+ * Or via the command line
+ *
+ * -object authz-simple,id=authz0,identity=fred
+ *
+ */
+struct QAuthZSimple {
+ QAuthZ parent_obj;
+
+ char *identity;
+};
+
+
+struct QAuthZSimpleClass {
+ QAuthZClass parent_class;
+};
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+ const char *identity,
+ Error **errp);
+
+
+#endif /* QAUTHZ_SIMPLE_H__ */
+
diff --git a/include/qemu/acl.h b/include/qemu/acl.h
deleted file mode 100644
index 73d2a71c8d..0000000000
--- a/include/qemu/acl.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * QEMU access control list management
- *
- * Copyright (C) 2009 Red Hat, Inc
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#ifndef QEMU_ACL_H
-#define QEMU_ACL_H
-
-#include "qemu/queue.h"
-
-typedef struct qemu_acl_entry qemu_acl_entry;
-typedef struct qemu_acl qemu_acl;
-
-struct qemu_acl_entry {
- char *match;
- int deny;
-
- QTAILQ_ENTRY(qemu_acl_entry) next;
-};
-
-struct qemu_acl {
- char *aclname;
- unsigned int nentries;
- QTAILQ_HEAD(,qemu_acl_entry) entries;
- int defaultDeny;
-};
-
-qemu_acl *qemu_acl_init(const char *aclname);
-
-qemu_acl *qemu_acl_find(const char *aclname);
-
-int qemu_acl_party_is_allowed(qemu_acl *acl,
- const char *party);
-
-void qemu_acl_reset(qemu_acl *acl);
-
-int qemu_acl_append(qemu_acl *acl,
- int deny,
- const char *match);
-int qemu_acl_insert(qemu_acl *acl,
- int deny,
- const char *match,
- int index);
-int qemu_acl_remove(qemu_acl *acl,
- const char *match);
-
-#endif /* QEMU_ACL_H */
diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
new file mode 100644
index 0000000000..cd031832ed
--- /dev/null
+++ b/include/qemu/filemonitor.h
@@ -0,0 +1,128 @@
+/*
+ * QEMU file monitor helper
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QEMU_FILE_MONITOR_H
+#define QEMU_FILE_MONITOR_H
+
+#include "qemu-common.h"
+
+
+typedef struct QFileMonitor QFileMonitor;
+
+typedef enum {
+ /* File has been created in a dir */
+ QFILE_MONITOR_EVENT_CREATED,
+ /* File has been modified in a dir */
+ QFILE_MONITOR_EVENT_MODIFIED,
+ /* File has been deleted in a dir */
+ QFILE_MONITOR_EVENT_DELETED,
+ /* File has attributes changed */
+ QFILE_MONITOR_EVENT_ATTRIBUTES,
+ /* Dir is no longer being monitored (due to deletion) */
+ QFILE_MONITOR_EVENT_IGNORED,
+} QFileMonitorEvent;
+
+
+/**
+ * QFileMonitorHandler:
+ * @id: id from qemu_file_monitor_add_watch()
+ * @event: the file change that occurred
+ * @filename: the name of the file affected
+ * @opaque: opaque data provided to qemu_file_monitor_add_watch()
+ *
+ * Invoked whenever a file changes. If @event is
+ * QFILE_MONITOR_EVENT_IGNORED, @filename will be
+ * empty.
+ *
+ */
+typedef void (*QFileMonitorHandler)(int id,
+ QFileMonitorEvent event,
+ const char *filename,
+ void *opaque);
+
+/**
+ * qemu_file_monitor_new:
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Create a handle for a file monitoring object.
+ *
+ * This object does locking internally to enable it to be
+ * safe to use from multiple threads
+ *
+ * If the platform does not support file monitoring, an
+ * error will be reported. Likewise if file monitoring
+ * is supported, but cannot be initialized
+ *
+ * Currently this is implemented on Linux platforms with
+ * the inotify subsystem.
+ *
+ * Returns: the new monitoring object, or NULL on error
+ */
+QFileMonitor *qemu_file_monitor_new(Error **errp);
+
+/**
+ * qemu_file_monitor_free:
+ * @mon: the file monitor context
+ *
+ * Free resources associated with the file monitor,
+ * including any currently registered watches.
+ */
+void qemu_file_monitor_free(QFileMonitor *mon);
+
+/**
+ * qemu_file_monitor_add_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to watch
+ * @filename: optional filename to filter on
+ * @cb: the function to invoke when @dirpath has changes
+ * @opaque: data to pass to @cb
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Register to receive notifications of changes
+ * in the directory @dirpath. All files in the
+ * directory will be monitored. If the caller is
+ * only interested in one specific file, @filename
+ * can be used to filter events.
+ *
+ * Returns: a positive integer watch ID, or -1 on error
+ */
+int qemu_file_monitor_add_watch(QFileMonitor *mon,
+ const char *dirpath,
+ const char *filename,
+ QFileMonitorHandler cb,
+ void *opaque,
+ Error **errp);
+
+/**
+ * qemu_file_monitor_remove_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to unwatch
+ * @id: id of the watch to remove
+ *
+ * Removes the file monitoring watch @id, associated
+ * with the directory @dirpath. This must never be
+ * called from a QFileMonitorHandler callback, or a
+ * deadlock will result.
+ */
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+ const char *dirpath,
+ int id);
+
+#endif /* QEMU_FILE_MONITOR_H */
diff --git a/monitor.c b/monitor.c
index 33ccbf3957..defa129319 100644
--- a/monitor.c
+++ b/monitor.c
@@ -51,7 +51,8 @@
#include "sysemu/balloon.h"
#include "qemu/timer.h"
#include "sysemu/hw_accel.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
+#include "qapi/util.h"
#include "sysemu/tpm.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qerror.h"
@@ -2016,93 +2017,148 @@ static void hmp_wavcapture(Monitor *mon, const QDict *qdict)
QLIST_INSERT_HEAD (&capture_head, s, entries);
}
-static qemu_acl *find_acl(Monitor *mon, const char *name)
+static QAuthZList *find_auth(Monitor *mon, const char *name)
{
- qemu_acl *acl = qemu_acl_find(name);
+ Object *obj;
+ Object *container;
- if (!acl) {
+ container = object_get_objects_root();
+ obj = object_resolve_path_component(container, name);
+ if (!obj) {
monitor_printf(mon, "acl: unknown list '%s'\n", name);
+ return NULL;
}
- return acl;
+
+ return QAUTHZ_LIST(obj);
}
static void hmp_acl_show(Monitor *mon, const QDict *qdict)
{
const char *aclname = qdict_get_str(qdict, "aclname");
- qemu_acl *acl = find_acl(mon, aclname);
- qemu_acl_entry *entry;
- int i = 0;
-
- if (acl) {
- monitor_printf(mon, "policy: %s\n",
- acl->defaultDeny ? "deny" : "allow");
- QTAILQ_FOREACH(entry, &acl->entries, next) {
- i++;
- monitor_printf(mon, "%d: %s %s\n", i,
- entry->deny ? "deny" : "allow", entry->match);
- }
+ QAuthZList *auth = find_auth(mon, aclname);
+ QAuthZListRuleList *rules;
+ size_t i = 0;
+
+ if (!auth) {
+ return;
+ }
+
+ monitor_printf(mon, "policy: %s\n",
+ QAuthZListPolicy_str(auth->policy));
+
+ rules = auth->rules;
+ while (rules) {
+ QAuthZListRule *rule = rules->value;
+ i++;
+ monitor_printf(mon, "%zu: %s %s\n", i,
+ QAuthZListPolicy_str(rule->policy),
+ rule->match);
+ rules = rules->next;
}
}
static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
{
const char *aclname = qdict_get_str(qdict, "aclname");
- qemu_acl *acl = find_acl(mon, aclname);
+ QAuthZList *auth = find_auth(mon, aclname);
- if (acl) {
- qemu_acl_reset(acl);
- monitor_printf(mon, "acl: removed all rules\n");
+ if (!auth) {
+ return;
}
+
+ auth->policy = QAUTHZ_LIST_POLICY_DENY;
+ qapi_free_QAuthZListRuleList(auth->rules);
+ auth->rules = NULL;
+ monitor_printf(mon, "acl: removed all rules\n");
}
static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
{
const char *aclname = qdict_get_str(qdict, "aclname");
const char *policy = qdict_get_str(qdict, "policy");
- qemu_acl *acl = find_acl(mon, aclname);
+ QAuthZList *auth = find_auth(mon, aclname);
+ int val;
+ Error *err = NULL;
- if (acl) {
- if (strcmp(policy, "allow") == 0) {
- acl->defaultDeny = 0;
+ if (!auth) {
+ return;
+ }
+
+ val = qapi_enum_parse(&QAuthZListPolicy_lookup,
+ policy,
+ QAUTHZ_LIST_POLICY_DENY,
+ &err);
+ if (err) {
+ error_free(err);
+ monitor_printf(mon, "acl: unknown policy '%s', "
+ "expected 'deny' or 'allow'\n", policy);
+ } else {
+ auth->policy = val;
+ if (auth->policy == QAUTHZ_LIST_POLICY_ALLOW) {
monitor_printf(mon, "acl: policy set to 'allow'\n");
- } else if (strcmp(policy, "deny") == 0) {
- acl->defaultDeny = 1;
- monitor_printf(mon, "acl: policy set to 'deny'\n");
} else {
- monitor_printf(mon, "acl: unknown policy '%s', "
- "expected 'deny' or 'allow'\n", policy);
+ monitor_printf(mon, "acl: policy set to 'deny'\n");
}
}
}
+static QAuthZListFormat hmp_acl_get_format(const char *match)
+{
+ if (strchr(match, '*')) {
+ return QAUTHZ_LIST_FORMAT_GLOB;
+ } else {
+ return QAUTHZ_LIST_FORMAT_EXACT;
+ }
+}
+
static void hmp_acl_add(Monitor *mon, const QDict *qdict)
{
const char *aclname = qdict_get_str(qdict, "aclname");
const char *match = qdict_get_str(qdict, "match");
- const char *policy = qdict_get_str(qdict, "policy");
+ const char *policystr = qdict_get_str(qdict, "policy");
int has_index = qdict_haskey(qdict, "index");
int index = qdict_get_try_int(qdict, "index", -1);
- qemu_acl *acl = find_acl(mon, aclname);
- int deny, ret;
-
- if (acl) {
- if (strcmp(policy, "allow") == 0) {
- deny = 0;
- } else if (strcmp(policy, "deny") == 0) {
- deny = 1;
- } else {
- monitor_printf(mon, "acl: unknown policy '%s', "
- "expected 'deny' or 'allow'\n", policy);
- return;
- }
- if (has_index)
- ret = qemu_acl_insert(acl, deny, match, index);
- else
- ret = qemu_acl_append(acl, deny, match);
- if (ret < 0)
- monitor_printf(mon, "acl: unable to add acl entry\n");
- else
- monitor_printf(mon, "acl: added rule at position %d\n", ret);
+ QAuthZList *auth = find_auth(mon, aclname);
+ Error *err = NULL;
+ QAuthZListPolicy policy;
+ QAuthZListFormat format;
+ size_t i = 0;
+
+ if (!auth) {
+ return;
+ }
+
+ policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
+ policystr,
+ QAUTHZ_LIST_POLICY_DENY,
+ &err);
+ if (err) {
+ error_free(err);
+ monitor_printf(mon, "acl: unknown policy '%s', "
+ "expected 'deny' or 'allow'\n", policystr);
+ return;
+ }
+
+ format = hmp_acl_get_format(match);
+
+ if (has_index && index == 0) {
+ monitor_printf(mon, "acl: unable to add acl entry\n");
+ return;
+ }
+
+ if (has_index) {
+ i = qauthz_list_insert_rule(auth, match, policy,
+ format, index - 1, &err);
+ } else {
+ i = qauthz_list_append_rule(auth, match, policy,
+ format, &err);
+ }
+ if (err) {
+ monitor_printf(mon, "acl: unable to add rule: %s",
+ error_get_pretty(err));
+ error_free(err);
+ } else {
+ monitor_printf(mon, "acl: added rule at position %zu\n", i + 1);
}
}
@@ -2110,15 +2166,18 @@ static void hmp_acl_remove(Monitor *mon, const QDict *qdict)
{
const char *aclname = qdict_get_str(qdict, "aclname");
const char *match = qdict_get_str(qdict, "match");
- qemu_acl *acl = find_acl(mon, aclname);
- int ret;
+ QAuthZList *auth = find_auth(mon, aclname);
+ ssize_t i = 0;
- if (acl) {
- ret = qemu_acl_remove(acl, match);
- if (ret < 0)
- monitor_printf(mon, "acl: no matching acl entry\n");
- else
- monitor_printf(mon, "acl: removed rule at position %d\n", ret);
+ if (!auth) {
+ return;
+ }
+
+ i = qauthz_list_delete_rule(auth, match);
+ if (i >= 0) {
+ monitor_printf(mon, "acl: removed rule at position %zu\n", i + 1);
+ } else {
+ monitor_printf(mon, "acl: no matching acl entry\n");
}
}
diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs
index 87e4df1660..77acca0209 100644
--- a/qapi/Makefile.objs
+++ b/qapi/Makefile.objs
@@ -5,7 +5,7 @@ util-obj-y += opts-visitor.o qapi-clone-visitor.o
util-obj-y += qmp-event.o
util-obj-y += qapi-util.o
-QAPI_COMMON_MODULES = block-core block char common crypto introspect
+QAPI_COMMON_MODULES = authz block-core block char common crypto introspect
QAPI_COMMON_MODULES += job migration misc net rdma rocker run-state
QAPI_COMMON_MODULES += sockets tpm trace transaction ui
QAPI_TARGET_MODULES = target
diff --git a/qapi/authz.json b/qapi/authz.json
new file mode 100644
index 0000000000..1c836a3abd
--- /dev/null
+++ b/qapi/authz.json
@@ -0,0 +1,58 @@
+# -*- Mode: Python -*-
+#
+# QAPI authz definitions
+
+##
+# @QAuthZListPolicy:
+#
+# The authorization policy result
+#
+# @deny: deny access
+# @allow: allow access
+#
+# Since: 4.0
+##
+{ 'enum': 'QAuthZListPolicy',
+ 'prefix': 'QAUTHZ_LIST_POLICY',
+ 'data': ['deny', 'allow']}
+
+##
+# @QAuthZListFormat:
+#
+# The authorization policy match format
+#
+# @exact: an exact string match
+# @glob: string with ? and * shell wildcard support
+#
+# Since: 4.0
+##
+{ 'enum': 'QAuthZListFormat',
+ 'prefix': 'QAUTHZ_LIST_FORMAT',
+ 'data': ['exact', 'glob']}
+
+##
+# @QAuthZListRule:
+#
+# A single authorization rule.
+#
+# @match: a string or glob to match against a user identity
+# @policy: the result to return if @match evaluates to true
+# @format: the format of the @match rule (default 'exact')
+#
+# Since: 4.0
+##
+{ 'struct': 'QAuthZListRule',
+ 'data': {'match': 'str',
+ 'policy': 'QAuthZListPolicy',
+ '*format': 'QAuthZListFormat'}}
+
+##
+# @QAuthZListRuleListHack:
+#
+# Not exposed via QMP; hack to generate QAuthZListRuleList
+# for use internally by the code.
+#
+# Since: 4.0
+##
+{ 'struct': 'QAuthZListRuleListHack',
+ 'data': { 'unused': ['QAuthZListRule'] } }
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index db61bfd688..a34899c626 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -92,6 +92,7 @@
{ 'include': 'rocker.json' }
{ 'include': 'tpm.json' }
{ 'include': 'ui.json' }
+{ 'include': 'authz.json' }
{ 'include': 'migration.json' }
{ 'include': 'transaction.json' }
{ 'include': 'trace.json' }
diff --git a/qemu-options.hx b/qemu-options.hx
index c843126ebd..1cf9aac1fe 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4365,6 +4365,111 @@ e.g to launch a SEV guest
.....
@end example
+
+
+@item -object authz-simple,id=@var{id},identity=@var{string}
+
+Create an authorization object that will control access to network services.
+
+The @option{identity} parameter is identifies the user and its format
+depends on the network service that authorization object is associated
+with. For authorizing based on TLS x509 certificates, the identity must
+be the x509 distinguished name. Note that care must be taken to escape
+any commas in the distinguished name.
+
+An example authorization object to validate a x509 distinguished name
+would look like:
+@example
+ # $QEMU \
+ ...
+ -object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,O=Example Org,,L=London,,ST=London,,C=GB' \
+ ...
+@end example
+
+Note the use of quotes due to the x509 distinguished name containing
+whitespace, and escaping of ','.
+
+@item -object authz-listfile,id=@var{id},filename=@var{path},refresh=@var{yes|no}
+
+Create an authorization object that will control access to network services.
+
+The @option{filename} parameter is the fully qualified path to a file
+containing the access control list rules in JSON format.
+
+An example set of rules that match against SASL usernames might look
+like:
+
+@example
+ @{
+ "rules": [
+ @{ "match": "fred", "policy": "allow", "format": "exact" @},
+ @{ "match": "bob", "policy": "allow", "format": "exact" @},
+ @{ "match": "danb", "policy": "deny", "format": "glob" @},
+ @{ "match": "dan*", "policy": "allow", "format": "exact" @},
+ ],
+ "policy": "deny"
+ @}
+@end example
+
+When checking access the object will iterate over all the rules and
+the first rule to match will have its @option{policy} value returned
+as the result. If no rules match, then the default @option{policy}
+value is returned.
+
+The rules can either be an exact string match, or they can use the
+simple UNIX glob pattern matching to allow wildcards to be used.
+
+If @option{refresh} is set to true the file will be monitored
+and automatically reloaded whenever its content changes.
+
+As with the @code{authz-simple} object, the format of the identity
+strings being matched depends on the network service, but is usually
+a TLS x509 distinguished name, or a SASL username.
+
+An example authorization object to validate a SASL username
+would look like:
+@example
+ # $QEMU \
+ ...
+ -object authz-simple,id=auth0,filename=/etc/qemu/vnc-sasl.acl,refresh=yes
+ ...
+@end example
+
+@item -object authz-pam,id=@var{id},service=@var{string}
+
+Create an authorization object that will control access to network services.
+
+The @option{service} parameter provides the name of a PAM service to use
+for authorization. It requires that a file @code{/etc/pam.d/@var{service}}
+exist to provide the configuration for the @code{account} subsystem.
+
+An example authorization object to validate a TLS x509 distinguished
+name would look like:
+
+@example
+ # $QEMU \
+ ...
+ -object authz-pam,id=auth0,service=qemu-vnc
+ ...
+@end example
+
+There would then be a corresponding config file for PAM at
+@code{/etc/pam.d/qemu-vnc} that contains:
+
+@example
+account requisite pam_listfile.so item=user sense=allow \
+ file=/etc/qemu/vnc.allow
+@end example
+
+Finally the @code{/etc/qemu/vnc.allow} file would contain
+the list of x509 distingished names that are permitted
+access
+
+@example
+CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
+@end example
+
+
@end table
ETEXI
diff --git a/qom/object.c b/qom/object.c
index b8c732063b..05a8567041 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -646,16 +646,20 @@ Object *object_new_with_propv(const char *typename,
goto error;
}
- object_property_add_child(parent, id, obj, &local_err);
- if (local_err) {
- goto error;
+ if (id != NULL) {
+ object_property_add_child(parent, id, obj, &local_err);
+ if (local_err) {
+ goto error;
+ }
}
uc = (UserCreatable *)object_dynamic_cast(obj, TYPE_USER_CREATABLE);
if (uc) {
user_creatable_complete(uc, &local_err);
if (local_err) {
- object_unparent(obj);
+ if (id != NULL) {
+ object_unparent(obj);
+ }
goto error;
}
}
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index db85d1eb75..cb5809934a 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -75,16 +75,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
goto out;
}
- object_property_add_child(object_get_objects_root(),
- id, obj, &local_err);
- if (local_err) {
- goto out;
+ if (id != NULL) {
+ object_property_add_child(object_get_objects_root(),
+ id, obj, &local_err);
+ if (local_err) {
+ goto out;
+ }
}
user_creatable_complete(USER_CREATABLE(obj), &local_err);
if (local_err) {
- object_property_del(object_get_objects_root(),
- id, &error_abort);
+ if (id != NULL) {
+ object_property_del(object_get_objects_root(),
+ id, &error_abort);
+ }
goto out;
}
out:
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 3741f8f6dd..e60488da7d 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -114,7 +114,12 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
endif
check-unit-y += tests/test-timed-average$(EXESUF)
+check-unit-$(CONFIG_INOTIFY1) += tests/test-util-filemonitor$(EXESUF)
check-unit-y += tests/test-util-sockets$(EXESUF)
+check-unit-y += tests/test-authz-simple$(EXESUF)
+check-unit-y += tests/test-authz-list$(EXESUF)
+check-unit-y += tests/test-authz-listfile$(EXESUF)
+check-unit-$(CONFIG_AUTH_PAM) += tests/test-authz-pam$(EXESUF)
check-unit-y += tests/test-io-task$(EXESUF)
check-unit-y += tests/test-io-channel-socket$(EXESUF)
check-unit-y += tests/test-io-channel-file$(EXESUF)
@@ -532,9 +537,10 @@ test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
tests/test-qapi-introspect.o \
$(test-qom-obj-y)
-benchmark-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
-test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
+benchmark-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
+test-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
+test-authz-obj-y = $(test-qom-obj-y) $(authz-obj-y)
test-block-obj-y = $(block-obj-y) $(test-io-obj-y) tests/iothread.o
tests/check-qnum$(EXESUF): tests/check-qnum.o $(test-util-obj-y)
@@ -657,8 +663,14 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
tests/crypto-tls-psk-helpers.o \
$(test-crypto-obj-y)
+tests/test-util-filemonitor$(EXESUF): tests/test-util-filemonitor.o \
+ $(test-util-obj-y)
tests/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
tests/socket-helpers.o $(test-util-obj-y)
+tests/test-authz-simple$(EXESUF): tests/test-authz-simple.o $(test-authz-obj-y)
+tests/test-authz-list$(EXESUF): tests/test-authz-list.o $(test-authz-obj-y)
+tests/test-authz-listfile$(EXESUF): tests/test-authz-listfile.o $(test-authz-obj-y)
+tests/test-authz-pam$(EXESUF): tests/test-authz-pam.o $(test-authz-obj-y)
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
tests/io-channel-helpers.o tests/socket-helpers.o $(test-io-obj-y)
diff --git a/tests/test-authz-list.c b/tests/test-authz-list.c
new file mode 100644
index 0000000000..24347a6ac3
--- /dev/null
+++ b/tests/test-authz-list.c
@@ -0,0 +1,159 @@
+/*
+ * QEMU list file authorization object tests
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "authz/list.h"
+
+static void test_authz_default_deny(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_default_allow(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_ALLOW,
+ &error_abort);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_deny(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_ALLOW,
+ &error_abort);
+
+ qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
+ QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_allow(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort);
+
+ qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+
+static void test_authz_complex(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort);
+
+ qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+ qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+ qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
+ QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+ qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_add_remove(void)
+{
+ QAuthZList *auth = qauthz_list_new("auth0",
+ QAUTHZ_LIST_POLICY_ALLOW,
+ &error_abort);
+
+ g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
+ QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_EXACT,
+ &error_abort),
+ ==, 0);
+ g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
+ QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_EXACT,
+ &error_abort),
+ ==, 1);
+ g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
+ QAUTHZ_LIST_POLICY_DENY,
+ QAUTHZ_LIST_FORMAT_EXACT,
+ &error_abort),
+ ==, 2);
+ g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
+ QAUTHZ_LIST_POLICY_DENY,
+ QAUTHZ_LIST_FORMAT_EXACT,
+ &error_abort),
+ ==, 3);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+ g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
+ ==, 2);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+ g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
+ QAUTHZ_LIST_POLICY_DENY,
+ QAUTHZ_LIST_FORMAT_EXACT,
+ 2,
+ &error_abort),
+ ==, 2);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
+ g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
+ g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
+ g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
+ g_test_add_func("/auth/list/complex", test_authz_complex);
+ g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
+
+ return g_test_run();
+}
diff --git a/tests/test-authz-listfile.c b/tests/test-authz-listfile.c
new file mode 100644
index 0000000000..1e452fef6d
--- /dev/null
+++ b/tests/test-authz-listfile.c
@@ -0,0 +1,195 @@
+/*
+ * QEMU list authorization object tests
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "authz/listfile.h"
+
+static char *workdir;
+
+static gchar *qemu_authz_listfile_test_save(const gchar *name,
+ const gchar *cfg)
+{
+ gchar *path = g_strdup_printf("%s/default-deny.cfg", workdir);
+ GError *gerr = NULL;
+
+ if (!g_file_set_contents(path, cfg, -1, &gerr)) {
+ g_printerr("Unable to save config %s: %s\n",
+ path, gerr->message);
+ g_error_free(gerr);
+ g_free(path);
+ rmdir(workdir);
+ abort();
+ }
+
+ return path;
+}
+
+static void test_authz_default_deny(void)
+{
+ gchar *file = qemu_authz_listfile_test_save(
+ "default-deny.cfg",
+ "{ \"policy\": \"deny\" }");
+ Error *local_err = NULL;
+
+ QAuthZListFile *auth = qauthz_list_file_new("auth0",
+ file, false,
+ &local_err);
+ unlink(file);
+ g_free(file);
+ g_assert(local_err == NULL);
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_default_allow(void)
+{
+ gchar *file = qemu_authz_listfile_test_save(
+ "default-allow.cfg",
+ "{ \"policy\": \"allow\" }");
+ Error *local_err = NULL;
+
+ QAuthZListFile *auth = qauthz_list_file_new("auth0",
+ file, false,
+ &local_err);
+ unlink(file);
+ g_free(file);
+ g_assert(local_err == NULL);
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_deny(void)
+{
+ gchar *file = qemu_authz_listfile_test_save(
+ "explicit-deny.cfg",
+ "{ \"rules\": [ "
+ " { \"match\": \"fred\","
+ " \"policy\": \"deny\","
+ " \"format\": \"exact\" } ],"
+ " \"policy\": \"allow\" }");
+ Error *local_err = NULL;
+
+ QAuthZListFile *auth = qauthz_list_file_new("auth0",
+ file, false,
+ &local_err);
+ unlink(file);
+ g_free(file);
+ g_assert(local_err == NULL);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_allow(void)
+{
+ gchar *file = qemu_authz_listfile_test_save(
+ "explicit-allow.cfg",
+ "{ \"rules\": [ "
+ " { \"match\": \"fred\","
+ " \"policy\": \"allow\","
+ " \"format\": \"exact\" } ],"
+ " \"policy\": \"deny\" }");
+ Error *local_err = NULL;
+
+ QAuthZListFile *auth = qauthz_list_file_new("auth0",
+ file, false,
+ &local_err);
+ unlink(file);
+ g_free(file);
+ g_assert(local_err == NULL);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+
+static void test_authz_complex(void)
+{
+ gchar *file = qemu_authz_listfile_test_save(
+ "complex.cfg",
+ "{ \"rules\": [ "
+ " { \"match\": \"fred\","
+ " \"policy\": \"allow\","
+ " \"format\": \"exact\" },"
+ " { \"match\": \"bob\","
+ " \"policy\": \"allow\","
+ " \"format\": \"exact\" },"
+ " { \"match\": \"dan\","
+ " \"policy\": \"deny\","
+ " \"format\": \"exact\" },"
+ " { \"match\": \"dan*\","
+ " \"policy\": \"allow\","
+ " \"format\": \"glob\" } ],"
+ " \"policy\": \"deny\" }");
+
+ Error *local_err = NULL;
+
+ QAuthZListFile *auth = qauthz_list_file_new("auth0",
+ file, false,
+ &local_err);
+ unlink(file);
+ g_free(file);
+ g_assert(local_err == NULL);
+
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
+ g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+ g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+
+int main(int argc, char **argv)
+{
+ int ret;
+ GError *gerr = NULL;
+
+ g_test_init(&argc, &argv, NULL);
+
+ module_call_init(MODULE_INIT_QOM);
+
+ workdir = g_dir_make_tmp("qemu-test-authz-listfile-XXXXXX",
+ &gerr);
+ if (!workdir) {
+ g_printerr("Unable to create temporary dir: %s\n",
+ gerr->message);
+ g_error_free(gerr);
+ abort();
+ }
+
+ g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
+ g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
+ g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
+ g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
+ g_test_add_func("/auth/list/complex", test_authz_complex);
+
+ ret = g_test_run();
+
+ rmdir(workdir);
+ g_free(workdir);
+
+ return ret;
+}
diff --git a/tests/test-authz-pam.c b/tests/test-authz-pam.c
new file mode 100644
index 0000000000..93d5ac8bbf
--- /dev/null
+++ b/tests/test-authz-pam.c
@@ -0,0 +1,124 @@
+/*
+ * QEMU PAM authorization object tests
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "authz/pamacct.h"
+
+#include <security/pam_appl.h>
+
+static bool failauth;
+
+/*
+ * These two functions are exported by libpam.so.
+ *
+ * By defining them again here, our impls are resolved
+ * by the linker instead of those in libpam.so
+ *
+ * The test suite is thus isolated from the host system
+ * PAM setup, so we can do predictable test scenarios
+ */
+int
+pam_start(const char *service_name, const char *user,
+ const struct pam_conv *pam_conversation,
+ pam_handle_t **pamh)
+{
+ failauth = true;
+ if (!g_str_equal(service_name, "qemu-vnc")) {
+ return PAM_AUTH_ERR;
+ }
+
+ if (g_str_equal(user, "fred")) {
+ failauth = false;
+ }
+
+ return PAM_SUCCESS;
+}
+
+
+int
+pam_acct_mgmt(pam_handle_t *pamh, int flags)
+{
+ if (failauth) {
+ return PAM_AUTH_ERR;
+ }
+
+ return PAM_SUCCESS;
+}
+
+
+static void test_authz_unknown_service(void)
+{
+ Error *local_err = NULL;
+ QAuthZPAM *auth = qauthz_pam_new("auth0",
+ "qemu-does-not-exist",
+ &error_abort);
+
+ g_assert_nonnull(auth);
+
+ g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "fred", &local_err));
+
+ error_free_or_abort(&local_err);
+ object_unparent(OBJECT(auth));
+}
+
+
+static void test_authz_good_user(void)
+{
+ QAuthZPAM *auth = qauthz_pam_new("auth0",
+ "qemu-vnc",
+ &error_abort);
+
+ g_assert_nonnull(auth);
+
+ g_assert_true(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+ object_unparent(OBJECT(auth));
+}
+
+
+static void test_authz_bad_user(void)
+{
+ Error *local_err = NULL;
+ QAuthZPAM *auth = qauthz_pam_new("auth0",
+ "qemu-vnc",
+ &error_abort);
+
+ g_assert_nonnull(auth);
+
+ g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "bob", &local_err));
+
+ error_free_or_abort(&local_err);
+ object_unparent(OBJECT(auth));
+}
+
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_add_func("/auth/pam/unknown-service", test_authz_unknown_service);
+ g_test_add_func("/auth/pam/good-user", test_authz_good_user);
+ g_test_add_func("/auth/pam/bad-user", test_authz_bad_user);
+
+ return g_test_run();
+}
diff --git a/tests/test-authz-simple.c b/tests/test-authz-simple.c
new file mode 100644
index 0000000000..2cf14fb87e
--- /dev/null
+++ b/tests/test-authz-simple.c
@@ -0,0 +1,50 @@
+/*
+ * QEMU simple authorization object testing
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+
+#include "authz/simple.h"
+
+
+static void test_authz_simple(void)
+{
+ QAuthZSimple *authz = qauthz_simple_new("authz0",
+ "cthulu",
+ &error_abort);
+
+ g_assert(!qauthz_is_allowed(QAUTHZ(authz), "cthul", &error_abort));
+ g_assert(qauthz_is_allowed(QAUTHZ(authz), "cthulu", &error_abort));
+ g_assert(!qauthz_is_allowed(QAUTHZ(authz), "cthuluu", &error_abort));
+ g_assert(!qauthz_is_allowed(QAUTHZ(authz), "fred", &error_abort));
+
+ object_unparent(OBJECT(authz));
+}
+
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ module_call_init(MODULE_INIT_QOM);
+
+ g_test_add_func("/authz/simple", test_authz_simple);
+
+ return g_test_run();
+}
diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c
index 6fa9950afb..15212ec276 100644
--- a/tests/test-crypto-tlssession.c
+++ b/tests/test-crypto-tlssession.c
@@ -28,7 +28,7 @@
#include "qom/object_interfaces.h"
#include "qapi/error.h"
#include "qemu/sockets.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
@@ -229,7 +229,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
QCryptoTLSCreds *serverCreds;
QCryptoTLSSession *clientSess = NULL;
QCryptoTLSSession *serverSess = NULL;
- qemu_acl *acl;
+ QAuthZList *auth;
const char * const *wildcards;
int channel[2];
bool clientShake = false;
@@ -285,11 +285,15 @@ static void test_crypto_tls_session_x509(const void *opaque)
SERVER_CERT_DIR);
g_assert(serverCreds != NULL);
- acl = qemu_acl_init("tlssessionacl");
- qemu_acl_reset(acl);
+ auth = qauthz_list_new("tlssessionacl",
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort);
wildcards = data->wildcards;
while (wildcards && *wildcards) {
- qemu_acl_append(acl, 0, *wildcards);
+ qauthz_list_append_rule(auth, *wildcards,
+ QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_GLOB,
+ &error_abort);
wildcards++;
}
@@ -377,6 +381,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
object_unparent(OBJECT(serverCreds));
object_unparent(OBJECT(clientCreds));
+ object_unparent(OBJECT(auth));
qcrypto_tls_session_free(serverSess);
qcrypto_tls_session_free(clientSess);
diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
index 4900c6d433..43b707eba7 100644
--- a/tests/test-io-channel-tls.c
+++ b/tests/test-io-channel-tls.c
@@ -29,8 +29,8 @@
#include "io-channel-helpers.h"
#include "crypto/init.h"
#include "crypto/tlscredsx509.h"
-#include "qemu/acl.h"
#include "qapi/error.h"
+#include "authz/list.h"
#include "qom/object_interfaces.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
@@ -113,7 +113,7 @@ static void test_io_channel_tls(const void *opaque)
QIOChannelTLS *serverChanTLS;
QIOChannelSocket *clientChanSock;
QIOChannelSocket *serverChanSock;
- qemu_acl *acl;
+ QAuthZList *auth;
const char * const *wildcards;
int channel[2];
struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
@@ -161,11 +161,15 @@ static void test_io_channel_tls(const void *opaque)
SERVER_CERT_DIR);
g_assert(serverCreds != NULL);
- acl = qemu_acl_init("channeltlsacl");
- qemu_acl_reset(acl);
+ auth = qauthz_list_new("channeltlsacl",
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort);
wildcards = data->wildcards;
while (wildcards && *wildcards) {
- qemu_acl_append(acl, 0, *wildcards);
+ qauthz_list_append_rule(auth, *wildcards,
+ QAUTHZ_LIST_POLICY_ALLOW,
+ QAUTHZ_LIST_FORMAT_GLOB,
+ &error_abort);
wildcards++;
}
@@ -253,6 +257,8 @@ static void test_io_channel_tls(const void *opaque)
object_unref(OBJECT(serverChanSock));
object_unref(OBJECT(clientChanSock));
+ object_unparent(OBJECT(auth));
+
close(channel[0]);
close(channel[1]);
}
diff --git a/tests/test-util-filemonitor.c b/tests/test-util-filemonitor.c
new file mode 100644
index 0000000000..5d95cea5ee
--- /dev/null
+++ b/tests/test-util-filemonitor.c
@@ -0,0 +1,685 @@
+/*
+ * Tests for util/filemonitor-*.c
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * 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 library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "qemu/filemonitor.h"
+
+#include <utime.h>
+
+enum {
+ QFILE_MONITOR_TEST_OP_CREATE,
+ QFILE_MONITOR_TEST_OP_APPEND,
+ QFILE_MONITOR_TEST_OP_TRUNC,
+ QFILE_MONITOR_TEST_OP_RENAME,
+ QFILE_MONITOR_TEST_OP_TOUCH,
+ QFILE_MONITOR_TEST_OP_UNLINK,
+};
+
+typedef struct {
+ int type;
+ const char *filesrc;
+ const char *filedst;
+} QFileMonitorTestOp;
+
+typedef struct {
+ const char *file;
+} QFileMonitorTestWatch;
+
+typedef struct {
+ gsize nwatches;
+ const QFileMonitorTestWatch *watches;
+
+ gsize nops;
+ const QFileMonitorTestOp *ops;
+} QFileMonitorTestPlan;
+
+typedef struct {
+ int id;
+ QFileMonitorEvent event;
+ char *filename;
+} QFileMonitorTestRecord;
+
+
+typedef struct {
+ QemuMutex lock;
+ GList *records;
+} QFileMonitorTestData;
+
+static QemuMutex evlock;
+static bool evstopping;
+static bool evrunning;
+
+/*
+ * Main function for a background thread that is
+ * running the event loop during the test
+ */
+static void *
+qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
+{
+ qemu_mutex_lock(&evlock);
+
+ while (!evstopping) {
+ qemu_mutex_unlock(&evlock);
+ main_loop_wait(true);
+ qemu_mutex_lock(&evlock);
+ }
+
+ evrunning = false;
+ qemu_mutex_unlock(&evlock);
+ return NULL;
+}
+
+
+/*
+ * File monitor event handler which simply maintains
+ * an ordered list of all events that it receives
+ */
+static void
+qemu_file_monitor_test_handler(int id,
+ QFileMonitorEvent event,
+ const char *filename,
+ void *opaque)
+{
+ QFileMonitorTestData *data = opaque;
+ QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
+
+ rec->id = id;
+ rec->event = event;
+ rec->filename = g_strdup(filename);
+
+ qemu_mutex_lock(&data->lock);
+ data->records = g_list_append(data->records, rec);
+ qemu_mutex_unlock(&data->lock);
+}
+
+
+static void
+qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
+{
+ g_free(rec->filename);
+ g_free(rec);
+}
+
+
+/*
+ * Get the next event record that has been received by
+ * the file monitor event handler. Since events are
+ * emitted in the background thread running the event
+ * loop, we can't assume there is a record available
+ * immediately. Thus we will sleep for upto 5 seconds
+ * to wait for the event to be queued for us.
+ */
+static QFileMonitorTestRecord *
+qemu_file_monitor_test_next_record(QFileMonitorTestData *data)
+{
+ GTimer *timer = g_timer_new();
+ QFileMonitorTestRecord *record = NULL;
+ GList *tmp;
+
+ qemu_mutex_lock(&data->lock);
+ while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
+ qemu_mutex_unlock(&data->lock);
+ usleep(10 * 1000);
+ qemu_mutex_lock(&data->lock);
+ }
+ if (data->records) {
+ record = data->records->data;
+ tmp = data->records;
+ data->records = g_list_remove_link(data->records, tmp);
+ g_list_free(tmp);
+ }
+ qemu_mutex_unlock(&data->lock);
+
+ g_timer_destroy(timer);
+ return record;
+}
+
+
+/*
+ * Check whether the event record we retrieved matches
+ * data we were expecting to see for the event
+ */
+static bool
+qemu_file_monitor_test_expect(QFileMonitorTestData *data,
+ int id,
+ QFileMonitorEvent event,
+ const char *filename)
+{
+ QFileMonitorTestRecord *rec;
+ bool ret = false;
+
+ rec = qemu_file_monitor_test_next_record(data);
+
+ if (!rec) {
+ g_printerr("Missing event watch id %d event %d file %s\n",
+ id, event, filename);
+ return false;
+ }
+
+ if (id != rec->id) {
+ g_printerr("Expected watch id %d but got %d\n", id, rec->id);
+ goto cleanup;
+ }
+
+ if (event != rec->event) {
+ g_printerr("Expected event %d but got %d\n", event, rec->event);
+ goto cleanup;
+ }
+
+ if (!g_str_equal(filename, rec->filename)) {
+ g_printerr("Expected filename %s but got %s\n",
+ filename, rec->filename);
+ goto cleanup;
+ }
+
+ ret = true;
+
+ cleanup:
+ qemu_file_monitor_test_record_free(rec);
+ return ret;
+}
+
+
+static void
+test_file_monitor_events(const void *opaque)
+{
+ const QFileMonitorTestPlan *plan = opaque;
+ Error *local_err = NULL;
+ GError *gerr = NULL;
+ QFileMonitor *mon = qemu_file_monitor_new(&local_err);
+ QemuThread th;
+ GTimer *timer;
+ gchar *dir = NULL;
+ int err = -1;
+ gsize i, j;
+ char *pathsrc = NULL;
+ char *pathdst = NULL;
+ QFileMonitorTestData data;
+
+ qemu_mutex_init(&data.lock);
+ data.records = NULL;
+
+ /*
+ * The file monitor needs the main loop running in
+ * order to receive events from inotify. We must
+ * thus spawn a background thread to run an event
+ * loop impl, while this thread triggers the
+ * actual file operations we're testing
+ */
+ evrunning = 1;
+ evstopping = 0;
+ qemu_thread_create(&th, "event-loop",
+ qemu_file_monitor_test_event_loop, NULL,
+ QEMU_THREAD_JOINABLE);
+
+ if (local_err) {
+ g_printerr("File monitoring not available: %s",
+ error_get_pretty(local_err));
+ error_free(local_err);
+ return;
+ }
+
+ dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
+ &gerr);
+ if (!dir) {
+ g_printerr("Unable to create tmp dir %s",
+ gerr->message);
+ g_error_free(gerr);
+ abort();
+ }
+
+ /*
+ * First register all the directory / file watches
+ * we're interested in seeing events against
+ */
+ for (i = 0; i < plan->nwatches; i++) {
+ int watchid;
+ watchid = qemu_file_monitor_add_watch(mon,
+ dir,
+ plan->watches[i].file,
+ qemu_file_monitor_test_handler,
+ &data,
+ &local_err);
+ if (watchid < 0) {
+ g_printerr("Unable to add watch %s",
+ error_get_pretty(local_err));
+ goto cleanup;
+ }
+ }
+
+
+ /*
+ * Now invoke all the file operations (create,
+ * delete, rename, chmod, etc). These operations
+ * will trigger the various file monitor events
+ */
+ for (i = 0; i < plan->nops; i++) {
+ const QFileMonitorTestOp *op = &(plan->ops[i]);
+ int fd;
+ struct utimbuf ubuf;
+
+ pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
+ if (op->filedst) {
+ pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
+ }
+
+ switch (op->type) {
+ case QFILE_MONITOR_TEST_OP_CREATE:
+ fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
+ if (fd < 0) {
+ g_printerr("Unable to create %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ close(fd);
+ break;
+
+ case QFILE_MONITOR_TEST_OP_APPEND:
+ fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
+ if (fd < 0) {
+ g_printerr("Unable to open %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+
+ if (write(fd, "Hello World", 10) != 10) {
+ g_printerr("Unable to write %s: %s",
+ pathsrc, strerror(errno));
+ close(fd);
+ goto cleanup;
+ }
+ close(fd);
+ break;
+
+ case QFILE_MONITOR_TEST_OP_TRUNC:
+ if (truncate(pathsrc, 4) < 0) {
+ g_printerr("Unable to truncate %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_RENAME:
+ if (rename(pathsrc, pathdst) < 0) {
+ g_printerr("Unable to rename %s to %s: %s",
+ pathsrc, pathdst, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_UNLINK:
+ if (unlink(pathsrc) < 0) {
+ g_printerr("Unable to unlink %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_TOUCH:
+ ubuf.actime = 1024;
+ ubuf.modtime = 1025;
+ if (utime(pathsrc, &ubuf) < 0) {
+ g_printerr("Unable to touch %s: %s",
+ pathsrc, strerror(errno));
+ goto cleanup;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ g_free(pathsrc);
+ g_free(pathdst);
+ pathsrc = pathdst = NULL;
+ }
+
+
+ /*
+ * Finally validate that we have received all the events
+ * we expect to see for the combination of watches and
+ * file operations
+ */
+ for (i = 0; i < plan->nops; i++) {
+ const QFileMonitorTestOp *op = &(plan->ops[i]);
+
+ switch (op->type) {
+ case QFILE_MONITOR_TEST_OP_CREATE:
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filesrc))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_CREATED, op->filesrc))
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_APPEND:
+ case QFILE_MONITOR_TEST_OP_TRUNC:
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filesrc))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_MODIFIED, op->filesrc))
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_RENAME:
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filesrc))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
+ goto cleanup;
+ }
+
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filedst))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_CREATED, op->filedst))
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_TOUCH:
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filesrc))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_ATTRIBUTES, op->filesrc))
+ goto cleanup;
+ }
+ break;
+
+ case QFILE_MONITOR_TEST_OP_UNLINK:
+ for (j = 0; j < plan->nwatches; j++) {
+ if (plan->watches[j].file &&
+ !g_str_equal(plan->watches[j].file, op->filesrc))
+ continue;
+
+ if (!qemu_file_monitor_test_expect(
+ &data, j, QFILE_MONITOR_EVENT_DELETED, op->filesrc))
+ goto cleanup;
+ }
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ err = 0;
+
+ cleanup:
+ g_free(pathsrc);
+ g_free(pathdst);
+
+ qemu_mutex_lock(&evlock);
+ evstopping = 1;
+ timer = g_timer_new();
+ while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
+ qemu_mutex_unlock(&evlock);
+ usleep(10 * 1000);
+ qemu_mutex_lock(&evlock);
+ }
+ qemu_mutex_unlock(&evlock);
+
+ if (g_timer_elapsed(timer, NULL) >= 5) {
+ g_printerr("Event loop failed to quit after 5 seconds\n");
+ }
+ g_timer_destroy(timer);
+
+ for (i = 0; i < plan->nops; i++) {
+ const QFileMonitorTestOp *op = &(plan->ops[i]);
+ pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
+ unlink(pathsrc);
+ g_free(pathsrc);
+ if (op->filedst) {
+ pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
+ unlink(pathdst);
+ g_free(pathdst);
+ }
+ }
+
+ qemu_file_monitor_free(mon);
+ g_list_foreach(data.records,
+ (GFunc)qemu_file_monitor_test_record_free, NULL);
+ g_list_free(data.records);
+ qemu_mutex_destroy(&data.lock);
+ if (dir) {
+ rmdir(dir);
+ }
+ g_free(dir);
+ g_assert(err == 0);
+}
+
+
+/*
+ * Set of structs which define which file name patterns
+ * we're trying to watch against. NULL, means all files
+ * in the directory
+ */
+static const QFileMonitorTestWatch watches_any[] = {
+ { NULL },
+};
+
+static const QFileMonitorTestWatch watches_one[] = {
+ { "one.txt" },
+};
+
+static const QFileMonitorTestWatch watches_two[] = {
+ { "two.txt" },
+};
+
+static const QFileMonitorTestWatch watches_many[] = {
+ { NULL },
+ { "one.txt" },
+ { "two.txt" },
+};
+
+
+/*
+ * Various sets of file operations we're going to
+ * trigger and validate events for
+ */
+static const QFileMonitorTestOp ops_create_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", }
+};
+
+static const QFileMonitorTestOp ops_delete_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_UNLINK,
+ .filesrc = "one.txt", }
+};
+
+static const QFileMonitorTestOp ops_create_many[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "three.txt", }
+};
+
+static const QFileMonitorTestOp ops_rename_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_RENAME,
+ .filesrc = "one.txt", .filedst = "two.txt" }
+};
+
+static const QFileMonitorTestOp ops_rename_many[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "two.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_RENAME,
+ .filesrc = "one.txt", .filedst = "two.txt" }
+};
+
+static const QFileMonitorTestOp ops_append_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_APPEND,
+ .filesrc = "one.txt", },
+};
+
+static const QFileMonitorTestOp ops_trunc_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_TRUNC,
+ .filesrc = "one.txt", },
+};
+
+static const QFileMonitorTestOp ops_touch_one[] = {
+ { .type = QFILE_MONITOR_TEST_OP_CREATE,
+ .filesrc = "one.txt", },
+ { .type = QFILE_MONITOR_TEST_OP_TOUCH,
+ .filesrc = "one.txt", },
+};
+
+
+/*
+ * No we define data sets for the combinatorial
+ * expansion of file watches and operation sets
+ */
+#define PLAN_DATA(o, w) \
+ static const QFileMonitorTestPlan plan_ ## o ## _ ## w = { \
+ .nops = G_N_ELEMENTS(ops_ ##o), \
+ .ops = ops_ ##o, \
+ .nwatches = G_N_ELEMENTS(watches_ ##w), \
+ .watches = watches_ ## w, \
+ }
+
+PLAN_DATA(create_one, any);
+PLAN_DATA(create_one, one);
+PLAN_DATA(create_one, two);
+PLAN_DATA(create_one, many);
+
+PLAN_DATA(delete_one, any);
+PLAN_DATA(delete_one, one);
+PLAN_DATA(delete_one, two);
+PLAN_DATA(delete_one, many);
+
+PLAN_DATA(create_many, any);
+PLAN_DATA(create_many, one);
+PLAN_DATA(create_many, two);
+PLAN_DATA(create_many, many);
+
+PLAN_DATA(rename_one, any);
+PLAN_DATA(rename_one, one);
+PLAN_DATA(rename_one, two);
+PLAN_DATA(rename_one, many);
+
+PLAN_DATA(rename_many, any);
+PLAN_DATA(rename_many, one);
+PLAN_DATA(rename_many, two);
+PLAN_DATA(rename_many, many);
+
+PLAN_DATA(append_one, any);
+PLAN_DATA(append_one, one);
+PLAN_DATA(append_one, two);
+PLAN_DATA(append_one, many);
+
+PLAN_DATA(trunc_one, any);
+PLAN_DATA(trunc_one, one);
+PLAN_DATA(trunc_one, two);
+PLAN_DATA(trunc_one, many);
+
+PLAN_DATA(touch_one, any);
+PLAN_DATA(touch_one, one);
+PLAN_DATA(touch_one, two);
+PLAN_DATA(touch_one, many);
+
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qemu_init_main_loop(&error_abort);
+
+ qemu_mutex_init(&evlock);
+
+ /*
+ * Register test cases for the combinatorial
+ * expansion of file watches and operation sets
+ */
+ #define PLAN_REGISTER(o, w) \
+ g_test_add_data_func("/util/filemonitor/" # o "/" # w, \
+ &plan_ ## o ## _ ## w, test_file_monitor_events)
+
+ PLAN_REGISTER(create_one, any);
+ PLAN_REGISTER(create_one, one);
+ PLAN_REGISTER(create_one, two);
+ PLAN_REGISTER(create_one, many);
+
+ PLAN_REGISTER(delete_one, any);
+ PLAN_REGISTER(delete_one, one);
+ PLAN_REGISTER(delete_one, two);
+ PLAN_REGISTER(delete_one, many);
+
+ PLAN_REGISTER(create_many, any);
+ PLAN_REGISTER(create_many, one);
+ PLAN_REGISTER(create_many, two);
+ PLAN_REGISTER(create_many, many);
+
+ PLAN_REGISTER(rename_one, any);
+ PLAN_REGISTER(rename_one, one);
+ PLAN_REGISTER(rename_one, two);
+ PLAN_REGISTER(rename_one, many);
+
+ PLAN_REGISTER(rename_many, any);
+ PLAN_REGISTER(rename_many, one);
+ PLAN_REGISTER(rename_many, two);
+ PLAN_REGISTER(rename_many, many);
+
+ PLAN_REGISTER(append_one, any);
+ PLAN_REGISTER(append_one, one);
+ PLAN_REGISTER(append_one, two);
+ PLAN_REGISTER(append_one, many);
+
+ PLAN_REGISTER(trunc_one, any);
+ PLAN_REGISTER(trunc_one, one);
+ PLAN_REGISTER(trunc_one, two);
+ PLAN_REGISTER(trunc_one, many);
+
+ PLAN_REGISTER(touch_one, any);
+ PLAN_REGISTER(touch_one, one);
+ PLAN_REGISTER(touch_one, two);
+ PLAN_REGISTER(touch_one, many);
+
+ return g_test_run();
+}
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index 3751a777a4..7b2b09f242 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -24,6 +24,7 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
+#include "authz/base.h"
#include "vnc.h"
#include "trace.h"
@@ -146,13 +147,14 @@ size_t vnc_client_read_sasl(VncState *vs)
static int vnc_auth_sasl_check_access(VncState *vs)
{
const void *val;
- int err;
- int allow;
+ int rv;
+ Error *err = NULL;
+ bool allow;
- err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
- if (err != SASL_OK) {
+ rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
+ if (rv != SASL_OK) {
trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
- sasl_errstring(err, NULL, NULL));
+ sasl_errstring(rv, NULL, NULL));
return -1;
}
if (val == NULL) {
@@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
vs->sasl.username = g_strdup((const char*)val);
trace_vnc_auth_sasl_username(vs, vs->sasl.username);
- if (vs->vd->sasl.acl == NULL) {
+ if (vs->vd->sasl.authzid == NULL) {
trace_vnc_auth_sasl_acl(vs, 1);
return 0;
}
- allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
+ allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
+ vs->sasl.username, &err);
+ if (err) {
+ trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
+ error_get_pretty(err));
+ error_free(err);
+ return -1;
+ }
trace_vnc_auth_sasl_acl(vs, allow);
return allow ? 0 : -1;
diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h
index 2ae224ee3a..fb55fe04ca 100644
--- a/ui/vnc-auth-sasl.h
+++ b/ui/vnc-auth-sasl.h
@@ -30,8 +30,8 @@
typedef struct VncStateSASL VncStateSASL;
typedef struct VncDisplaySASL VncDisplaySASL;
-#include "qemu/acl.h"
#include "qemu/main-loop.h"
+#include "authz/base.h"
struct VncStateSASL {
sasl_conn_t *conn;
@@ -60,7 +60,8 @@ struct VncStateSASL {
};
struct VncDisplaySASL {
- qemu_acl *acl;
+ QAuthZ *authz;
+ char *authzid;
};
void vnc_sasl_client_cleanup(VncState *vs);
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index d99ea362c1..f072e16ace 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -109,7 +109,7 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
tls = qio_channel_tls_new_server(
vs->ioc,
vs->vd->tlscreds,
- vs->vd->tlsaclname,
+ vs->vd->tlsauthzid,
&err);
if (!tls) {
trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 950f1cd2ac..95c9703c72 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -62,7 +62,7 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
tls = qio_channel_tls_new_server(
vs->ioc,
vs->vd->tlscreds,
- vs->vd->tlsaclname,
+ vs->vd->tlsauthzid,
&err);
if (!tls) {
VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
diff --git a/ui/vnc.c b/ui/vnc.c
index 7e0710ed8f..da4a21d4ce 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -33,7 +33,7 @@
#include "qemu/option.h"
#include "qemu/sockets.h"
#include "qemu/timer.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
#include "qemu/config-file.h"
#include "qapi/qapi-emit-events.h"
#include "qapi/qapi-events-ui.h"
@@ -3229,12 +3229,24 @@ static void vnc_display_close(VncDisplay *vd)
object_unparent(OBJECT(vd->tlscreds));
vd->tlscreds = NULL;
}
- g_free(vd->tlsaclname);
- vd->tlsaclname = NULL;
+ if (vd->tlsauthz) {
+ object_unparent(OBJECT(vd->tlsauthz));
+ vd->tlsauthz = NULL;
+ }
+ g_free(vd->tlsauthzid);
+ vd->tlsauthzid = NULL;
if (vd->lock_key_sync) {
qemu_remove_led_event_handler(vd->led);
vd->led = NULL;
}
+#ifdef CONFIG_VNC_SASL
+ if (vd->sasl.authz) {
+ object_unparent(OBJECT(vd->sasl.authz));
+ vd->sasl.authz = NULL;
+ }
+ g_free(vd->sasl.authzid);
+ vd->sasl.authzid = NULL;
+#endif
}
int vnc_display_password(const char *id, const char *password)
@@ -3887,23 +3899,24 @@ void vnc_display_open(const char *id, Error **errp)
if (acl) {
if (strcmp(vd->id, "default") == 0) {
- vd->tlsaclname = g_strdup("vnc.x509dname");
+ vd->tlsauthzid = g_strdup("vnc.x509dname");
} else {
- vd->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vd->id);
+ vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
}
- qemu_acl_init(vd->tlsaclname);
+ vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort));
}
#ifdef CONFIG_VNC_SASL
if (acl && sasl) {
- char *aclname;
-
if (strcmp(vd->id, "default") == 0) {
- aclname = g_strdup("vnc.username");
+ vd->sasl.authzid = g_strdup("vnc.username");
} else {
- aclname = g_strdup_printf("vnc.%s.username", vd->id);
+ vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
}
- vd->sasl.acl = qemu_acl_init(aclname);
- g_free(aclname);
+ vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort));
}
#endif
diff --git a/ui/vnc.h b/ui/vnc.h
index 81daa7a0eb..ee3da08f4a 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -39,6 +39,7 @@
#include "io/channel-socket.h"
#include "io/channel-tls.h"
#include "io/net-listener.h"
+#include "authz/base.h"
#include <zlib.h>
#include "keymaps.h"
@@ -178,7 +179,8 @@ struct VncDisplay
bool lossy;
bool non_adaptive;
QCryptoTLSCreds *tlscreds;
- char *tlsaclname;
+ QAuthZ *tlsauthz;
+ char *tlsauthzid;
#ifdef CONFIG_VNC_SASL
VncDisplaySASL sasl;
#endif
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 0820923c18..0808575e3e 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -20,7 +20,6 @@ util-obj-y += envlist.o path.o module.o
util-obj-y += host-utils.o
util-obj-y += bitmap.o bitops.o hbitmap.o
util-obj-y += fifo8.o
-util-obj-y += acl.o
util-obj-y += cacheinfo.o
util-obj-y += error.o qemu-error.o
util-obj-y += id.o
@@ -50,5 +49,8 @@ util-obj-y += range.o
util-obj-y += stats64.o
util-obj-y += systemd.o
util-obj-y += iova-tree.o
+util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
util-obj-$(CONFIG_OPENGL) += drm.o
+
+stub-obj-y += filemonitor-stub.o
diff --git a/util/acl.c b/util/acl.c
deleted file mode 100644
index c105addadc..0000000000
--- a/util/acl.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * QEMU access control list management
- *
- * Copyright (C) 2009 Red Hat, Inc
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-
-#include "qemu/osdep.h"
-#include "qemu-common.h"
-#include "qemu/acl.h"
-
-#ifdef CONFIG_FNMATCH
-#include <fnmatch.h>
-#endif
-
-
-static unsigned int nacls = 0;
-static qemu_acl **acls = NULL;
-
-
-
-qemu_acl *qemu_acl_find(const char *aclname)
-{
- int i;
- for (i = 0 ; i < nacls ; i++) {
- if (strcmp(acls[i]->aclname, aclname) == 0)
- return acls[i];
- }
-
- return NULL;
-}
-
-qemu_acl *qemu_acl_init(const char *aclname)
-{
- qemu_acl *acl;
-
- acl = qemu_acl_find(aclname);
- if (acl)
- return acl;
-
- acl = g_malloc(sizeof(*acl));
- acl->aclname = g_strdup(aclname);
- /* Deny by default, so there is no window of "open
- * access" between QEMU starting, and the user setting
- * up ACLs in the monitor */
- acl->defaultDeny = 1;
-
- acl->nentries = 0;
- QTAILQ_INIT(&acl->entries);
-
- acls = g_realloc(acls, sizeof(*acls) * (nacls +1));
- acls[nacls] = acl;
- nacls++;
-
- return acl;
-}
-
-int qemu_acl_party_is_allowed(qemu_acl *acl,
- const char *party)
-{
- qemu_acl_entry *entry;
-
- QTAILQ_FOREACH(entry, &acl->entries, next) {
-#ifdef CONFIG_FNMATCH
- if (fnmatch(entry->match, party, 0) == 0)
- return entry->deny ? 0 : 1;
-#else
- /* No fnmatch, so fallback to exact string matching
- * instead of allowing wildcards */
- if (strcmp(entry->match, party) == 0)
- return entry->deny ? 0 : 1;
-#endif
- }
-
- return acl->defaultDeny ? 0 : 1;
-}
-
-
-void qemu_acl_reset(qemu_acl *acl)
-{
- qemu_acl_entry *entry, *next_entry;
-
- /* Put back to deny by default, so there is no window
- * of "open access" while the user re-initializes the
- * access control list */
- acl->defaultDeny = 1;
- QTAILQ_FOREACH_SAFE(entry, &acl->entries, next, next_entry) {
- QTAILQ_REMOVE(&acl->entries, entry, next);
- g_free(entry->match);
- g_free(entry);
- }
- acl->nentries = 0;
-}
-
-
-int qemu_acl_append(qemu_acl *acl,
- int deny,
- const char *match)
-{
- qemu_acl_entry *entry;
-
- entry = g_malloc(sizeof(*entry));
- entry->match = g_strdup(match);
- entry->deny = deny;
-
- QTAILQ_INSERT_TAIL(&acl->entries, entry, next);
- acl->nentries++;
-
- return acl->nentries;
-}
-
-
-int qemu_acl_insert(qemu_acl *acl,
- int deny,
- const char *match,
- int index)
-{
- qemu_acl_entry *tmp;
- int i = 0;
-
- if (index <= 0)
- return -1;
- if (index > acl->nentries) {
- return qemu_acl_append(acl, deny, match);
- }
-
- QTAILQ_FOREACH(tmp, &acl->entries, next) {
- i++;
- if (i == index) {
- qemu_acl_entry *entry;
- entry = g_malloc(sizeof(*entry));
- entry->match = g_strdup(match);
- entry->deny = deny;
-
- QTAILQ_INSERT_BEFORE(tmp, entry, next);
- acl->nentries++;
- break;
- }
- }
-
- return i;
-}
-
-int qemu_acl_remove(qemu_acl *acl,
- const char *match)
-{
- qemu_acl_entry *entry;
- int i = 0;
-
- QTAILQ_FOREACH(entry, &acl->entries, next) {
- i++;
- if (strcmp(entry->match, match) == 0) {
- QTAILQ_REMOVE(&acl->entries, entry, next);
- acl->nentries--;
- g_free(entry->match);
- g_free(entry);
- return i;
- }
- }
- return -1;
-}
diff --git a/util/filemonitor-inotify.c b/util/filemonitor-inotify.c
new file mode 100644
index 0000000000..3a72be037f
--- /dev/null
+++ b/util/filemonitor-inotify.c
@@ -0,0 +1,339 @@
+/*
+ * QEMU file monitor Linux inotify impl
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/filemonitor.h"
+#include "qemu/main-loop.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#include <sys/inotify.h>
+
+struct QFileMonitor {
+ int fd;
+
+ QemuMutex lock; /* protects dirs & idmap */
+ GHashTable *dirs; /* dirname => QFileMonitorDir */
+ GHashTable *idmap; /* inotify ID => dirname */
+};
+
+
+typedef struct {
+ int id; /* watch ID */
+ char *filename; /* optional filter */
+ QFileMonitorHandler cb;
+ void *opaque;
+} QFileMonitorWatch;
+
+
+typedef struct {
+ char *path;
+ int id; /* inotify ID */
+ int nextid; /* watch ID counter */
+ GArray *watches; /* QFileMonitorWatch elements */
+} QFileMonitorDir;
+
+
+static void qemu_file_monitor_watch(void *arg)
+{
+ QFileMonitor *mon = arg;
+ char buf[4096]
+ __attribute__ ((aligned(__alignof__(struct inotify_event))));
+ int used = 0;
+ int len;
+
+ qemu_mutex_lock(&mon->lock);
+
+ if (mon->fd == -1) {
+ qemu_mutex_unlock(&mon->lock);
+ return;
+ }
+
+ len = read(mon->fd, buf, sizeof(buf));
+
+ if (len < 0) {
+ if (errno != EAGAIN) {
+ error_report("Failure monitoring inotify FD '%s',"
+ "disabling events", strerror(errno));
+ goto cleanup;
+ }
+
+ /* no more events right now */
+ goto cleanup;
+ }
+
+ /* Loop over all events in the buffer */
+ while (used < len) {
+ struct inotify_event *ev =
+ (struct inotify_event *)(buf + used);
+ const char *name = ev->len ? ev->name : "";
+ QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
+ GINT_TO_POINTER(ev->wd));
+ uint32_t iev = ev->mask &
+ (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
+ IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
+ int qev;
+ gsize i;
+
+ used += sizeof(struct inotify_event) + ev->len;
+
+ if (!dir) {
+ continue;
+ }
+
+ /*
+ * During a rename operation, the old name gets
+ * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
+ * To simplify life for callers, we turn these into
+ * DELETED and CREATED events
+ */
+ switch (iev) {
+ case IN_CREATE:
+ case IN_MOVED_TO:
+ qev = QFILE_MONITOR_EVENT_CREATED;
+ break;
+ case IN_MODIFY:
+ qev = QFILE_MONITOR_EVENT_MODIFIED;
+ break;
+ case IN_DELETE:
+ case IN_MOVED_FROM:
+ qev = QFILE_MONITOR_EVENT_DELETED;
+ break;
+ case IN_ATTRIB:
+ qev = QFILE_MONITOR_EVENT_ATTRIBUTES;
+ break;
+ case IN_IGNORED:
+ qev = QFILE_MONITOR_EVENT_IGNORED;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
+ for (i = 0; i < dir->watches->len; i++) {
+ QFileMonitorWatch *watch = &g_array_index(dir->watches,
+ QFileMonitorWatch,
+ i);
+
+ if (watch->filename == NULL ||
+ (name && g_str_equal(watch->filename, name))) {
+ trace_qemu_file_monitor_dispatch(mon, dir->path, name,
+ qev, watch->cb,
+ watch->opaque, watch->id);
+ watch->cb(watch->id, qev, name, watch->opaque);
+ }
+ }
+ }
+
+ cleanup:
+ qemu_mutex_unlock(&mon->lock);
+}
+
+
+static void
+qemu_file_monitor_dir_free(void *data)
+{
+ QFileMonitorDir *dir = data;
+ gsize i;
+
+ for (i = 0; i < dir->watches->len; i++) {
+ QFileMonitorWatch *watch = &g_array_index(dir->watches,
+ QFileMonitorWatch, i);
+ g_free(watch->filename);
+ }
+ g_array_unref(dir->watches);
+ g_free(dir->path);
+ g_free(dir);
+}
+
+
+QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+ int fd;
+ QFileMonitor *mon;
+
+ fd = inotify_init1(IN_NONBLOCK);
+ if (fd < 0) {
+ error_setg_errno(errp, errno,
+ "Unable to initialize inotify");
+ return NULL;
+ }
+
+ mon = g_new0(QFileMonitor, 1);
+ qemu_mutex_init(&mon->lock);
+ mon->fd = fd;
+
+ mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ qemu_file_monitor_dir_free);
+ mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+ trace_qemu_file_monitor_new(mon, mon->fd);
+
+ return mon;
+}
+
+static gboolean
+qemu_file_monitor_free_idle(void *opaque)
+{
+ QFileMonitor *mon = opaque;
+
+ if (!mon) {
+ return G_SOURCE_REMOVE;
+ }
+
+ qemu_mutex_lock(&mon->lock);
+
+ g_hash_table_unref(mon->idmap);
+ g_hash_table_unref(mon->dirs);
+
+ qemu_mutex_unlock(&mon->lock);
+
+ qemu_mutex_destroy(&mon->lock);
+ g_free(mon);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+qemu_file_monitor_free(QFileMonitor *mon)
+{
+ if (!mon) {
+ return;
+ }
+
+ qemu_mutex_lock(&mon->lock);
+ if (mon->fd != -1) {
+ qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
+ close(mon->fd);
+ mon->fd = -1;
+ }
+ qemu_mutex_unlock(&mon->lock);
+
+ /*
+ * Can't free it yet, because another thread
+ * may be running event loop, so the inotify
+ * callback might be pending. Using an idle
+ * source ensures we'll only free after the
+ * pending callback is done
+ */
+ g_idle_add((GSourceFunc)qemu_file_monitor_free_idle, mon);
+}
+
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+ const char *dirpath,
+ const char *filename,
+ QFileMonitorHandler cb,
+ void *opaque,
+ Error **errp)
+{
+ QFileMonitorDir *dir;
+ QFileMonitorWatch watch;
+ int ret = -1;
+
+ qemu_mutex_lock(&mon->lock);
+ dir = g_hash_table_lookup(mon->dirs, dirpath);
+ if (!dir) {
+ int rv = inotify_add_watch(mon->fd, dirpath,
+ IN_CREATE | IN_DELETE | IN_MODIFY |
+ IN_MOVED_TO | IN_MOVED_FROM | IN_ATTRIB);
+
+ if (rv < 0) {
+ error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
+ goto cleanup;
+ }
+
+ trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
+
+ dir = g_new0(QFileMonitorDir, 1);
+ dir->path = g_strdup(dirpath);
+ dir->id = rv;
+ dir->watches = g_array_new(FALSE, TRUE, sizeof(QFileMonitorWatch));
+
+ g_hash_table_insert(mon->dirs, dir->path, dir);
+ g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
+
+ if (g_hash_table_size(mon->dirs) == 1) {
+ qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
+ }
+ }
+
+ watch.id = dir->nextid++;
+ watch.filename = g_strdup(filename);
+ watch.cb = cb;
+ watch.opaque = opaque;
+
+ g_array_append_val(dir->watches, watch);
+
+ trace_qemu_file_monitor_add_watch(mon, dirpath,
+ filename ? filename : "<none>",
+ cb, opaque, watch.id);
+
+ ret = watch.id;
+
+ cleanup:
+ qemu_mutex_unlock(&mon->lock);
+ return ret;
+}
+
+
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+ const char *dirpath,
+ int id)
+{
+ QFileMonitorDir *dir;
+ gsize i;
+
+ qemu_mutex_lock(&mon->lock);
+
+ trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
+
+ dir = g_hash_table_lookup(mon->dirs, dirpath);
+ if (!dir) {
+ goto cleanup;
+ }
+
+ for (i = 0; i < dir->watches->len; i++) {
+ QFileMonitorWatch *watch = &g_array_index(dir->watches,
+ QFileMonitorWatch, i);
+ if (watch->id == id) {
+ g_free(watch->filename);
+ g_array_remove_index(dir->watches, i);
+ break;
+ }
+ }
+
+ if (dir->watches->len == 0) {
+ inotify_rm_watch(mon->fd, dir->id);
+ trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
+
+ g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
+ g_hash_table_remove(mon->dirs, dir->path);
+
+ if (g_hash_table_size(mon->dirs) == 0) {
+ qemu_set_fd_handler(mon->fd, NULL, NULL, NULL);
+ }
+ }
+
+ cleanup:
+ qemu_mutex_unlock(&mon->lock);
+}
diff --git a/util/filemonitor-stub.c b/util/filemonitor-stub.c
new file mode 100644
index 0000000000..48268b2bb6
--- /dev/null
+++ b/util/filemonitor-stub.c
@@ -0,0 +1,59 @@
+/*
+ * QEMU file monitor stub impl
+ *
+ * Copyright (c) 2018 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/filemonitor.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+
+
+QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+ error_setg(errp, "File monitoring not available on this platform");
+ return NULL;
+}
+
+
+void
+qemu_file_monitor_free(QFileMonitor *mon G_GNUC_UNUSED)
+{
+}
+
+
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon G_GNUC_UNUSED,
+ const char *dirpath G_GNUC_UNUSED,
+ const char *filename G_GNUC_UNUSED,
+ QFileMonitorHandler cb G_GNUC_UNUSED,
+ void *opaque G_GNUC_UNUSED,
+ Error **errp)
+{
+ error_setg(errp, "File monitoring not available on this platform");
+ return -1;
+}
+
+
+void
+qemu_file_monitor_remove_watch(QFileMonitor *mon G_GNUC_UNUSED,
+ const char *dirpath G_GNUC_UNUSED,
+ int id G_GNUC_UNUSED)
+{
+}
diff --git a/util/trace-events b/util/trace-events
index 79569b7fdf..ff19b253e2 100644
--- a/util/trace-events
+++ b/util/trace-events
@@ -21,6 +21,15 @@ buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
buffer_free(const char *buf, size_t len) "%s: capacity %zd"
+# util/filemonitor.c
+qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%u"
+qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int id) "File monitor %p remove watch dir='%s' id=%u"
+qemu_file_monitor_new(void *mon, int fd) "File monitor %p created fd=%d"
+qemu_file_monitor_enable_watch(void *mon, const char *dirpath, int id) "File monitor %p enable watch dir='%s' id=%u"
+qemu_file_monitor_disable_watch(void *mon, const char *dirpath, int id) "Fle monitor %p disable watch dir='%s' id=%u"
+qemu_file_monitor_event(void *mon, const char *dirpath, const char *filename, int mask, unsigned int id) "File monitor %p event dir='%s' file='%s' mask=0x%x id=%u"
+qemu_file_monitor_dispatch(void *mon, const char *dirpath, const char *filename, int ev, void *cb, void *opaque, unsigned int id) "File monitor %p dispatch dir='%s' file='%s' ev=%d cb=%p opaque=%p id=%u"
+
# util/qemu-coroutine.c
qemu_aio_coroutine_enter(void *ctx, void *from, void *to, void *opaque) "ctx %p from %p to %p opaque %p"
qemu_coroutine_yield(void *from, void *to) "from %p to %p"