mirror of
https://gitlab.freedesktop.org/libinput/libei.git
synced 2026-01-06 14:10:13 +01:00
Add configuration messages to the protocol
For the Portal case, we'll have the portal open the sockets for us and then (depending on policy) restrict what the client can do. Then the socket can be passed to the client with e.g. keyboards disabled and the client is none the wiser (other than that the server will reject any keyboard caps). Since the portal doesn't need a EI context, the configuration is a separate small library. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
5d99d858bf
commit
4cfa6e6225
6 changed files with 437 additions and 11 deletions
20
meson.build
20
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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
162
src/libreis.c
Normal file
162
src/libreis.c
Normal file
|
|
@ -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 <config.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
103
src/libreis.h
Normal file
103
src/libreis.h
Normal file
|
|
@ -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 <libeis.h>
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Loading…
Add table
Reference in a new issue