vdagentd: Allow non root access to unix domain socket
[vd_agent/hramrach.git] / vdagentd.c
blob51702fd8f9e1e9d6e44b50970dbd8671d3d93da4
1 /* vdagentd.c vdagentd (daemon) code
3 Copyright 2010 Red Hat, Inc.
5 Red Hat Authors:
6 Hans de Goede <hdegoede@redhat.com>
7 Gerd Hoffmann <kraxel@redhat.com>
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <sys/select.h>
30 #include <sys/stat.h>
31 #include <spice/vd_agent.h>
33 #include "udscs.h"
34 #include "vdagentd-proto.h"
35 #include "vdagentd-proto-strings.h"
36 #include "vdagentd-uinput.h"
37 #include "vdagent-virtio-port.h"
38 #include "console-kit.h"
40 /* variables */
41 static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
42 static const char *uinput = "/dev/uinput";
43 static int connection_count = 0;
44 static int debug = 0;
45 static struct udscs_server *server = NULL;
46 static struct vdagent_virtio_port *virtio_port = NULL;
47 static struct console_kit *console_kit = NULL;
48 static VDAgentMonitorsConfig *mon_config = NULL;
49 static uint32_t *capabilities = NULL;
50 static int capabilities_size = 0;
52 /* vdagent virtio port handling */
53 static void send_capabilities(struct vdagent_virtio_port *port,
54 uint32_t request)
56 VDAgentAnnounceCapabilities *caps;
57 uint32_t size;
59 size = sizeof(*caps) + VD_AGENT_CAPS_BYTES;
60 caps = calloc(1, size);
61 if (!caps) {
62 fprintf(stderr,
63 "out of memory allocating capabilities array (write)\n");
64 return;
67 caps->request = request;
68 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MOUSE_STATE);
69 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_MONITORS_CONFIG);
70 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_REPLY);
71 if (console_kit)
72 VD_AGENT_SET_CAPABILITY(caps->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
74 vdagent_virtio_port_write(port, VDP_CLIENT_PORT,
75 VD_AGENT_ANNOUNCE_CAPABILITIES, 0,
76 (uint8_t *)caps, size);
77 free(caps);
80 static void do_monitors(struct vdagent_virtio_port *port, int port_nr,
81 VDAgentMessage *message_header, VDAgentMonitorsConfig *new_monitors)
83 VDAgentReply reply;
84 uint32_t size;
86 /* Store monitor config to send to agents when they connect */
87 size = sizeof(VDAgentMonitorsConfig) +
88 new_monitors->num_of_monitors * sizeof(VDAgentMonConfig);
89 if (message_header->size != size) {
90 fprintf(stderr, "invalid message size for VDAgentMonitorsConfig\n");
91 return;
94 if (!mon_config ||
95 mon_config->num_of_monitors != new_monitors->num_of_monitors) {
96 free(mon_config);
97 mon_config = malloc(size);
98 if (!mon_config) {
99 fprintf(stderr, "out of memory allocating monitors config\n");
100 return;
103 memcpy(mon_config, new_monitors, size);
105 /* Send monitor config to currently connected agents */
106 udscs_server_write_all(server, VDAGENTD_MONITORS_CONFIG, 0,
107 (uint8_t *)mon_config, size);
109 /* Acknowledge reception of monitors config to spice server / client */
110 reply.type = VD_AGENT_MONITORS_CONFIG;
111 reply.error = VD_AGENT_SUCCESS;
112 vdagent_virtio_port_write(port, port_nr, VD_AGENT_REPLY, 0,
113 (uint8_t *)&reply, sizeof(reply));
116 static void do_capabilities(struct vdagent_virtio_port *port,
117 VDAgentMessage *message_header,
118 VDAgentAnnounceCapabilities *caps)
120 capabilities_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message_header->size);
122 free(capabilities);
123 capabilities = malloc(capabilities_size * sizeof(uint32_t));
124 if (!capabilities) {
125 fprintf(stderr,
126 "out of memory allocating capabilities array (read)\n");
127 capabilities_size = 0;
128 return;
130 memcpy(capabilities, caps->caps, capabilities_size * sizeof(uint32_t));
131 if (caps->request)
132 send_capabilities(port, 0);
135 static int connection_matches_active_session(struct udscs_connection **connp,
136 void *priv)
138 struct udscs_connection **conn_ret = (struct udscs_connection **)priv;
139 const char *conn_session, *active_session;
141 /* Check if this connection matches the currently active session */
142 conn_session = (const char *)udscs_get_user_data(*connp);
143 active_session = console_kit_get_active_session(console_kit);
144 if (!conn_session || !active_session)
145 return 0;
146 if (strcmp(conn_session, active_session))
147 return 0;
149 *conn_ret = *connp;
150 return 1;
153 static void do_clipboard(struct vdagent_virtio_port *port,
154 VDAgentMessage *message_header, uint8_t *message_data)
156 uint32_t type = 0, opaque = 0, size = 0;
157 uint8_t *data = NULL;
158 struct udscs_connection *conn = NULL;
159 int n;
161 switch (message_header->type) {
162 case VD_AGENT_CLIPBOARD_GRAB:
163 type = VDAGENTD_CLIPBOARD_GRAB;
164 data = message_data;
165 size = message_header->size;
166 break;
167 case VD_AGENT_CLIPBOARD_REQUEST: {
168 VDAgentClipboardRequest *req = (VDAgentClipboardRequest *)message_data;
169 type = VDAGENTD_CLIPBOARD_REQUEST;
170 opaque = req->type;
171 break;
173 case VD_AGENT_CLIPBOARD: {
174 VDAgentClipboard *clipboard = (VDAgentClipboard *)message_data;
175 type = VDAGENTD_CLIPBOARD_DATA;
176 opaque = clipboard->type;
177 size = message_header->size - sizeof(VDAgentClipboard);
178 data = clipboard->data;
179 break;
181 case VD_AGENT_CLIPBOARD_RELEASE:
182 type = VDAGENTD_CLIPBOARD_RELEASE;
183 break;
186 n = udscs_server_for_all_clients(server, connection_matches_active_session,
187 (void*)&conn);
188 if (n != 1 || conn == NULL) {
189 fprintf(stderr,
190 "Could not find an agent connnection belonging to the "
191 "active session, ignoring client clipboard request\n");
192 return;
195 udscs_write(conn, type, opaque, data, size);
198 int virtio_port_read_complete(
199 struct vdagent_virtio_port *port,
200 VDIChunkHeader *chunk_header,
201 VDAgentMessage *message_header,
202 uint8_t *data)
204 uint32_t min_size = 0;
206 if (message_header->protocol != VD_AGENT_PROTOCOL) {
207 fprintf(stderr, "message with wrong protocol version ignoring\n");
208 return 0;
211 switch (message_header->type) {
212 case VD_AGENT_MOUSE_STATE:
213 if (message_header->size != sizeof(VDAgentMouseState))
214 goto size_error;
215 uinput_do_mouse((VDAgentMouseState *)data, debug > 1);
216 break;
217 case VD_AGENT_MONITORS_CONFIG:
218 if (message_header->size < sizeof(VDAgentMonitorsConfig))
219 goto size_error;
220 do_monitors(port, chunk_header->port, message_header,
221 (VDAgentMonitorsConfig *)data);
222 break;
223 case VD_AGENT_ANNOUNCE_CAPABILITIES:
224 if (message_header->size < sizeof(VDAgentAnnounceCapabilities))
225 goto size_error;
226 do_capabilities(port, message_header,
227 (VDAgentAnnounceCapabilities *)data);
228 break;
229 case VD_AGENT_CLIPBOARD_GRAB:
230 case VD_AGENT_CLIPBOARD_REQUEST:
231 case VD_AGENT_CLIPBOARD:
232 case VD_AGENT_CLIPBOARD_RELEASE:
233 switch (message_header->type) {
234 case VD_AGENT_CLIPBOARD_GRAB:
235 min_size = sizeof(VDAgentClipboardGrab); break;
236 case VD_AGENT_CLIPBOARD_REQUEST:
237 min_size = sizeof(VDAgentClipboardRequest); break;
238 case VD_AGENT_CLIPBOARD:
239 min_size = sizeof(VDAgentClipboard); break;
241 if (message_header->size < min_size)
242 goto size_error;
243 do_clipboard(port, message_header, data);
244 break;
245 default:
246 if (debug)
247 fprintf(stderr, "unknown message type %d\n", message_header->type);
248 break;
251 return 0;
253 size_error:
254 fprintf(stderr, "read: invalid message size: %u for message type: %u\n",
255 message_header->size, message_header->type);
256 return 0;
259 /* vdagent client handling */
260 void do_client_clipboard(struct udscs_connection *conn,
261 struct udscs_message_header *header, const uint8_t *data)
263 const char *conn_session, *active_session;
265 if (!VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
266 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
267 goto error;
269 /* Check that this client is from the currently active session */
270 conn_session = (const char *)udscs_get_user_data(conn);
271 active_session = console_kit_get_active_session(console_kit);
272 if (!conn_session || !active_session) {
273 fprintf(stderr, "Could not get session info, ignoring agent clipboard request\n");
274 goto error;
276 if (strcmp(conn_session, active_session)) {
277 fprintf(stderr, "Clipboard request from agent which is not in the active session?\n");
278 goto error;
281 switch (header->type) {
282 case VDAGENTD_CLIPBOARD_GRAB:
283 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
284 VD_AGENT_CLIPBOARD_GRAB, 0,
285 data, header->size);
286 break;
287 case VDAGENTD_CLIPBOARD_REQUEST: {
288 VDAgentClipboardRequest req = { .type = header->opaque };
289 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
290 VD_AGENT_CLIPBOARD_REQUEST, 0,
291 (uint8_t *)&req, sizeof(req));
292 break;
294 case VDAGENTD_CLIPBOARD_DATA: {
295 VDAgentClipboard *clipboard;
296 uint32_t size = sizeof(*clipboard) + header->size;
298 clipboard = calloc(1, size);
299 if (!clipboard) {
300 fprintf(stderr,
301 "out of memory allocating clipboard (write)\n");
302 return;
304 clipboard->type = header->opaque;
305 memcpy(clipboard->data, data, header->size);
307 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
308 VD_AGENT_CLIPBOARD, 0,
309 (uint8_t *)clipboard, size);
310 free(clipboard);
311 break;
313 case VDAGENTD_CLIPBOARD_RELEASE:
314 vdagent_virtio_port_write(virtio_port, VDP_CLIENT_PORT,
315 VD_AGENT_CLIPBOARD_RELEASE, 0, NULL, 0);
316 break;
319 return;
321 error:
322 if (header->type == VDAGENTD_CLIPBOARD_REQUEST) {
323 /* Let the agent know no answer is coming */
324 udscs_write(conn, VDAGENTD_CLIPBOARD_DATA,
325 VD_AGENT_CLIPBOARD_NONE, NULL, 0);
329 void client_connect(struct udscs_connection *conn)
331 uint32_t pid;
332 const char *session;
334 pid = udscs_get_peer_cred(conn).pid;
335 session = console_kit_session_for_pid(console_kit, pid);
336 udscs_set_user_data(conn, (void *)session);
338 /* We don't create the tablet until we've gotten the xorg resolution
339 from the vdagent client */
340 connection_count++;
342 if (mon_config)
343 udscs_write(conn, VDAGENTD_MONITORS_CONFIG, 0, (uint8_t *)mon_config,
344 sizeof(VDAgentMonitorsConfig) +
345 mon_config->num_of_monitors * sizeof(VDAgentMonConfig));
348 void client_disconnect(struct udscs_connection *conn)
350 connection_count--;
351 if (connection_count == 0) {
352 uinput_close();
353 vdagent_virtio_port_destroy(&virtio_port);
355 free(udscs_get_user_data(conn));
358 void client_read_complete(struct udscs_connection **connp,
359 struct udscs_message_header *header, const uint8_t *data)
361 switch (header->type) {
362 case VDAGENTD_GUEST_XORG_RESOLUTION: {
363 struct vdagentd_guest_xorg_resolution *res =
364 (struct vdagentd_guest_xorg_resolution *)data;
366 if (header->size != sizeof(*res)) {
367 fprintf(stderr,
368 "guest xorg resolution message has wrong size, disconnecting client\n");
369 udscs_destroy_connection(connp);
370 return;
373 /* Now that we know the xorg resolution setup the uinput device */
374 uinput_setup(uinput, res->width, res->height);
375 /* Now that we have a tablet and thus can forward mouse events,
376 we can open the vdagent virtio port. */
377 if (!virtio_port) {
378 virtio_port = vdagent_virtio_port_create(portdev,
379 virtio_port_read_complete,
380 NULL);
381 if (!virtio_port)
382 exit(1);
384 send_capabilities(virtio_port, 1);
386 break;
388 case VDAGENTD_CLIPBOARD_GRAB:
389 case VDAGENTD_CLIPBOARD_REQUEST:
390 case VDAGENTD_CLIPBOARD_DATA:
391 case VDAGENTD_CLIPBOARD_RELEASE:
392 do_client_clipboard(*connp, header, data);
393 break;
394 default:
395 fprintf(stderr, "unknown message from vdagent client: %u, ignoring\n",
396 header->type);
400 /* main */
402 static void usage(FILE *fp)
404 fprintf(fp,
405 "vdagent -- handle spice agent mouse via uinput\n"
406 "options:\n"
407 " -h print this text\n"
408 " -d print debug messages (and don't daemonize)\n"
409 " -s <port> set virtio serial port [%s]\n"
410 " -u <dev> set uinput device [%s]\n",
411 portdev, uinput);
414 void daemonize(void)
416 /* detach from terminal */
417 switch (fork()) {
418 case -1:
419 perror("fork");
420 exit(1);
421 case 0:
422 close(0); close(1); close(2);
423 setsid();
424 open("/dev/null",O_RDWR); dup(0); dup(0);
425 break;
426 default:
427 exit(0);
431 void main_loop(void)
433 fd_set readfds, writefds;
434 int n, nfds;
436 /* FIXME catch sigquit and set a flag to quit */
437 for (;;) {
438 FD_ZERO(&readfds);
439 FD_ZERO(&writefds);
441 nfds = udscs_server_fill_fds(server, &readfds, &writefds);
442 n = vdagent_virtio_port_fill_fds(virtio_port, &readfds, &writefds);
443 if (n >= nfds)
444 nfds = n + 1;
446 n = select(nfds, &readfds, &writefds, NULL, NULL);
447 if (n == -1) {
448 if (errno == EINTR)
449 continue;
450 perror("select");
451 exit(1);
454 udscs_server_handle_fds(server, &readfds, &writefds);
455 vdagent_virtio_port_handle_fds(&virtio_port, &readfds, &writefds);
459 int main(int argc, char *argv[])
461 int c;
463 for (;;) {
464 if (-1 == (c = getopt(argc, argv, "dhx:y:s:u:")))
465 break;
466 switch (c) {
467 case 'd':
468 debug++;
469 break;
470 case 's':
471 portdev = optarg;
472 break;
473 case 'u':
474 uinput = optarg;
475 break;
476 case 'h':
477 usage(stdout);
478 exit(0);
479 default:
480 usage(stderr);
481 exit(1);
485 /* Setup communication with vdagent process(es) */
486 server = udscs_create_server(VDAGENTD_SOCKET, client_connect,
487 client_read_complete, client_disconnect,
488 vdagentd_messages, VDAGENTD_NO_MESSAGES,
489 debug? stderr:NULL, stderr);
490 if (!server)
491 exit(1);
492 if (chmod(VDAGENTD_SOCKET, 0666)) {
493 fprintf(stderr, "could not change permissions on %s: %s\n",
494 VDAGENTD_SOCKET, strerror(errno));
497 console_kit = console_kit_create(stderr);
498 if (!console_kit)
499 fprintf(stderr, "Could not connect to console kit, disabling copy and paste support\n");
501 if (!debug)
502 daemonize();
504 main_loop();
506 udscs_destroy_server(server);
508 return 0;