On 11/30/18 3:06 PM, Daniel P. Berrangé wrote: > Add a QAuthZListFile object type that implements the QAuthZ interface. This > built-in implementation is a proxy around the QAuthZList object type, > initializing it from an external file, and optionally, automatically > reloading it whenever it changes. > > To create an instance of this object via the QMP monitor, the syntax > used would be: > > { > "execute": "object-add", > "arguments": { > "qom-type": "authz-list-file", > "id": "authz0", > "parameters": {
"parameters" -> "props" > "filename": "/etc/qemu/vnc.acl", > "refresh": "yes" I had to use "refresh": yes (value unquoted) to avoid: { "error": { "class": "GenericError", "desc": "Invalid parameter type for 'refresh', expected: boolean" } } > } > } > } > > If "refresh" is "yes", inotify is used to monitor the file, > automatically reloading changes. If an error occurs during reloading, > all authorizations will fail until the file is next successfully > loaded. > > The /etc/qemu/vnc.acl file would contain a JSON representation of a > QAuthZList object > > { > "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" > } > > This sets up an authorization rule that allows 'fred', 'bob' and anyone > whose name starts with 'dan', except for 'danb'. Everyone unmatched is > denied. > > The object can be loaded on the comand line using > > -object authz-list-file,id=authz0,filename=/etc/qemu/vnc.acl,refresh=yes > > Signed-off-by: Daniel P. Berrangé <berra...@redhat.com> > --- > authz/Makefile.objs | 1 + > authz/listfile.c | 283 ++++++++++++++++++++++++++++++++++++ > authz/trace-events | 4 + > include/authz/listfile.h | 111 ++++++++++++++ > qemu-options.hx | 46 ++++++ > tests/Makefile.include | 2 + > tests/test-authz-listfile.c | 195 +++++++++++++++++++++++++ > 7 files changed, 642 insertions(+) > create mode 100644 authz/listfile.c > create mode 100644 include/authz/listfile.h > create mode 100644 tests/test-authz-listfile.c > > diff --git a/authz/Makefile.objs b/authz/Makefile.objs > index 921fa624d7..8351bf181d 100644 > --- a/authz/Makefile.objs > +++ b/authz/Makefile.objs > @@ -1,3 +1,4 @@ > authz-obj-y += base.o > authz-obj-y += simple.o > authz-obj-y += list.o > +authz-obj-y += listfile.o > 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/trace-events b/authz/trace-events > index a896d876e8..fb65349a90 100644 > --- a/authz/trace-events > +++ b/authz/trace-events > @@ -9,3 +9,7 @@ qauthz_simple_is_allowed(void *authz, const char > *wantidentity, const char *goti > # 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" > diff --git a/include/authz/listfile.h b/include/authz/listfile.h > new file mode 100644 > index 0000000000..80de9fe9c1 > --- /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", > + * "parameters": { Ditto ... > + * "filename": "/etc/qemu/myvm-vnc.acl", > + * "refresh": "yes" Ditto. > + * } > + * } > + * } > + * > + * 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/qemu-options.hx b/qemu-options.hx > index 7732881e7a..afc90dec2c 100644 > --- a/qemu-options.hx > +++ b/qemu-options.hx > @@ -4427,6 +4427,52 @@ would look like: > 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 > + > @end table > > ETEXI > diff --git a/tests/Makefile.include b/tests/Makefile.include > index 825ea218cb..a3f9134e85 100644 > --- a/tests/Makefile.include > +++ b/tests/Makefile.include > @@ -114,6 +114,7 @@ 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-y += tests/test-io-task$(EXESUF) > check-unit-y += tests/test-io-channel-socket$(EXESUF) > check-unit-y += tests/test-io-channel-file$(EXESUF) > @@ -646,6 +647,7 @@ 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-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-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; > +} > Tested-by: Philippe Mathieu-Daudé <phi...@redhat.com> Except qauthz_list_file_event() which I'm not confident because I need to understand better how events works, rest of the patch: Reviewed-by: Philippe Mathieu-Daudé <phi...@redhat.com>