Add a standard authorization framework
The current network services now support encryption via TLS and in some cases support authentication via SASL. In cases where SASL is not available, x509 client certificates can be used as a crude authorization scheme, but using a sub-CA and controlling who you give certs to. In general this is not very flexible though, so this series introduces a new standard authorization framework. It comes with four initial authorization mechanisms - Simple - an exact username match. This is useful when there is exactly one user that is known to connect. For example when live migrating from one QEMU to another with TLS, libvirt would use the simple scheme to whitelist the TLS cert of the source QEMU. - List - an full access control list, with optional regex matching. This is more flexible and is used to provide 100% backcompat with the existing HMP ACL commands. The caveat is that we can't create these via the CLI -object arg yet. - ListFile - the same as List, but with the rules stored in JSON format in an external file. This avoids the -object limitation while also allowing the admin to change list entries on the file. QEMU uses inotify to notice these changes and auto-reload the file contents. This is likely a good default choice for most network services, if the "simple" mechanism isn't sufficient. - PAM - delegate the username lookup to a PAM module, which opens the door to many options including things like SQL/LDAP lookups. -----BEGIN PGP SIGNATURE----- iQIcBAABCAAGBQJcdVxaAAoJEL6G67QVEE/fgK4P/jxF1aTzuc0N0jD7myNi2LE4 aUqXueawNLrZKk8ZZQtEy/71/fTNJm9fVwxN9zIWITb4jTj4c3hqUnK1iVtEXXOR e0qZdKAsVAD2cpj9PBaihn84f+hrSF1ZnuMqtt4/kfOE9QPfLRpbqCNe+7JX5Z7z Cw8k+ATvm6Xc5QVEkc+8JTMft9lQaOdRJvldR2CVSqeFgIZWIrQ2NIG44m2GqO6b wqqUgmaMHd4cGJ4fgTn8G74GNzp4hpXzgTC9P881zC8qc+dKKJasTq1jSR3eWYKJ aE4Y+4EDjH/a0sP/zf97OfDzb/mGEpw8ng00g3o0qphFmSWMHVcoghB6wUZZcZGu VcGyoH+ztDdIcoD9HwVbkzMvYt9qrtMNrMUf3p1DRRNrrwcopSYnG6gZ9ArckgD4 2VzxLrBbfS9tsWGAvOw/+c4SIxEzGQ0mcqHQeZONxLAi+zwkzuKVu973JBkvCBus aXpct8B2aMMxZeJ1ipWksj5PvAAEjhOquDqZrUtRiP7vg4156UiAhxW9x6hK//O1 pof4jVBPZSYgrt3+PjADwK1fmPv01KkNa1dVi/Ew7fBJjGQNfJ12JlAYdSeoGG4i ds4OBbQH+Ko40++ZGu+n7xlk/0GQEw771c2GNDoA7ehHPg++S2yvrRoVVneyicur RYzwaPQ1nYrGAo3fAJEd =67pz -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/berrange/tags/authz-core-pull-request' into staging Add a standard authorization framework The current network services now support encryption via TLS and in some cases support authentication via SASL. In cases where SASL is not available, x509 client certificates can be used as a crude authorization scheme, but using a sub-CA and controlling who you give certs to. In general this is not very flexible though, so this series introduces a new standard authorization framework. It comes with four initial authorization mechanisms - Simple - an exact username match. This is useful when there is exactly one user that is known to connect. For example when live migrating from one QEMU to another with TLS, libvirt would use the simple scheme to whitelist the TLS cert of the source QEMU. - List - an full access control list, with optional regex matching. This is more flexible and is used to provide 100% backcompat with the existing HMP ACL commands. The caveat is that we can't create these via the CLI -object arg yet. - ListFile - the same as List, but with the rules stored in JSON format in an external file. This avoids the -object limitation while also allowing the admin to change list entries on the file. QEMU uses inotify to notice these changes and auto-reload the file contents. This is likely a good default choice for most network services, if the "simple" mechanism isn't sufficient. - PAM - delegate the username lookup to a PAM module, which opens the door to many options including things like SQL/LDAP lookups. # gpg: Signature made Tue 26 Feb 2019 15:33:46 GMT # gpg: using RSA key BE86EBB415104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" [full] # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" [full] # Primary key fingerprint: DAF3 A6FD B26B 6291 2D0E 8E3F BE86 EBB4 1510 4FDF * remotes/berrange/tags/authz-core-pull-request: authz: delete existing ACL implementation authz: add QAuthZPAM object type for authorizing using PAM authz: add QAuthZListFile object type for a file access control list authz: add QAuthZList object type for an access control list authz: add QAuthZSimple object type for easy whitelist auth checks authz: add QAuthZ object as an authorization base class hw/usb: switch MTP to use new inotify APIs hw/usb: fix const-ness for string params in MTP driver hw/usb: don't set IN_ISDIR for inotify watch in MTP driver qom: don't require user creatable objects to be registered util: add helper APIs for dealing with inotify in portable manner Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
86c7e2f4a9
15
MAINTAINERS
15
MAINTAINERS
|
@ -2079,6 +2079,14 @@ F: io/
|
||||||
F: include/io/
|
F: include/io/
|
||||||
F: tests/test-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
|
Sockets
|
||||||
M: Daniel P. Berrange <berrange@redhat.com>
|
M: Daniel P. Berrange <berrange@redhat.com>
|
||||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||||
|
@ -2087,6 +2095,13 @@ F: include/qemu/sockets.h
|
||||||
F: util/qemu-sockets.c
|
F: util/qemu-sockets.c
|
||||||
F: qapi/sockets.json
|
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
|
Throttling infrastructure
|
||||||
M: Alberto Garcia <berto@igalia.com>
|
M: Alberto Garcia <berto@igalia.com>
|
||||||
S: Supported
|
S: Supported
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -359,6 +359,7 @@ endif
|
||||||
|
|
||||||
dummy := $(call unnest-vars,, \
|
dummy := $(call unnest-vars,, \
|
||||||
stub-obj-y \
|
stub-obj-y \
|
||||||
|
authz-obj-y \
|
||||||
chardev-obj-y \
|
chardev-obj-y \
|
||||||
util-obj-y \
|
util-obj-y \
|
||||||
qga-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))
|
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
||||||
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
||||||
|
|
||||||
|
$(SOFTMMU_SUBDIR_RULES): $(authz-obj-y)
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
|
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(io-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.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-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 $(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 $(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)
|
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): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
|
||||||
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
|
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
|
ifdef CONFIG_MPATH
|
||||||
scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist
|
scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# Common libraries for tools and emulators
|
# Common libraries for tools and emulators
|
||||||
stub-obj-y = stubs/ crypto/
|
stub-obj-y = stubs/ util/ crypto/
|
||||||
util-obj-y = util/ qobject/ qapi/
|
util-obj-y = util/ qobject/ qapi/
|
||||||
|
|
||||||
chardev-obj-y = chardev/
|
chardev-obj-y = chardev/
|
||||||
slirp-obj-$(CONFIG_SLIRP) = slirp/
|
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 is code used by both qemu system emulation and qemu-img
|
||||||
|
|
||||||
|
@ -125,6 +130,7 @@ trace-events-subdirs =
|
||||||
trace-events-subdirs += accel/kvm
|
trace-events-subdirs += accel/kvm
|
||||||
trace-events-subdirs += accel/tcg
|
trace-events-subdirs += accel/tcg
|
||||||
trace-events-subdirs += audio
|
trace-events-subdirs += audio
|
||||||
|
trace-events-subdirs += authz
|
||||||
trace-events-subdirs += block
|
trace-events-subdirs += block
|
||||||
trace-events-subdirs += chardev
|
trace-events-subdirs += chardev
|
||||||
trace-events-subdirs += crypto
|
trace-events-subdirs += crypto
|
||||||
|
|
|
@ -179,6 +179,7 @@ include $(SRC_PATH)/Makefile.objs
|
||||||
dummy := $(call unnest-vars,,target-obj-y)
|
dummy := $(call unnest-vars,,target-obj-y)
|
||||||
target-obj-y-save := $(target-obj-y)
|
target-obj-y-save := $(target-obj-y)
|
||||||
dummy := $(call unnest-vars,.., \
|
dummy := $(call unnest-vars,.., \
|
||||||
|
authz-obj-y \
|
||||||
block-obj-y \
|
block-obj-y \
|
||||||
block-obj-m \
|
block-obj-m \
|
||||||
chardev-obj-y \
|
chardev-obj-y \
|
||||||
|
@ -193,6 +194,7 @@ target-obj-y := $(target-obj-y-save)
|
||||||
all-obj-y += $(common-obj-y)
|
all-obj-y += $(common-obj-y)
|
||||||
all-obj-y += $(target-obj-y)
|
all-obj-y += $(target-obj-y)
|
||||||
all-obj-y += $(qom-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_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
|
||||||
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
||||||
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
||||||
|
|
7
authz/Makefile.objs
Normal file
7
authz/Makefile.objs
Normal file
|
@ -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
|
82
authz/base.c
Normal file
82
authz/base.c
Normal file
|
@ -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)
|
||||||
|
|
271
authz/list.c
Normal file
271
authz/list.c
Normal file
|
@ -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);
|
283
authz/listfile.c
Normal file
283
authz/listfile.c
Normal file
|
@ -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);
|
148
authz/pamacct.c
Normal file
148
authz/pamacct.c
Normal file
|
@ -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);
|
115
authz/simple.c
Normal file
115
authz/simple.c
Normal file
|
@ -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);
|
18
authz/trace-events
Normal file
18
authz/trace-events
Normal file
|
@ -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"
|
54
configure
vendored
54
configure
vendored
|
@ -463,6 +463,7 @@ gnutls=""
|
||||||
nettle=""
|
nettle=""
|
||||||
gcrypt=""
|
gcrypt=""
|
||||||
gcrypt_hmac="no"
|
gcrypt_hmac="no"
|
||||||
|
auth_pam=""
|
||||||
vte=""
|
vte=""
|
||||||
virglrenderer=""
|
virglrenderer=""
|
||||||
tpm="yes"
|
tpm="yes"
|
||||||
|
@ -1381,6 +1382,10 @@ for opt do
|
||||||
;;
|
;;
|
||||||
--enable-gcrypt) gcrypt="yes"
|
--enable-gcrypt) gcrypt="yes"
|
||||||
;;
|
;;
|
||||||
|
--disable-auth-pam) auth_pam="no"
|
||||||
|
;;
|
||||||
|
--enable-auth-pam) auth_pam="yes"
|
||||||
|
;;
|
||||||
--enable-rdma) rdma="yes"
|
--enable-rdma) rdma="yes"
|
||||||
;;
|
;;
|
||||||
--disable-rdma) rdma="no"
|
--disable-rdma) rdma="no"
|
||||||
|
@ -1707,6 +1712,7 @@ disabled with --disable-FEATURE, default is enabled if available:
|
||||||
gnutls GNUTLS cryptography support
|
gnutls GNUTLS cryptography support
|
||||||
nettle nettle cryptography support
|
nettle nettle cryptography support
|
||||||
gcrypt libgcrypt cryptography support
|
gcrypt libgcrypt cryptography support
|
||||||
|
auth-pam PAM access control
|
||||||
sdl SDL UI
|
sdl SDL UI
|
||||||
sdl_image SDL Image support for icons
|
sdl_image SDL Image support for icons
|
||||||
gtk gtk UI
|
gtk gtk UI
|
||||||
|
@ -2864,6 +2870,33 @@ else
|
||||||
fi
|
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 )
|
# getifaddrs (for tests/test-io-channel-socket )
|
||||||
|
|
||||||
|
@ -3172,20 +3205,6 @@ if test "$xkbcommon" != "no" ; then
|
||||||
fi
|
fi
|
||||||
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
|
# xfsctl() probe, used for file-posix.c
|
||||||
|
@ -6091,6 +6110,7 @@ echo "GNUTLS support $gnutls"
|
||||||
echo "libgcrypt $gcrypt"
|
echo "libgcrypt $gcrypt"
|
||||||
echo "nettle $nettle $(echo_version $nettle $nettle_version)"
|
echo "nettle $nettle $(echo_version $nettle $nettle_version)"
|
||||||
echo "libtasn1 $tasn1"
|
echo "libtasn1 $tasn1"
|
||||||
|
echo "PAM $auth_pam"
|
||||||
echo "curses support $curses"
|
echo "curses support $curses"
|
||||||
echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)"
|
echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)"
|
||||||
echo "curl support $curl"
|
echo "curl support $curl"
|
||||||
|
@ -6382,9 +6402,6 @@ if test "$xkbcommon" = "yes" ; then
|
||||||
echo "XKBCOMMON_CFLAGS=$xkbcommon_cflags" >> $config_host_mak
|
echo "XKBCOMMON_CFLAGS=$xkbcommon_cflags" >> $config_host_mak
|
||||||
echo "XKBCOMMON_LIBS=$xkbcommon_libs" >> $config_host_mak
|
echo "XKBCOMMON_LIBS=$xkbcommon_libs" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
if test "$fnmatch" = "yes" ; then
|
|
||||||
echo "CONFIG_FNMATCH=y" >> $config_host_mak
|
|
||||||
fi
|
|
||||||
if test "$xfs" = "yes" ; then
|
if test "$xfs" = "yes" ; then
|
||||||
echo "CONFIG_XFS=y" >> $config_host_mak
|
echo "CONFIG_XFS=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
@ -6550,6 +6567,9 @@ fi
|
||||||
if test "$tasn1" = "yes" ; then
|
if test "$tasn1" = "yes" ; then
|
||||||
echo "CONFIG_TASN1=y" >> $config_host_mak
|
echo "CONFIG_TASN1=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
if test "$auth_pam" = "yes" ; then
|
||||||
|
echo "CONFIG_AUTH_PAM=y" >> $config_host_mak
|
||||||
|
fi
|
||||||
if test "$have_ifaddrs_h" = "yes" ; then
|
if test "$have_ifaddrs_h" = "yes" ; then
|
||||||
echo "HAVE_IFADDRS_H=y" >> $config_host_mak
|
echo "HAVE_IFADDRS_H=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#include "crypto/tlscredspsk.h"
|
#include "crypto/tlscredspsk.h"
|
||||||
#include "crypto/tlscredsx509.h"
|
#include "crypto/tlscredsx509.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
#include "qemu/acl.h"
|
#include "authz/base.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
#ifdef CONFIG_GNUTLS
|
#ifdef CONFIG_GNUTLS
|
||||||
|
@ -37,7 +37,7 @@ struct QCryptoTLSSession {
|
||||||
QCryptoTLSCreds *creds;
|
QCryptoTLSCreds *creds;
|
||||||
gnutls_session_t handle;
|
gnutls_session_t handle;
|
||||||
char *hostname;
|
char *hostname;
|
||||||
char *aclname;
|
char *authzid;
|
||||||
bool handshakeComplete;
|
bool handshakeComplete;
|
||||||
QCryptoTLSSessionWriteFunc writeFunc;
|
QCryptoTLSSessionWriteFunc writeFunc;
|
||||||
QCryptoTLSSessionReadFunc readFunc;
|
QCryptoTLSSessionReadFunc readFunc;
|
||||||
|
@ -56,7 +56,7 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
|
||||||
gnutls_deinit(session->handle);
|
gnutls_deinit(session->handle);
|
||||||
g_free(session->hostname);
|
g_free(session->hostname);
|
||||||
g_free(session->peername);
|
g_free(session->peername);
|
||||||
g_free(session->aclname);
|
g_free(session->authzid);
|
||||||
object_unref(OBJECT(session->creds));
|
object_unref(OBJECT(session->creds));
|
||||||
g_free(session);
|
g_free(session);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
|
||||||
QCryptoTLSSession *
|
QCryptoTLSSession *
|
||||||
qcrypto_tls_session_new(QCryptoTLSCreds *creds,
|
qcrypto_tls_session_new(QCryptoTLSCreds *creds,
|
||||||
const char *hostname,
|
const char *hostname,
|
||||||
const char *aclname,
|
const char *authzid,
|
||||||
QCryptoTLSCredsEndpoint endpoint,
|
QCryptoTLSCredsEndpoint endpoint,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
|
@ -105,13 +105,13 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds,
|
||||||
session = g_new0(QCryptoTLSSession, 1);
|
session = g_new0(QCryptoTLSSession, 1);
|
||||||
trace_qcrypto_tls_session_new(
|
trace_qcrypto_tls_session_new(
|
||||||
session, creds, hostname ? hostname : "<none>",
|
session, creds, hostname ? hostname : "<none>",
|
||||||
aclname ? aclname : "<none>", endpoint);
|
authzid ? authzid : "<none>", endpoint);
|
||||||
|
|
||||||
if (hostname) {
|
if (hostname) {
|
||||||
session->hostname = g_strdup(hostname);
|
session->hostname = g_strdup(hostname);
|
||||||
}
|
}
|
||||||
if (aclname) {
|
if (authzid) {
|
||||||
session->aclname = g_strdup(aclname);
|
session->authzid = g_strdup(authzid);
|
||||||
}
|
}
|
||||||
session->creds = creds;
|
session->creds = creds;
|
||||||
object_ref(OBJECT(creds));
|
object_ref(OBJECT(creds));
|
||||||
|
@ -262,6 +262,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
|
||||||
unsigned int nCerts, i;
|
unsigned int nCerts, i;
|
||||||
time_t now;
|
time_t now;
|
||||||
gnutls_x509_crt_t cert = NULL;
|
gnutls_x509_crt_t cert = NULL;
|
||||||
|
Error *err = NULL;
|
||||||
|
|
||||||
now = time(NULL);
|
now = time(NULL);
|
||||||
if (now == ((time_t)-1)) {
|
if (now == ((time_t)-1)) {
|
||||||
|
@ -349,19 +350,17 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
|
||||||
gnutls_strerror(ret));
|
gnutls_strerror(ret));
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (session->aclname) {
|
if (session->authzid) {
|
||||||
qemu_acl *acl = qemu_acl_find(session->aclname);
|
bool allow;
|
||||||
int allow;
|
|
||||||
if (!acl) {
|
allow = qauthz_is_allowed_by_id(session->authzid,
|
||||||
error_setg(errp, "Cannot find ACL %s",
|
session->peername, &err);
|
||||||
session->aclname);
|
if (err) {
|
||||||
|
error_propagate(errp, err);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
allow = qemu_acl_party_is_allowed(acl, session->peername);
|
|
||||||
|
|
||||||
if (!allow) {
|
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);
|
session->peername);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +554,7 @@ qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
|
||||||
QCryptoTLSSession *
|
QCryptoTLSSession *
|
||||||
qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
|
qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
|
||||||
const char *hostname 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,
|
QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"
|
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
|
# 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"
|
qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
|
||||||
|
|
303
hw/usb/dev-mtp.c
303
hw/usb/dev-mtp.c
|
@ -11,17 +11,16 @@
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
|
#include "qemu/error-report.h"
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
#include <sys/statvfs.h>
|
#include <sys/statvfs.h>
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
#include <sys/inotify.h>
|
|
||||||
#include "qemu/main-loop.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/iov.h"
|
#include "qemu/iov.h"
|
||||||
|
#include "qemu/filemonitor.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "hw/usb.h"
|
#include "hw/usb.h"
|
||||||
#include "desc.h"
|
#include "desc.h"
|
||||||
|
@ -132,7 +131,6 @@ enum {
|
||||||
EP_EVENT,
|
EP_EVENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
typedef struct MTPMonEntry MTPMonEntry;
|
typedef struct MTPMonEntry MTPMonEntry;
|
||||||
|
|
||||||
struct MTPMonEntry {
|
struct MTPMonEntry {
|
||||||
|
@ -141,7 +139,6 @@ struct MTPMonEntry {
|
||||||
|
|
||||||
QTAILQ_ENTRY(MTPMonEntry) next;
|
QTAILQ_ENTRY(MTPMonEntry) next;
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
struct MTPControl {
|
struct MTPControl {
|
||||||
uint16_t code;
|
uint16_t code;
|
||||||
|
@ -172,10 +169,8 @@ struct MTPObject {
|
||||||
char *name;
|
char *name;
|
||||||
char *path;
|
char *path;
|
||||||
struct stat stat;
|
struct stat stat;
|
||||||
#ifdef CONFIG_INOTIFY1
|
/* file monitor watch id */
|
||||||
/* inotify watch cookie */
|
int watchid;
|
||||||
int watchfd;
|
|
||||||
#endif
|
|
||||||
MTPObject *parent;
|
MTPObject *parent;
|
||||||
uint32_t nchildren;
|
uint32_t nchildren;
|
||||||
QLIST_HEAD(, MTPObject) children;
|
QLIST_HEAD(, MTPObject) children;
|
||||||
|
@ -198,11 +193,8 @@ struct MTPState {
|
||||||
bool readonly;
|
bool readonly;
|
||||||
|
|
||||||
QTAILQ_HEAD(, MTPObject) objects;
|
QTAILQ_HEAD(, MTPObject) objects;
|
||||||
#ifdef CONFIG_INOTIFY1
|
QFileMonitor *file_monitor;
|
||||||
/* inotify descriptor */
|
|
||||||
int inotifyfd;
|
|
||||||
QTAILQ_HEAD(, MTPMonEntry) events;
|
QTAILQ_HEAD(, MTPMonEntry) events;
|
||||||
#endif
|
|
||||||
/* Responder is expecting a write operation */
|
/* Responder is expecting a write operation */
|
||||||
bool write_pending;
|
bool write_pending;
|
||||||
struct {
|
struct {
|
||||||
|
@ -383,7 +375,7 @@ static const USBDesc desc = {
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
|
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);
|
MTPObject *o = g_new0(MTPObject, 1);
|
||||||
|
|
||||||
|
@ -391,6 +383,7 @@ static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
|
||||||
goto ignore;
|
goto ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
o->watchid = -1;
|
||||||
o->handle = handle;
|
o->handle = handle;
|
||||||
o->parent = parent;
|
o->parent = parent;
|
||||||
o->name = g_strdup(name);
|
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);
|
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);
|
QTAILQ_REMOVE(&s->objects, o, next);
|
||||||
if (o->parent) {
|
if (o->parent) {
|
||||||
QLIST_REMOVE(o, list);
|
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,
|
static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
|
||||||
char *name)
|
const char *name)
|
||||||
{
|
{
|
||||||
MTPObject *child =
|
MTPObject *child =
|
||||||
usb_mtp_object_alloc(s, s->next_handle++, o, name);
|
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,
|
static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
|
||||||
char *name, int len)
|
const char *name, int len)
|
||||||
{
|
{
|
||||||
MTPObject *iter;
|
MTPObject *iter;
|
||||||
|
|
||||||
|
if (len == -1) {
|
||||||
|
len = strlen(name);
|
||||||
|
}
|
||||||
|
|
||||||
QLIST_FOREACH(iter, &parent->children, list) {
|
QLIST_FOREACH(iter, &parent->children, list) {
|
||||||
if (strncmp(iter->name, name, len) == 0) {
|
if (strncmp(iter->name, name, len) == 0) {
|
||||||
return iter;
|
return iter;
|
||||||
|
@ -497,13 +498,12 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_INOTIFY1
|
static MTPObject *usb_mtp_object_lookup_id(MTPState *s, int id)
|
||||||
static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
|
|
||||||
{
|
{
|
||||||
MTPObject *iter;
|
MTPObject *iter;
|
||||||
|
|
||||||
QTAILQ_FOREACH(iter, &s->objects, next) {
|
QTAILQ_FOREACH(iter, &s->objects, next) {
|
||||||
if (iter->watchfd == wd) {
|
if (iter->watchid == id) {
|
||||||
return iter;
|
return iter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,160 +511,103 @@ static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
|
||||||
return NULL;
|
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;
|
MTPState *s = opaque;
|
||||||
ssize_t bytes;
|
MTPObject *parent = usb_mtp_object_lookup_id(s, id);
|
||||||
/* From the man page: atleast one event can be read */
|
MTPMonEntry *entry = NULL;
|
||||||
int pos;
|
MTPObject *o;
|
||||||
char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
|
|
||||||
|
|
||||||
for (;;) {
|
if (!parent) {
|
||||||
bytes = read(s->inotifyfd, buf, sizeof(buf));
|
|
||||||
pos = 0;
|
|
||||||
|
|
||||||
if (bytes <= 0) {
|
|
||||||
/* Better luck next time */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Ignore initiator initiated events.
|
|
||||||
* For now we are good because the store is RO
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int usb_mtp_inotify_init(MTPState *s)
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
fd = inotify_init1(IN_NONBLOCK);
|
|
||||||
if (fd == -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QTAILQ_INIT(&s->events);
|
|
||||||
s->inotifyfd = fd;
|
|
||||||
|
|
||||||
qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void usb_mtp_inotify_cleanup(MTPState *s)
|
|
||||||
{
|
|
||||||
MTPMonEntry *e, *p;
|
|
||||||
|
|
||||||
if (!s->inotifyfd) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
|
switch (ev) {
|
||||||
close(s->inotifyfd);
|
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:
|
||||||
|
/*
|
||||||
|
* 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, 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
case QFILE_MONITOR_EVENT_IGNORED:
|
||||||
|
trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path,
|
||||||
|
"Obj parent dir ignored");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QFILE_MONITOR_EVENT_ATTRIBUTES:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
QTAILQ_INSERT_HEAD(&s->events, entry, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_mtp_file_monitor_cleanup(MTPState *s)
|
||||||
|
{
|
||||||
|
MTPMonEntry *e, *p;
|
||||||
|
|
||||||
QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
|
QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
|
||||||
QTAILQ_REMOVE(&s->events, e, next);
|
QTAILQ_REMOVE(&s->events, e, next);
|
||||||
g_free(e);
|
g_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qemu_file_monitor_free(s->file_monitor);
|
||||||
|
s->file_monitor = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
|
static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
|
||||||
{
|
{
|
||||||
struct dirent *entry;
|
struct dirent *entry;
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
int fd;
|
int fd;
|
||||||
|
Error *err = NULL;
|
||||||
|
|
||||||
if (o->have_children) {
|
if (o->have_children) {
|
||||||
return;
|
return;
|
||||||
|
@ -680,16 +623,21 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
|
||||||
close(fd);
|
close(fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
int watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
|
if (s->file_monitor) {
|
||||||
if (watchfd == -1) {
|
int id = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
|
||||||
fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
|
file_monitor_event, s, &err);
|
||||||
} else {
|
if (id == -1) {
|
||||||
trace_usb_mtp_inotify_event(s->dev.addr, o->path,
|
error_report("usb-mtp: failed to add watch for %s: %s", o->path,
|
||||||
0, "Watch Added");
|
error_get_pretty(err));
|
||||||
o->watchfd = watchfd;
|
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) {
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
usb_mtp_add_child(s, o, entry->d_name);
|
usb_mtp_add_child(s, o, entry->d_name);
|
||||||
}
|
}
|
||||||
|
@ -1197,13 +1145,11 @@ enum {
|
||||||
/* Assumes that children, if any, have been already freed */
|
/* Assumes that children, if any, have been already freed */
|
||||||
static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
|
static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_INOTIFY1
|
|
||||||
assert(o->nchildren == 0);
|
assert(o->nchildren == 0);
|
||||||
QTAILQ_REMOVE(&s->objects, o, next);
|
QTAILQ_REMOVE(&s->objects, o, next);
|
||||||
g_free(o->name);
|
g_free(o->name);
|
||||||
g_free(o->path);
|
g_free(o->path);
|
||||||
g_free(o);
|
g_free(o);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans)
|
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;
|
MTPData *data_in = NULL;
|
||||||
MTPObject *o = NULL;
|
MTPObject *o = NULL;
|
||||||
uint32_t nres = 0, res0 = 0;
|
uint32_t nres = 0, res0 = 0;
|
||||||
|
Error *err = NULL;
|
||||||
|
|
||||||
/* sanity checks */
|
/* sanity checks */
|
||||||
if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
|
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);
|
trace_usb_mtp_op_open_session(s->dev.addr);
|
||||||
s->session = c->argv[0];
|
s->session = c->argv[0];
|
||||||
usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
|
usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
if (usb_mtp_inotify_init(s)) {
|
s->file_monitor = qemu_file_monitor_new(&err);
|
||||||
fprintf(stderr, "usb-mtp: file monitoring init failed\n");
|
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;
|
break;
|
||||||
case CMD_CLOSE_SESSION:
|
case CMD_CLOSE_SESSION:
|
||||||
trace_usb_mtp_op_close_session(s->dev.addr);
|
trace_usb_mtp_op_close_session(s->dev.addr);
|
||||||
s->session = 0;
|
s->session = 0;
|
||||||
s->next_handle = 0;
|
s->next_handle = 0;
|
||||||
#ifdef CONFIG_INOTIFY1
|
usb_mtp_file_monitor_cleanup(s);
|
||||||
usb_mtp_inotify_cleanup(s);
|
|
||||||
#endif
|
|
||||||
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
|
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
|
||||||
assert(QTAILQ_EMPTY(&s->objects));
|
assert(QTAILQ_EMPTY(&s->objects));
|
||||||
break;
|
break;
|
||||||
|
@ -1554,9 +1503,7 @@ static void usb_mtp_handle_reset(USBDevice *dev)
|
||||||
|
|
||||||
trace_usb_mtp_reset(s->dev.addr);
|
trace_usb_mtp_reset(s->dev.addr);
|
||||||
|
|
||||||
#ifdef CONFIG_INOTIFY1
|
usb_mtp_file_monitor_cleanup(s);
|
||||||
usb_mtp_inotify_cleanup(s);
|
|
||||||
#endif
|
|
||||||
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
|
usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
|
||||||
s->session = 0;
|
s->session = 0;
|
||||||
usb_mtp_data_free(s->data_in);
|
usb_mtp_data_free(s->data_in);
|
||||||
|
@ -2027,7 +1974,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EP_EVENT:
|
case EP_EVENT:
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
if (!QTAILQ_EMPTY(&s->events)) {
|
if (!QTAILQ_EMPTY(&s->events)) {
|
||||||
struct MTPMonEntry *e = QTAILQ_LAST(&s->events);
|
struct MTPMonEntry *e = QTAILQ_LAST(&s->events);
|
||||||
uint32_t handle;
|
uint32_t handle;
|
||||||
|
@ -2051,7 +1997,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
|
||||||
g_free(e);
|
g_free(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
p->status = USB_RET_NAK;
|
p->status = USB_RET_NAK;
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -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_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_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_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
|
# hw/usb/host-libusb.c
|
||||||
usb_host_open_started(int bus, int addr) "dev %d:%d"
|
usb_host_open_started(int bus, int addr) "dev %d:%d"
|
||||||
|
|
112
include/authz/base.h
Normal file
112
include/authz/base.h
Normal file
|
@ -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__ */
|
||||||
|
|
106
include/authz/list.h
Normal file
106
include/authz/list.h
Normal file
|
@ -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__ */
|
||||||
|
|
111
include/authz/listfile.h
Normal file
111
include/authz/listfile.h
Normal file
|
@ -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__ */
|
||||||
|
|
100
include/authz/pamacct.h
Normal file
100
include/authz/pamacct.h
Normal file
|
@ -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__ */
|
84
include/authz/simple.h
Normal file
84
include/authz/simple.h
Normal file
|
@ -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__ */
|
||||||
|
|
|
@ -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 */
|
|
128
include/qemu/filemonitor.h
Normal file
128
include/qemu/filemonitor.h
Normal file
|
@ -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 */
|
175
monitor.c
175
monitor.c
|
@ -51,7 +51,8 @@
|
||||||
#include "sysemu/balloon.h"
|
#include "sysemu/balloon.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "sysemu/hw_accel.h"
|
#include "sysemu/hw_accel.h"
|
||||||
#include "qemu/acl.h"
|
#include "authz/list.h"
|
||||||
|
#include "qapi/util.h"
|
||||||
#include "sysemu/tpm.h"
|
#include "sysemu/tpm.h"
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
#include "qapi/qmp/qerror.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);
|
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);
|
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)
|
static void hmp_acl_show(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
const char *aclname = qdict_get_str(qdict, "aclname");
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
||||||
qemu_acl *acl = find_acl(mon, aclname);
|
QAuthZList *auth = find_auth(mon, aclname);
|
||||||
qemu_acl_entry *entry;
|
QAuthZListRuleList *rules;
|
||||||
int i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
if (acl) {
|
if (!auth) {
|
||||||
monitor_printf(mon, "policy: %s\n",
|
return;
|
||||||
acl->defaultDeny ? "deny" : "allow");
|
}
|
||||||
QTAILQ_FOREACH(entry, &acl->entries, next) {
|
|
||||||
i++;
|
monitor_printf(mon, "policy: %s\n",
|
||||||
monitor_printf(mon, "%d: %s %s\n", i,
|
QAuthZListPolicy_str(auth->policy));
|
||||||
entry->deny ? "deny" : "allow", entry->match);
|
|
||||||
}
|
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)
|
static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
const char *aclname = qdict_get_str(qdict, "aclname");
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
||||||
qemu_acl *acl = find_acl(mon, aclname);
|
QAuthZList *auth = find_auth(mon, aclname);
|
||||||
|
|
||||||
if (acl) {
|
if (!auth) {
|
||||||
qemu_acl_reset(acl);
|
return;
|
||||||
monitor_printf(mon, "acl: removed all rules\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
const char *aclname = qdict_get_str(qdict, "aclname");
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
||||||
const char *policy = qdict_get_str(qdict, "policy");
|
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 (!auth) {
|
||||||
if (strcmp(policy, "allow") == 0) {
|
return;
|
||||||
acl->defaultDeny = 0;
|
}
|
||||||
|
|
||||||
|
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");
|
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 {
|
} else {
|
||||||
monitor_printf(mon, "acl: unknown policy '%s', "
|
monitor_printf(mon, "acl: policy set to 'deny'\n");
|
||||||
"expected 'deny' or 'allow'\n", policy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
static void hmp_acl_add(Monitor *mon, const QDict *qdict)
|
||||||
{
|
{
|
||||||
const char *aclname = qdict_get_str(qdict, "aclname");
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
||||||
const char *match = qdict_get_str(qdict, "match");
|
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 has_index = qdict_haskey(qdict, "index");
|
||||||
int index = qdict_get_try_int(qdict, "index", -1);
|
int index = qdict_get_try_int(qdict, "index", -1);
|
||||||
qemu_acl *acl = find_acl(mon, aclname);
|
QAuthZList *auth = find_auth(mon, aclname);
|
||||||
int deny, ret;
|
Error *err = NULL;
|
||||||
|
QAuthZListPolicy policy;
|
||||||
|
QAuthZListFormat format;
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
if (acl) {
|
if (!auth) {
|
||||||
if (strcmp(policy, "allow") == 0) {
|
return;
|
||||||
deny = 0;
|
}
|
||||||
} else if (strcmp(policy, "deny") == 0) {
|
|
||||||
deny = 1;
|
policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
|
||||||
} else {
|
policystr,
|
||||||
monitor_printf(mon, "acl: unknown policy '%s', "
|
QAUTHZ_LIST_POLICY_DENY,
|
||||||
"expected 'deny' or 'allow'\n", policy);
|
&err);
|
||||||
return;
|
if (err) {
|
||||||
}
|
error_free(err);
|
||||||
if (has_index)
|
monitor_printf(mon, "acl: unknown policy '%s', "
|
||||||
ret = qemu_acl_insert(acl, deny, match, index);
|
"expected 'deny' or 'allow'\n", policystr);
|
||||||
else
|
return;
|
||||||
ret = qemu_acl_append(acl, deny, match);
|
}
|
||||||
if (ret < 0)
|
|
||||||
monitor_printf(mon, "acl: unable to add acl entry\n");
|
format = hmp_acl_get_format(match);
|
||||||
else
|
|
||||||
monitor_printf(mon, "acl: added rule at position %d\n", ret);
|
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 *aclname = qdict_get_str(qdict, "aclname");
|
||||||
const char *match = qdict_get_str(qdict, "match");
|
const char *match = qdict_get_str(qdict, "match");
|
||||||
qemu_acl *acl = find_acl(mon, aclname);
|
QAuthZList *auth = find_auth(mon, aclname);
|
||||||
int ret;
|
ssize_t i = 0;
|
||||||
|
|
||||||
if (acl) {
|
if (!auth) {
|
||||||
ret = qemu_acl_remove(acl, match);
|
return;
|
||||||
if (ret < 0)
|
}
|
||||||
monitor_printf(mon, "acl: no matching acl entry\n");
|
|
||||||
else
|
i = qauthz_list_delete_rule(auth, match);
|
||||||
monitor_printf(mon, "acl: removed rule at position %d\n", ret);
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ util-obj-y += opts-visitor.o qapi-clone-visitor.o
|
||||||
util-obj-y += qmp-event.o
|
util-obj-y += qmp-event.o
|
||||||
util-obj-y += qapi-util.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 += job migration misc net rdma rocker run-state
|
||||||
QAPI_COMMON_MODULES += sockets tpm trace transaction ui
|
QAPI_COMMON_MODULES += sockets tpm trace transaction ui
|
||||||
QAPI_TARGET_MODULES = target
|
QAPI_TARGET_MODULES = target
|
||||||
|
|
58
qapi/authz.json
Normal file
58
qapi/authz.json
Normal file
|
@ -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'] } }
|
|
@ -92,6 +92,7 @@
|
||||||
{ 'include': 'rocker.json' }
|
{ 'include': 'rocker.json' }
|
||||||
{ 'include': 'tpm.json' }
|
{ 'include': 'tpm.json' }
|
||||||
{ 'include': 'ui.json' }
|
{ 'include': 'ui.json' }
|
||||||
|
{ 'include': 'authz.json' }
|
||||||
{ 'include': 'migration.json' }
|
{ 'include': 'migration.json' }
|
||||||
{ 'include': 'transaction.json' }
|
{ 'include': 'transaction.json' }
|
||||||
{ 'include': 'trace.json' }
|
{ 'include': 'trace.json' }
|
||||||
|
|
105
qemu-options.hx
105
qemu-options.hx
|
@ -4365,6 +4365,111 @@ e.g to launch a SEV guest
|
||||||
.....
|
.....
|
||||||
|
|
||||||
@end example
|
@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
|
@end table
|
||||||
|
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
12
qom/object.c
12
qom/object.c
|
@ -646,16 +646,20 @@ Object *object_new_with_propv(const char *typename,
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
object_property_add_child(parent, id, obj, &local_err);
|
if (id != NULL) {
|
||||||
if (local_err) {
|
object_property_add_child(parent, id, obj, &local_err);
|
||||||
goto error;
|
if (local_err) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uc = (UserCreatable *)object_dynamic_cast(obj, TYPE_USER_CREATABLE);
|
uc = (UserCreatable *)object_dynamic_cast(obj, TYPE_USER_CREATABLE);
|
||||||
if (uc) {
|
if (uc) {
|
||||||
user_creatable_complete(uc, &local_err);
|
user_creatable_complete(uc, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
object_unparent(obj);
|
if (id != NULL) {
|
||||||
|
object_unparent(obj);
|
||||||
|
}
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,16 +75,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
object_property_add_child(object_get_objects_root(),
|
if (id != NULL) {
|
||||||
id, obj, &local_err);
|
object_property_add_child(object_get_objects_root(),
|
||||||
if (local_err) {
|
id, obj, &local_err);
|
||||||
goto out;
|
if (local_err) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_creatable_complete(USER_CREATABLE(obj), &local_err);
|
user_creatable_complete(USER_CREATABLE(obj), &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
object_property_del(object_get_objects_root(),
|
if (id != NULL) {
|
||||||
id, &error_abort);
|
object_property_del(object_get_objects_root(),
|
||||||
|
id, &error_abort);
|
||||||
|
}
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
out:
|
out:
|
||||||
|
|
|
@ -114,7 +114,12 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
||||||
check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
|
check-unit-$(land,$(CONFIG_LINUX),$(CONFIG_VIRTIO_SERIAL)) += tests/test-qga$(EXESUF)
|
||||||
endif
|
endif
|
||||||
check-unit-y += tests/test-timed-average$(EXESUF)
|
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-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-task$(EXESUF)
|
||||||
check-unit-y += tests/test-io-channel-socket$(EXESUF)
|
check-unit-y += tests/test-io-channel-socket$(EXESUF)
|
||||||
check-unit-y += tests/test-io-channel-file$(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 \
|
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
|
||||||
tests/test-qapi-introspect.o \
|
tests/test-qapi-introspect.o \
|
||||||
$(test-qom-obj-y)
|
$(test-qom-obj-y)
|
||||||
benchmark-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 = $(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-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
|
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)
|
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-x509-helpers.o tests/pkix_asn1_tab.o \
|
||||||
tests/crypto-tls-psk-helpers.o \
|
tests/crypto-tls-psk-helpers.o \
|
||||||
$(test-crypto-obj-y)
|
$(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/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
|
||||||
tests/socket-helpers.o $(test-util-obj-y)
|
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-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
|
||||||
tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
|
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)
|
tests/io-channel-helpers.o tests/socket-helpers.o $(test-io-obj-y)
|
||||||
|
|
159
tests/test-authz-list.c
Normal file
159
tests/test-authz-list.c
Normal file
|
@ -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();
|
||||||
|
}
|
195
tests/test-authz-listfile.c
Normal file
195
tests/test-authz-listfile.c
Normal file
|
@ -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;
|
||||||
|
}
|
124
tests/test-authz-pam.c
Normal file
124
tests/test-authz-pam.c
Normal file
|
@ -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();
|
||||||
|
}
|
50
tests/test-authz-simple.c
Normal file
50
tests/test-authz-simple.c
Normal file
|
@ -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();
|
||||||
|
}
|
|
@ -28,7 +28,7 @@
|
||||||
#include "qom/object_interfaces.h"
|
#include "qom/object_interfaces.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qemu/acl.h"
|
#include "authz/list.h"
|
||||||
|
|
||||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
|
||||||
QCryptoTLSCreds *serverCreds;
|
QCryptoTLSCreds *serverCreds;
|
||||||
QCryptoTLSSession *clientSess = NULL;
|
QCryptoTLSSession *clientSess = NULL;
|
||||||
QCryptoTLSSession *serverSess = NULL;
|
QCryptoTLSSession *serverSess = NULL;
|
||||||
qemu_acl *acl;
|
QAuthZList *auth;
|
||||||
const char * const *wildcards;
|
const char * const *wildcards;
|
||||||
int channel[2];
|
int channel[2];
|
||||||
bool clientShake = false;
|
bool clientShake = false;
|
||||||
|
@ -285,11 +285,15 @@ static void test_crypto_tls_session_x509(const void *opaque)
|
||||||
SERVER_CERT_DIR);
|
SERVER_CERT_DIR);
|
||||||
g_assert(serverCreds != NULL);
|
g_assert(serverCreds != NULL);
|
||||||
|
|
||||||
acl = qemu_acl_init("tlssessionacl");
|
auth = qauthz_list_new("tlssessionacl",
|
||||||
qemu_acl_reset(acl);
|
QAUTHZ_LIST_POLICY_DENY,
|
||||||
|
&error_abort);
|
||||||
wildcards = data->wildcards;
|
wildcards = data->wildcards;
|
||||||
while (wildcards && *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++;
|
wildcards++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +381,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
|
||||||
|
|
||||||
object_unparent(OBJECT(serverCreds));
|
object_unparent(OBJECT(serverCreds));
|
||||||
object_unparent(OBJECT(clientCreds));
|
object_unparent(OBJECT(clientCreds));
|
||||||
|
object_unparent(OBJECT(auth));
|
||||||
|
|
||||||
qcrypto_tls_session_free(serverSess);
|
qcrypto_tls_session_free(serverSess);
|
||||||
qcrypto_tls_session_free(clientSess);
|
qcrypto_tls_session_free(clientSess);
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
#include "io-channel-helpers.h"
|
#include "io-channel-helpers.h"
|
||||||
#include "crypto/init.h"
|
#include "crypto/init.h"
|
||||||
#include "crypto/tlscredsx509.h"
|
#include "crypto/tlscredsx509.h"
|
||||||
#include "qemu/acl.h"
|
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
|
#include "authz/list.h"
|
||||||
#include "qom/object_interfaces.h"
|
#include "qom/object_interfaces.h"
|
||||||
|
|
||||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||||
|
@ -113,7 +113,7 @@ static void test_io_channel_tls(const void *opaque)
|
||||||
QIOChannelTLS *serverChanTLS;
|
QIOChannelTLS *serverChanTLS;
|
||||||
QIOChannelSocket *clientChanSock;
|
QIOChannelSocket *clientChanSock;
|
||||||
QIOChannelSocket *serverChanSock;
|
QIOChannelSocket *serverChanSock;
|
||||||
qemu_acl *acl;
|
QAuthZList *auth;
|
||||||
const char * const *wildcards;
|
const char * const *wildcards;
|
||||||
int channel[2];
|
int channel[2];
|
||||||
struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
|
struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
|
||||||
|
@ -161,11 +161,15 @@ static void test_io_channel_tls(const void *opaque)
|
||||||
SERVER_CERT_DIR);
|
SERVER_CERT_DIR);
|
||||||
g_assert(serverCreds != NULL);
|
g_assert(serverCreds != NULL);
|
||||||
|
|
||||||
acl = qemu_acl_init("channeltlsacl");
|
auth = qauthz_list_new("channeltlsacl",
|
||||||
qemu_acl_reset(acl);
|
QAUTHZ_LIST_POLICY_DENY,
|
||||||
|
&error_abort);
|
||||||
wildcards = data->wildcards;
|
wildcards = data->wildcards;
|
||||||
while (wildcards && *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++;
|
wildcards++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +257,8 @@ static void test_io_channel_tls(const void *opaque)
|
||||||
object_unref(OBJECT(serverChanSock));
|
object_unref(OBJECT(serverChanSock));
|
||||||
object_unref(OBJECT(clientChanSock));
|
object_unref(OBJECT(clientChanSock));
|
||||||
|
|
||||||
|
object_unparent(OBJECT(auth));
|
||||||
|
|
||||||
close(channel[0]);
|
close(channel[0]);
|
||||||
close(channel[1]);
|
close(channel[1]);
|
||||||
}
|
}
|
||||||
|
|
685
tests/test-util-filemonitor.c
Normal file
685
tests/test-util-filemonitor.c
Normal file
|
@ -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();
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
|
#include "authz/base.h"
|
||||||
#include "vnc.h"
|
#include "vnc.h"
|
||||||
#include "trace.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)
|
static int vnc_auth_sasl_check_access(VncState *vs)
|
||||||
{
|
{
|
||||||
const void *val;
|
const void *val;
|
||||||
int err;
|
int rv;
|
||||||
int allow;
|
Error *err = NULL;
|
||||||
|
bool allow;
|
||||||
|
|
||||||
err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
|
rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
|
||||||
if (err != SASL_OK) {
|
if (rv != SASL_OK) {
|
||||||
trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
|
trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
|
||||||
sasl_errstring(err, NULL, NULL));
|
sasl_errstring(rv, NULL, NULL));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (val == NULL) {
|
if (val == NULL) {
|
||||||
|
@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
|
||||||
vs->sasl.username = g_strdup((const char*)val);
|
vs->sasl.username = g_strdup((const char*)val);
|
||||||
trace_vnc_auth_sasl_username(vs, vs->sasl.username);
|
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);
|
trace_vnc_auth_sasl_acl(vs, 1);
|
||||||
return 0;
|
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);
|
trace_vnc_auth_sasl_acl(vs, allow);
|
||||||
return allow ? 0 : -1;
|
return allow ? 0 : -1;
|
||||||
|
|
|
@ -30,8 +30,8 @@
|
||||||
typedef struct VncStateSASL VncStateSASL;
|
typedef struct VncStateSASL VncStateSASL;
|
||||||
typedef struct VncDisplaySASL VncDisplaySASL;
|
typedef struct VncDisplaySASL VncDisplaySASL;
|
||||||
|
|
||||||
#include "qemu/acl.h"
|
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
|
#include "authz/base.h"
|
||||||
|
|
||||||
struct VncStateSASL {
|
struct VncStateSASL {
|
||||||
sasl_conn_t *conn;
|
sasl_conn_t *conn;
|
||||||
|
@ -60,7 +60,8 @@ struct VncStateSASL {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VncDisplaySASL {
|
struct VncDisplaySASL {
|
||||||
qemu_acl *acl;
|
QAuthZ *authz;
|
||||||
|
char *authzid;
|
||||||
};
|
};
|
||||||
|
|
||||||
void vnc_sasl_client_cleanup(VncState *vs);
|
void vnc_sasl_client_cleanup(VncState *vs);
|
||||||
|
|
|
@ -109,7 +109,7 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
|
||||||
tls = qio_channel_tls_new_server(
|
tls = qio_channel_tls_new_server(
|
||||||
vs->ioc,
|
vs->ioc,
|
||||||
vs->vd->tlscreds,
|
vs->vd->tlscreds,
|
||||||
vs->vd->tlsaclname,
|
vs->vd->tlsauthzid,
|
||||||
&err);
|
&err);
|
||||||
if (!tls) {
|
if (!tls) {
|
||||||
trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
|
trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
|
||||||
|
|
|
@ -62,7 +62,7 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
|
||||||
tls = qio_channel_tls_new_server(
|
tls = qio_channel_tls_new_server(
|
||||||
vs->ioc,
|
vs->ioc,
|
||||||
vs->vd->tlscreds,
|
vs->vd->tlscreds,
|
||||||
vs->vd->tlsaclname,
|
vs->vd->tlsauthzid,
|
||||||
&err);
|
&err);
|
||||||
if (!tls) {
|
if (!tls) {
|
||||||
VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
|
VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
|
||||||
|
|
37
ui/vnc.c
37
ui/vnc.c
|
@ -33,7 +33,7 @@
|
||||||
#include "qemu/option.h"
|
#include "qemu/option.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "qemu/acl.h"
|
#include "authz/list.h"
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "qapi/qapi-emit-events.h"
|
#include "qapi/qapi-emit-events.h"
|
||||||
#include "qapi/qapi-events-ui.h"
|
#include "qapi/qapi-events-ui.h"
|
||||||
|
@ -3229,12 +3229,24 @@ static void vnc_display_close(VncDisplay *vd)
|
||||||
object_unparent(OBJECT(vd->tlscreds));
|
object_unparent(OBJECT(vd->tlscreds));
|
||||||
vd->tlscreds = NULL;
|
vd->tlscreds = NULL;
|
||||||
}
|
}
|
||||||
g_free(vd->tlsaclname);
|
if (vd->tlsauthz) {
|
||||||
vd->tlsaclname = NULL;
|
object_unparent(OBJECT(vd->tlsauthz));
|
||||||
|
vd->tlsauthz = NULL;
|
||||||
|
}
|
||||||
|
g_free(vd->tlsauthzid);
|
||||||
|
vd->tlsauthzid = NULL;
|
||||||
if (vd->lock_key_sync) {
|
if (vd->lock_key_sync) {
|
||||||
qemu_remove_led_event_handler(vd->led);
|
qemu_remove_led_event_handler(vd->led);
|
||||||
vd->led = NULL;
|
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)
|
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 (acl) {
|
||||||
if (strcmp(vd->id, "default") == 0) {
|
if (strcmp(vd->id, "default") == 0) {
|
||||||
vd->tlsaclname = g_strdup("vnc.x509dname");
|
vd->tlsauthzid = g_strdup("vnc.x509dname");
|
||||||
} else {
|
} 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
|
#ifdef CONFIG_VNC_SASL
|
||||||
if (acl && sasl) {
|
if (acl && sasl) {
|
||||||
char *aclname;
|
|
||||||
|
|
||||||
if (strcmp(vd->id, "default") == 0) {
|
if (strcmp(vd->id, "default") == 0) {
|
||||||
aclname = g_strdup("vnc.username");
|
vd->sasl.authzid = g_strdup("vnc.username");
|
||||||
} else {
|
} 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);
|
vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
|
||||||
g_free(aclname);
|
QAUTHZ_LIST_POLICY_DENY,
|
||||||
|
&error_abort));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
4
ui/vnc.h
4
ui/vnc.h
|
@ -39,6 +39,7 @@
|
||||||
#include "io/channel-socket.h"
|
#include "io/channel-socket.h"
|
||||||
#include "io/channel-tls.h"
|
#include "io/channel-tls.h"
|
||||||
#include "io/net-listener.h"
|
#include "io/net-listener.h"
|
||||||
|
#include "authz/base.h"
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include "keymaps.h"
|
#include "keymaps.h"
|
||||||
|
@ -178,7 +179,8 @@ struct VncDisplay
|
||||||
bool lossy;
|
bool lossy;
|
||||||
bool non_adaptive;
|
bool non_adaptive;
|
||||||
QCryptoTLSCreds *tlscreds;
|
QCryptoTLSCreds *tlscreds;
|
||||||
char *tlsaclname;
|
QAuthZ *tlsauthz;
|
||||||
|
char *tlsauthzid;
|
||||||
#ifdef CONFIG_VNC_SASL
|
#ifdef CONFIG_VNC_SASL
|
||||||
VncDisplaySASL sasl;
|
VncDisplaySASL sasl;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -20,7 +20,6 @@ util-obj-y += envlist.o path.o module.o
|
||||||
util-obj-y += host-utils.o
|
util-obj-y += host-utils.o
|
||||||
util-obj-y += bitmap.o bitops.o hbitmap.o
|
util-obj-y += bitmap.o bitops.o hbitmap.o
|
||||||
util-obj-y += fifo8.o
|
util-obj-y += fifo8.o
|
||||||
util-obj-y += acl.o
|
|
||||||
util-obj-y += cacheinfo.o
|
util-obj-y += cacheinfo.o
|
||||||
util-obj-y += error.o qemu-error.o
|
util-obj-y += error.o qemu-error.o
|
||||||
util-obj-y += id.o
|
util-obj-y += id.o
|
||||||
|
@ -50,5 +49,8 @@ util-obj-y += range.o
|
||||||
util-obj-y += stats64.o
|
util-obj-y += stats64.o
|
||||||
util-obj-y += systemd.o
|
util-obj-y += systemd.o
|
||||||
util-obj-y += iova-tree.o
|
util-obj-y += iova-tree.o
|
||||||
|
util-obj-$(CONFIG_INOTIFY1) += filemonitor-inotify.o
|
||||||
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
|
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
|
||||||
util-obj-$(CONFIG_OPENGL) += drm.o
|
util-obj-$(CONFIG_OPENGL) += drm.o
|
||||||
|
|
||||||
|
stub-obj-y += filemonitor-stub.o
|
||||||
|
|
179
util/acl.c
179
util/acl.c
|
@ -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;
|
|
||||||
}
|
|
339
util/filemonitor-inotify.c
Normal file
339
util/filemonitor-inotify.c
Normal file
|
@ -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);
|
||||||
|
}
|
59
util/filemonitor-stub.c
Normal file
59
util/filemonitor-stub.c
Normal file
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
|
@ -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_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"
|
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
|
# 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_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"
|
qemu_coroutine_yield(void *from, void *to) "from %p to %p"
|
||||||
|
|
Loading…
Reference in a new issue