diff --git a/meson.build b/meson.build index 8edfe21..7bdf553 100644 --- a/meson.build +++ b/meson.build @@ -84,6 +84,26 @@ pkgconfig.generate(lib_libeis, libraries: lib_libeis, ) +lib_libreis = shared_library('reis', + 'src/libreis.h', + 'src/libreis.c', + proto_headers, + dependencies: [dep_libutil, dep_protobuf], + install: true, + include_directories: 'src', +) +install_headers('src/libreis.h') + +dep_libreis = declare_dependency(link_with: lib_libreis, + include_directories: 'src') + +pkgconfig.generate(lib_libreis, + filebase: 'libreis', + name: 'libREIS', + description: 'Restriction handler for Emulated Input servers', + version: meson.project_version(), + libraries: lib_libreis, +) executable('eis-socket-server', 'tools/eis-socket-server.c', diff --git a/proto/ei.proto b/proto/ei.proto index 9da019a..e0cda04 100644 --- a/proto/ei.proto +++ b/proto/ei.proto @@ -10,16 +10,17 @@ syntax = "proto3"; * ServerMessage → sent from the server to the client * * A normal sequence consists of: - * 1. - client establishes connection to server - * 2. - client sends "Connect" - * 2.a - server replies with "Connected" or - * 2.b - server replies with "Disconnected" and closes its end of the socket - * 3. - client sends "AddDevice" - * 3.a - server replies with "Accepted" or - * 3.b - server replies with "Removed" - * 4. - client sends "PointerRelative" or any other event - * 5. - client sends "RemoveDevice" - * 6. - client sends "Disconnect" and closes its end of the socket + * [0. - proxy configures connection, see the section below] + * 1. - client establishes connection to server + * 2. - client sends "Connect" + * 2.a - server replies with "Connected" or + * 2.b - server replies with "Disconnected" and closes its end of the socket + * 3. - client sends "AddDevice" + * 3.a - server replies with "Accepted" or + * 3.b - server replies with "Removed" + * 4. - client sends "PointerRelative" or any other event + * 5. - client sends "RemoveDevice" + * 6. - client sends "Disconnect" and closes its end of the socket * * The server may send Disconnect at any time. * The server may send Removed for a device at any time after that device's @@ -40,7 +41,36 @@ syntax = "proto3"; * Where the server accepts all, the events will be processed as expected. * Where the server rejects, the corresponding data will get discarded without * warning to the client. + * + * Pre-configuring a connection + * ---------------------------- + * + * Where a proxy is in place (e.g. a portal), the client connection can be + * preconfigured to match the permissions model. The proxy would open a + * socket to the server, write the Configure* messages onto that socket and + * then pass the fd to the client to create a libei context from that. + * + * The proxy can force a client name and/or restrict other options. This is + * transparent to the client, it doesn't know what restrictions are in place + * until it runs up against them (e.g. a device with a restricted capability + * will not be added with that capability). + * + * Configure messages may come at any time but they can only ever *reduce* + * the current capabilities, not increase them. */ + +/* ConfigureName *must* be sent before the Connect event */ +message ConfigureName { + string name = 1; +} + +/* Changes the capability policy and allows or denies specific capabilities */ +message ConfigureCapabilities { + bool policy_is_allow = 1; + uint32 allowed_capabilities = 2; + uint32 denied_capabilities = 3; +} + message Connect { string name = 1; } @@ -84,6 +114,8 @@ message ClientMessage { PointerRelative rel = 5; PointerButton button = 6; KeyboardKey key = 7; + ConfigureName configure_name = 8; + ConfigureCapabilities configure_caps = 9; } } diff --git a/src/libeis-client.c b/src/libeis-client.c index b4c06c3..c25231c 100644 --- a/src/libeis-client.c +++ b/src/libeis-client.c @@ -49,6 +49,9 @@ enum message_type { MESSAGE_POINTER_REL, MESSAGE_POINTER_BUTTON, MESSAGE_KEYBOARD_KEY, + + MESSAGE_CONFIGURE_NAME, + MESSAGE_CONFIGURE_CAPABILITIES, }; struct message { @@ -82,6 +85,14 @@ struct message { uint32_t key; bool state; } keyboard_key;; + struct message_configure_name { + char *name; + } configure_name; + struct message_configure_capabilities { + bool policy_is_allow; + uint32_t caps_allow; + uint32_t caps_deny; + } configure_caps; }; }; @@ -92,6 +103,9 @@ message_free(struct message *msg) case MESSAGE_CONNECT: free(msg->connect.name); break; + case MESSAGE_CONFIGURE_NAME: + free(msg->configure_name.name); + break; default: break; } @@ -308,6 +322,44 @@ client_keyboard_key(struct eis_client *client, uint32_t deviceid, return -EINVAL; } +static int +client_configure_name(struct eis_client *client, const char *name) +{ + /* We silently ignore wrong configure messages */ + if (client->state != EIS_CLIENT_STATE_NEW || client->name) + return 0; + + client->name = xstrdup(name); + + return 0; +} + +static int +client_configure_caps(struct eis_client *client, bool policy_is_allow, + uint32_t allowed_caps, uint32_t denied_caps) +{ + if (policy_is_allow) { + /* We can only manage allow masks while we're in + * default-allow policy. + * first call to set allow masks is taken as-is, from then onwards + * we can only restrict */ + if (client->restrictions.cap_allow_mask == ~0U) + client->restrictions.cap_allow_mask = allowed_caps; + else + client->restrictions.cap_allow_mask &= allowed_caps; + } else { + client->restrictions.cap_policy = CLIENT_CAP_POLICY_DENY; + } + + client->restrictions.cap_deny_mask |= denied_caps; + + /* FIXME: if something is disallowed now, we should disconnect + * accordingly. + */ + + return 0; +} + static int client_new_handle_msg(struct eis_client *client, struct message *msg) { @@ -316,7 +368,8 @@ client_new_handle_msg(struct eis_client *client, struct message *msg) switch (msg->type) { case MESSAGE_CONNECT: eis_queue_connect_event(client); - client->name = steal(&msg->connect.name); + if (client->name == NULL) + client->name = steal(&msg->connect.name); client->state = EIS_CLIENT_STATE_CONNECTING; break; case MESSAGE_DISCONNECT: @@ -329,6 +382,14 @@ client_new_handle_msg(struct eis_client *client, struct message *msg) case MESSAGE_KEYBOARD_KEY: rc = -EPROTO; break; + case MESSAGE_CONFIGURE_NAME: + rc = client_configure_name(client, msg->configure_name.name); + break; + case MESSAGE_CONFIGURE_CAPABILITIES: + rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, + msg->configure_caps.caps_allow, + msg->configure_caps.caps_deny); + break; } return rc; @@ -353,6 +414,14 @@ client_connecting_handle_msg(struct eis_client *client, const struct message *ms case MESSAGE_KEYBOARD_KEY: rc = -EPROTO; break; + case MESSAGE_CONFIGURE_NAME: + rc = client_configure_name(client, msg->configure_name.name); + break; + case MESSAGE_CONFIGURE_CAPABILITIES: + rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, + msg->configure_caps.caps_allow, + msg->configure_caps.caps_deny); + break; } return rc; @@ -392,6 +461,14 @@ client_connected_handle_msg(struct eis_client *client, msg->keyboard_key.key, msg->keyboard_key.state); break; + case MESSAGE_CONFIGURE_NAME: + rc = client_configure_name(client, msg->configure_name.name); + break; + case MESSAGE_CONFIGURE_CAPABILITIES: + rc = client_configure_caps(client, msg->configure_caps.policy_is_allow, + msg->configure_caps.caps_allow, + msg->configure_caps.caps_deny); + break; } return rc; } @@ -494,6 +571,26 @@ client_parse_message(const char *data, size_t *len) }; } break; + case CLIENT_MESSAGE__MSG_CONFIGURE_NAME: + { + ConfigureName *c = proto->configure_name; + *msg = (struct message) { + .type = MESSAGE_CONFIGURE_NAME, + .configure_name.name = xstrdup(c->name), + }; + } + break; + case CLIENT_MESSAGE__MSG_CONFIGURE_CAPS: + { + ConfigureCapabilities *c = proto->configure_caps; + *msg = (struct message) { + .type = MESSAGE_CONFIGURE_CAPABILITIES, + .configure_caps.policy_is_allow = c->policy_is_allow, + .configure_caps.caps_allow = c->allowed_capabilities, + .configure_caps.caps_deny = c->denied_capabilities, + }; + } + break; default: success = false; break; @@ -597,6 +694,9 @@ eis_client_new(struct eis *eis, int fd) client->source = source_ref(s); client->state = EIS_CLIENT_STATE_NEW; + client->restrictions.cap_policy = CLIENT_CAP_POLICY_ALLOW; + client->restrictions.cap_allow_mask = ~0U; + client->restrictions.cap_deny_mask = 0; eis_add_client(eis, eis_client_ref(client)); diff --git a/src/libeis-private.h b/src/libeis-private.h index 5f6f443..a86c421 100644 --- a/src/libeis-private.h +++ b/src/libeis-private.h @@ -61,6 +61,15 @@ struct eis_client { char *name; struct list devices; + + struct { + enum { + CLIENT_CAP_POLICY_ALLOW, + CLIENT_CAP_POLICY_DENY, + } cap_policy; + uint32_t cap_allow_mask; + uint32_t cap_deny_mask; + } restrictions; }; enum eis_device_state { diff --git a/src/libreis.c b/src/libreis.c new file mode 100644 index 0000000..3bdc797 --- /dev/null +++ b/src/libreis.c @@ -0,0 +1,162 @@ +/* + * Copyright © 2020 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 (including the next + * paragraph) 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 + +#include "libreis.h" + +#include "util-bits.h" +#include "util-object.h" +#include "util-macros.h" +#include "util-strings.h" +#include "util-mem.h" +#include "util-io.h" + +#include "proto/ei.pb-c.h" + +struct reis { + struct object object; + char *name; + bool policy_is_allow; + uint32_t allow; + uint32_t deny; +}; + +static void +reis_destroy(struct reis *reis) +{ + free(reis->name); +} + +_public_ +OBJECT_IMPLEMENT_UNREF(reis); +OBJECT_IMPLEMENT_CREATE(reis); + +static int +send_msg(int fd, const ClientMessage *msg) +{ + size_t msglen = client_message__get_packed_size(msg); + Frame frame = FRAME__INIT; + frame.length = msglen; + size_t framelen = frame__get_packed_size(&frame); + + uint8_t buf[framelen + msglen]; + frame__pack(&frame, buf); + client_message__pack(msg, buf + framelen); + return min(0, xsend(fd, buf, sizeof(buf))); +} + +_public_ struct reis * +reis_new(void) +{ + struct reis *reis = reis_create(NULL); + + reis->policy_is_allow = true; + + return reis; +} + +_public_ int +reis_apply(struct reis *reis, int eisfd) +{ + if (reis->name) { + ConfigureName n = CONFIGURE_NAME__INIT; + n.name = reis->name; + + ClientMessage msg = CLIENT_MESSAGE__INIT; + msg.configure_name = &n; + msg.msg_case = CLIENT_MESSAGE__MSG_CONFIGURE_NAME; + + int rc = send_msg(eisfd, &msg); + if (rc) + return rc; + } + + if (!reis->policy_is_allow || reis->allow || reis->deny) { + ConfigureCapabilities c = CONFIGURE_CAPABILITIES__INIT; + c.policy_is_allow = reis->policy_is_allow; + c.allowed_capabilities = reis->allow; + c.denied_capabilities = reis->deny; + + ClientMessage msg = CLIENT_MESSAGE__INIT; + msg.configure_caps = &c; + msg.msg_case = CLIENT_MESSAGE__MSG_CONFIGURE_CAPS; + + int rc = send_msg(eisfd, &msg); + if (rc) + return rc; + } + + return 0; +} + +_public_ int +reis_set_name(struct reis *reis, const char *name) +{ + free(reis->name); + reis->name = xstrdup(name); + + return 0; +} + +_public_ int +reis_set_cap_policy_allow(struct reis *reis) +{ + reis->policy_is_allow = true; + return 0; +} + +_public_ int +reis_set_cap_policy_deny(struct reis *reis) +{ + reis->policy_is_allow = false; + return 0; +} + +_public_ int +reis_allow_cap(struct reis *reis, enum eis_device_capability cap) +{ + switch (cap) { + case EIS_DEVICE_CAP_POINTER: + case EIS_DEVICE_CAP_POINTER_ABSOLUTE: + case EIS_DEVICE_CAP_KEYBOARD: + case EIS_DEVICE_CAP_TOUCH: + reis->allow |= bit(cap); + return 0; + } + return -EINVAL; +} + +_public_ int +reis_deny_cap(struct reis *reis, enum eis_device_capability cap) +{ + switch (cap) { + case EIS_DEVICE_CAP_POINTER: + case EIS_DEVICE_CAP_POINTER_ABSOLUTE: + case EIS_DEVICE_CAP_KEYBOARD: + case EIS_DEVICE_CAP_TOUCH: + reis->deny |= bit(cap); + return 0; + } + return -EINVAL; +} diff --git a/src/libreis.h b/src/libreis.h new file mode 100644 index 0000000..517c1ec --- /dev/null +++ b/src/libreis.h @@ -0,0 +1,103 @@ +/* + * Copyright © 2020 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 (including the next + * paragraph) 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. + */ + +#pragma once + +#include + +/** + * REIS is the Restrictions for EIS. This library is used by intermediaries + * between EI and EIS to reduce the capabilities that an EI client has + * available. + * + * It is a helper library that does not initiate a full EI or EIS + * context but works on the file descriptor instead. + * + * Restricting capabilities is a one-way-road. The default EIS context has + * full permissions, consecutive calls can only restrict the current + * permissions but not loosen them. + */ + +struct reis; + +struct reis * +reis_new(void); + +struct reis * +reis_unref(struct reis* reis); + +int +reis_apply(struct reis *reis, int eisfd); + + +/** + * Set the name for the client on this connection. + * + * This function has no effect if the EI client has already sent the + * connection message to the server. IOW this can only be used to *set* the + * name but not to *change* the name of the client. + * + * Calling this function multiple times has no effect, only the first name + * is used. + * + * @return zero on success or a negative errno otherwise + */ +int +reis_set_name(struct reis *reis, const char *name); + +/** + * Change the default policy for capabilities to "allow". Capabilities are + * allowed unless explicitly denied by reis_deny_cap(). + * + * This function has no effect if the default policy is "deny". + */ +int +reis_set_cap_policy_allow(struct reis *reis); + +/** + * Change the default policy for capabilities to "deny". Capabilities are + * allowed unless explicitly denied by reis_allow_cap(). + * + * You **must** call reis_allow_cap() **before** calling this function, once + * the default deny policy is in place, the allowed capabilities cannot be + * expanded further. + * + * A capability is permitted if: + * - the policy is allow and the capability is not in the deny list + * - the policy is deny and the capability was added to the allow list + * before the policy was set to deny + */ +int +reis_set_cap_policy_deny(struct reis *reis); + +/** + * Explicitly allow the given capability. + */ +int +reis_allow_cap(struct reis *reis, enum eis_device_capability cap); + +/** + * Explicitly deny the given capability. + */ +int +reis_deny_cap(struct reis *reis, enum eis_device_capability cap);