From 67f92ea676968f3ec6f6b8ab3fdc0a6bc33c987e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 14 Sep 2010 18:03:47 +0200 Subject: [PATCH] Add unix domain client server support To get a properly functioning agent we will need to split the functionality into a daemon (vdagentd, which has the rights to open the virtio device and to create fake input devices for the mouse) and into a client (vdagent) which runs under Xorg and thus can read / set things like the resolution and the clipboard and talks to the spice server / client through the daemon. Since we can have multiple xorg sessions active (through switch user for example), the daemon supports multiple agent connections. Security still needs to be filled in I'm afraid (see TODO). The protocol between the 2 is "described" in vdagentd-proto.h, currently there is only one vdagentd command, which allows vdagent to tell vdagentd the xorg screen resolution so that it knows what resolution to use for the fake absolute input device, and so that it can adjust that resolution if the xorg resolution changes. The client included in this commit is purely a test client, which just sends a hardcoded resolution once and then sits there and does nothing. --- Makefile | 5 +- TODO | 16 +++ client.c | 76 ++++++++++ udscs.c | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ udscs.h | 92 ++++++++++++ vdagent.c | 79 ++++++++++- vdagentd-proto.h | 37 +++++ 7 files changed, 716 insertions(+), 5 deletions(-) create mode 100644 TODO create mode 100644 client.c create mode 100644 udscs.c create mode 100644 udscs.h create mode 100644 vdagentd-proto.h diff --git a/Makefile b/Makefile index 5a576d3..3e43047 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ CFLAGS += -g -Wall CFLAGS += -g -Wall CFLAGS += $(shell pkg-config --cflags spice-protocol) -TARGETS := vdagent +TARGETS := vdagent client build: $(TARGETS) @@ -21,4 +21,5 @@ install: build clean: rm -f $(TARGETS) *.o *~ -vdagent: vdagent.o +vdagent: vdagent.o udscs.o +client: client.o udscs.o \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..b9b7feb --- /dev/null +++ b/TODO @@ -0,0 +1,16 @@ +Well everything really, but this is here to add things +which we certainly must not forget: + +!!SECURITY!! + +We need to come up with some sort of security concept for the +vdagent daemon and the vdagent clients communication. + +Yes client_s_ as there can be multiple xorg sessions running +when for example the switch user functionality is used. But we +don't want Bob to be getting access to Alice's clipboard through +vdagent! + +One possible option is to ask ConsoleKit who the current Console +owner is and then only send messages to / accept messages from +agents whose unix domain socket has the same uid. diff --git a/client.c b/client.c new file mode 100644 index 0000000..30e82ab --- /dev/null +++ b/client.c @@ -0,0 +1,76 @@ +/* vdagent.c xorg-client to vdagentd (daemon). + + Copyright 2010 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + 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 3 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 program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "udscs.h" +#include "vdagentd-proto.h" + +void daemon_read_complete(struct udscs_connection *conn, + struct udscs_message_header *header, const uint8_t *data) +{ +} + +int main(int argc, char *argv[]) +{ + struct udscs_connection *client; + fd_set readfds, writefds; + int n, nfds; + + client = udscs_connect(VDAGENTD_SOCKET, daemon_read_complete, NULL); + if (!client) + exit(1); + + /* test test */ + struct vdagentd_guest_xorg_resolution res = { 1680, 1050 }; + struct udscs_message_header header = { + VDAGENTD_GUEST_XORG_RESOLUTION, + 0, + sizeof(res), + }; + udscs_write(client, &header, (uint8_t *)&res); + + for (;;) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + nfds = udscs_client_fill_fds(client, &readfds, &writefds); + + n = select(nfds, &readfds, &writefds, NULL, NULL); + if (n == -1) { + if (errno == EINTR) + continue; + perror("select"); + exit(1); + } + + udscs_client_handle_fds(client, &readfds, &writefds); + } + + udscs_destroy_connection(client); + + return 0; +} diff --git a/udscs.c b/udscs.c new file mode 100644 index 0000000..a6905e5 --- /dev/null +++ b/udscs.c @@ -0,0 +1,416 @@ +/* udscs.c Unix Domain Socket Client Server framework. A framework for quickly + creating select() based servers capable of handling multiple clients and + matching select() based clients using variable size messages. + + Copyright 2010 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + 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 3 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 program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "udscs.h" + +struct udscs_buf { + uint8_t *buf; + size_t pos; + size_t size; + + struct udscs_buf *next; +}; + +struct udscs_connection { + int fd; + + /* Read stuff, single buffer, separate header and data buffer */ + int header_read; + struct udscs_message_header header; + struct udscs_buf data; + + /* Writes are stored in a linked list of buffers, with both the header + + data for a single message in 1 buffer. */ + struct udscs_buf *write_buf; + + /* Callbacks */ + udscs_read_callback read_callback; + udscs_disconnect_callback disconnect_callback; + + struct udscs_connection *next; + struct udscs_connection *prev; +}; + +struct udscs_server { + int fd; + struct udscs_connection connections_head; + udscs_read_callback read_callback; + udscs_disconnect_callback disconnect_callback; +}; + +static void udscs_do_write(struct udscs_connection *conn); +static void udscs_do_read(struct udscs_connection *conn); + + +struct udscs_server *udscs_create_server(const char *socketname, + udscs_read_callback read_callback, + udscs_disconnect_callback disconnect_callback) +{ + int c; + struct sockaddr_un address; + struct udscs_server *server; + + server = calloc(1, sizeof(*server)); + if (!server) + return NULL; + + server->fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (server->fd == -1) { + perror("creating unix domain socket"); + free(server); + return NULL; + } + + c = unlink(socketname); + if (c != 0 && errno != ENOENT) { + fprintf(stderr, "unlink %s: %s\n", socketname, strerror(errno)); + free(server); + return NULL; + } + + address.sun_family = AF_UNIX; + snprintf(address.sun_path, sizeof(address.sun_path), "%s", socketname); + c = bind(server->fd, (struct sockaddr *)&address, sizeof(address)); + if (c != 0) { + fprintf(stderr, "bind %s: %s\n", socketname, strerror(errno)); + free(server); + return NULL; + } + + c = listen(server->fd, 5); + if (c != 0) { + perror("listen"); + free(server); + return NULL; + } + + server->read_callback = read_callback; + server->disconnect_callback = disconnect_callback; + + return server; +} + +void udscs_destroy_server(struct udscs_server *server) +{ + struct udscs_connection *conn, *next_conn; + + conn = server->connections_head.next; + while (conn) { + next_conn = conn->next; + udscs_destroy_connection(conn); + conn = next_conn; + } + close(server->fd); + free(server); +} + +struct udscs_connection *udscs_connect(const char *socketname, + udscs_read_callback read_callback, + udscs_disconnect_callback disconnect_callback) +{ + int c; + struct sockaddr_un address; + struct udscs_connection *conn; + + conn = calloc(1, sizeof(*conn)); + if (!conn) + return NULL; + + conn->fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (conn->fd == -1) { + perror("creating unix domain socket"); + free(conn); + return NULL; + } + + address.sun_family = AF_UNIX; + snprintf(address.sun_path, sizeof(address.sun_path), "%s", socketname); + c = connect(conn->fd, (struct sockaddr *)&address, sizeof(address)); + if (c != 0) { + fprintf(stderr, "connect %s: %s\n", socketname, strerror(errno)); + free(conn); + return NULL; + } + + conn->read_callback = read_callback; + conn->disconnect_callback = disconnect_callback; + + return conn; +} + +void udscs_destroy_connection(struct udscs_connection *conn) +{ + struct udscs_buf *wbuf, *next_wbuf; + + wbuf = conn->write_buf; + while (wbuf) { + next_wbuf = wbuf->next; + free(wbuf->buf); + free(wbuf); + wbuf = next_wbuf; + } + + free(conn->data.buf); + + if (conn->prev) + conn->prev->next = conn->next; + + close(conn->fd); + free(conn); +} + +int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds, + fd_set *writefds) +{ + struct udscs_connection *conn; + int nfds = server->fd + 1; + + FD_SET(server->fd, readfds); + + conn = server->connections_head.next; + while (conn) { + int conn_nfds = udscs_client_fill_fds(conn, readfds, writefds); + if (conn_nfds > nfds) + nfds = conn_nfds; + + conn = conn->next; + } + + return nfds; +} + +int udscs_client_fill_fds(struct udscs_connection *conn, fd_set *readfds, + fd_set *writefds) +{ + FD_SET(conn->fd, readfds); + if (conn->write_buf) + FD_SET(conn->fd, writefds); + + return conn->fd + 1; +} + +static void udscs_server_accept(struct udscs_server *server) { + struct udscs_connection *new_conn, *conn; + struct sockaddr_un address; + socklen_t address_length = sizeof(address); + int fd; + + fd = accept(server->fd, (struct sockaddr *)&address, &address_length); + if (fd == -1) { + if (errno == EINTR) + return; + perror("accept"); + return; + } + + new_conn = calloc(1, sizeof(*conn)); + if (!new_conn) { + fprintf(stderr, "out of memory, disconnecting client\n"); + close(fd); + return; + } + + new_conn->fd = fd; + new_conn->read_callback = server->read_callback; + new_conn->disconnect_callback = server->disconnect_callback; + + conn = &server->connections_head; + while (conn->next) + conn = conn->next; + + new_conn->prev = conn; + conn->next = new_conn; +} + +void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds, + fd_set *writefds) +{ + struct udscs_connection *conn; + + if (FD_ISSET(server->fd, readfds)) + udscs_server_accept(server); + + conn = server->connections_head.next; + while (conn) { + udscs_client_handle_fds(conn, readfds, writefds); + conn = conn->next; + } +} + +void udscs_client_handle_fds(struct udscs_connection *conn, fd_set *readfds, + fd_set *writefds) +{ + if (FD_ISSET(conn->fd, readfds)) + udscs_do_read(conn); + + if (FD_ISSET(conn->fd, writefds)) + udscs_do_write(conn); +} + +int udscs_write(struct udscs_connection *conn, + struct udscs_message_header *header, const uint8_t *data) +{ + struct udscs_buf *wbuf, *new_wbuf; + + new_wbuf = malloc(sizeof(*new_wbuf)); + if (!new_wbuf) + return -1; + + new_wbuf->pos = 0; + new_wbuf->size = sizeof(*header) + header->size; + new_wbuf->next = NULL; + new_wbuf->buf = malloc(new_wbuf->size); + if (!new_wbuf->buf) { + free(new_wbuf); + return -1; + } + + memcpy(new_wbuf->buf, header, sizeof(*header)); + memcpy(new_wbuf->buf + sizeof(*header), data, header->size); + + if (!conn->write_buf) { + conn->write_buf = new_wbuf; + return 0; + } + + /* FIXME maybe limit the write_buf stack depth ? */ + wbuf = conn->write_buf; + while (wbuf->next) + wbuf = wbuf->next; + + wbuf->next = wbuf; + + return 0; +} + +int udscs_server_write_all(struct udscs_server *server, + struct udscs_message_header *header, const uint8_t *data) +{ + struct udscs_connection *conn; + + conn = server->connections_head.next; + while (conn) { + if (udscs_write(conn, header, data)) + return -1; + conn = conn->next; + } + + return 0; +} + +static void udscs_do_read(struct udscs_connection *conn) +{ + ssize_t n; + size_t to_read; + uint8_t *dest; + + if (conn->header_read < sizeof(conn->header)) { + to_read = sizeof(conn->header) - conn->header_read; + dest = (uint8_t *)&conn->header + conn->header_read; + } else { + to_read = conn->data.size - conn->data.pos; + dest = conn->data.buf + conn->data.pos; + } + + n = read(conn->fd, dest, to_read); + if (n < 0) { + if (errno == EINTR) + return; + perror("reading from unix domain socket"); + if (conn->disconnect_callback) + conn->disconnect_callback(conn); + udscs_destroy_connection(conn); + return; + } + + if (conn->header_read < sizeof(conn->header)) { + conn->header_read += n; + if (conn->header_read == sizeof(conn->header)) { + if (conn->header.size == 0) { + if (conn->read_callback) + conn->read_callback(conn, &conn->header, NULL); + conn->header_read = 0; + } else { + conn->data.pos = 0; + conn->data.size = conn->header.size; + conn->data.buf = malloc(conn->data.size); + if (!conn->data.buf) { + fprintf(stderr, "out of memory, disconnecting client\n"); + if (conn->disconnect_callback) + conn->disconnect_callback(conn); + udscs_destroy_connection(conn); + return; + } + } + } + } else { + conn->data.pos += n; + if (conn->data.pos == conn->data.size) { + if (conn->read_callback) + conn->read_callback(conn, &conn->header, conn->data.buf); + free(conn->data.buf); + conn->header_read = 0; + memset(&conn->data, 0, sizeof(conn->data)); + } + } +} + +static void udscs_do_write(struct udscs_connection *conn) +{ + ssize_t n; + size_t to_write; + + struct udscs_buf* wbuf = conn->write_buf; + if (!wbuf) { + fprintf(stderr, + "do_write called on a connection without a write buf ?!\n"); + return; + } + + to_write = wbuf->size - wbuf->pos; + n = write(conn->fd, wbuf->buf + wbuf->pos, to_write); + if (n < 0) { + if (errno == EINTR) + return; + perror("writing to unix domain socket"); + if (conn->disconnect_callback) + conn->disconnect_callback(conn); + udscs_destroy_connection(conn); + return; + } + + wbuf->pos += n; + if (wbuf->pos == wbuf->size) { + conn->write_buf = wbuf->next; + free(wbuf->buf); + free(wbuf); + } +} + diff --git a/udscs.h b/udscs.h new file mode 100644 index 0000000..9cbd30a --- /dev/null +++ b/udscs.h @@ -0,0 +1,92 @@ +/* udscs.h Unix Domain Socket Client Server framework header file + + Copyright 2010 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + 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 3 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 program. If not, see . +*/ + +#ifndef __UDSCS_H +#define __UDSCS_H + +#include +#include + +struct udscs_connection; +struct udscs_server; +struct udscs_message_header { + uint32_t type; + uint32_t opaque; + uint32_t size; + uint8_t data[0]; +}; + +typedef void (*udscs_read_callback)(struct udscs_connection *conn, + struct udscs_message_header *header, const uint8_t *data); +/* Note udscs will destroy the connection in question itself after + this callback has completed! */ +typedef void (*udscs_disconnect_callback)(struct udscs_connection *conn); + +/* Create a unix domain socket named name and start listening on it. + read_callback will get called when a complete message has been + received, and disconnect_callback when a client is disconnected. */ +struct udscs_server *udscs_create_server(const char *socketname, + udscs_read_callback read_callback, + udscs_disconnect_callback disconnect_callback); + +void udscs_destroy_server(struct udscs_server *server); + +/* Connect to a unix domain socket named name. read_callback will get called + when a complete message has been received, and disconnect_callback when a + client is disconnected. */ +struct udscs_connection *udscs_connect(const char *socketname, + udscs_read_callback read_callback, + udscs_disconnect_callback disconnect_callback); + +void udscs_destroy_connection(struct udscs_connection *conn); + + +/* Given an usdcs server or client fill the fd_sets pointed to by readfds and + writefds for select() usage. + + Return value: value of the highest fd + 1 */ +int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds, + fd_set *writefds); + +int udscs_client_fill_fds(struct udscs_connection *conn, fd_set *readfds, + fd_set *writefds); + +/* Handle any events flagged by select for the given udscs server or client. */ +void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds, + fd_set *writefds); + +void udscs_client_handle_fds(struct udscs_connection *conn, fd_set *readfds, + fd_set *writefds); + + +/* Queue the message described by header and header->size bytes of additional + data bytes for delivery to the vdagent connected through conn. + + Returns 0 on success -1 on error (only happens when malloc fails) */ +int udscs_write(struct udscs_connection *conn, + struct udscs_message_header *header, const uint8_t *data); + +/* Like udscs_write, but then send the message to all clients connected to + the server */ +int udscs_server_write_all(struct udscs_server *server, + struct udscs_message_header *header, const uint8_t *data); + +#endif diff --git a/vdagent.c b/vdagent.c index e8e9128..1d140c0 100644 --- a/vdagent.c +++ b/vdagent.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -13,6 +14,9 @@ #include +#include "udscs.h" +#include "vdagentd-proto.h" + typedef struct VDAgentHeader { uint32_t port; uint32_t size; @@ -26,6 +30,7 @@ static const char *uinput = "/dev/uinput"; static int vdagent, tablet; static int debug = 0; static int width = 1024, height = 768; /* FIXME: don't hardcode */ +static struct udscs_server *server = NULL; /* uinput */ @@ -209,6 +214,66 @@ void daemonize(void) } } +void client_read_complete(struct udscs_connection *conn, + struct udscs_message_header *header, const uint8_t *data) +{ + switch (header->type) { + case VDAGENTD_GUEST_XORG_RESOLUTION: { + struct vdagentd_guest_xorg_resolution *res = + (struct vdagentd_guest_xorg_resolution *)data; + + if (header->size != sizeof(*res)) { + /* FIXME destroy connection, but this will cause a double + free of data */ + break; + } + + width = res->width; + height = res->height; + close(tablet); + tablet = open(uinput, O_RDWR); + if (-1 == tablet) { + fprintf(stderr, "open %s: %s\n", uinput, strerror(errno)); + exit(1); + } + uinput_setup(); + break; + } + default: + fprintf(stderr, "unknown message from vdagent client: %u, ignoring\n", + header->type); + } +} + +void main_loop(void) +{ + fd_set readfds, writefds; + int n, nfds; + + for (;;) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + nfds = udscs_server_fill_fds(server, &readfds, &writefds); + + FD_SET(vdagent, &readfds); + if (vdagent >= nfds) + nfds = vdagent + 1; + + n = select(nfds, &readfds, &writefds, NULL, NULL); + if (n == -1) { + if (errno == EINTR) + continue; + perror("select"); + exit(1); + } + + udscs_server_handle_fds(server, &readfds, &writefds); + if (FD_ISSET(vdagent, &readfds)) + vdagent_read(); + } +} + int main(int argc, char *argv[]) { int c; @@ -241,12 +306,18 @@ int main(int argc, char *argv[]) } } + /* Open virtio port connection */ vdagent = open(portdev, O_RDWR); if (-1 == vdagent) { fprintf(stderr, "open %s: %s\n", portdev, strerror(errno)); exit(1); } + /* Setup communication with vdagent process(es) */ + server = udscs_create_server(VDAGENTD_SOCKET, client_read_complete, NULL); + if (!server) + exit(1); + tablet = open(uinput, O_RDWR); if (-1 == tablet) { fprintf(stderr, "open %s: %s\n", uinput, strerror(errno)); @@ -256,8 +327,10 @@ int main(int argc, char *argv[]) if (!debug) daemonize(); - for (;;) { - vdagent_read(); - } + + main_loop(); + + udscs_destroy_server(server); + return 0; } diff --git a/vdagentd-proto.h b/vdagentd-proto.h new file mode 100644 index 0000000..fa09105 --- /dev/null +++ b/vdagentd-proto.h @@ -0,0 +1,37 @@ +/* vdagentd-proto.h header file for the protocol over the unix domain socket + between the vdagent process / xorg-client and the vdagentd (daemon). + + Copyright 2010 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + 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 3 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 program. If not, see . +*/ + +#ifndef __VDAGENTD_PROTO_H +#define __VDAGENTD_PROTO_H + +#define VDAGENTD_SOCKET "/tmp/vdagent" + +enum { + VDAGENTD_GUEST_XORG_RESOLUTION, +}; + +struct vdagentd_guest_xorg_resolution { + int width; + int height; +}; + +#endif -- 2.11.4.GIT