1 /* 2 * Copyright (C) 2010 Red Hat, Inc. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License as 6 * published by the Free Software Foundation; either version 2 or 7 * (at your option) version 3 of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include <spice.h> 19 #include <spice-experimental.h> 20 21 #include <netdb.h> 22 #include <pthread.h> 23 24 #include "qemu-common.h" 25 #include "qemu-spice.h" 26 #include "qemu-timer.h" 27 #include "qemu-queue.h" 28 #include "qemu-x509.h" 29 #include "qemu_socket.h" 30 #include "qint.h" 31 #include "qbool.h" 32 #include "qstring.h" 33 #include "qjson.h" 34 #include "notify.h" 35 #include "migration.h" 36 #include "monitor.h" 37 #include "hw/hw.h" 38 39 /* core bits */ 40 41 static SpiceServer *spice_server; 42 static Notifier migration_state; 43 static const char *auth = "spice"; 44 static char *auth_passwd; 45 static time_t auth_expires = TIME_MAX; 46 int using_spice = 0; 47 48 static pthread_t me; 49 50 struct SpiceTimer { 51 QEMUTimer *timer; 52 QTAILQ_ENTRY(SpiceTimer) next; 53 }; 54 static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers); 55 56 static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) 57 { 58 SpiceTimer *timer; 59 60 timer = g_malloc0(sizeof(*timer)); 61 timer->timer = qemu_new_timer_ms(rt_clock, func, opaque); 62 QTAILQ_INSERT_TAIL(&timers, timer, next); 63 return timer; 64 } 65 66 static void timer_start(SpiceTimer *timer, uint32_t ms) 67 { 68 qemu_mod_timer(timer->timer, qemu_get_clock_ms(rt_clock) + ms); 69 } 70 71 static void timer_cancel(SpiceTimer *timer) 72 { 73 qemu_del_timer(timer->timer); 74 } 75 76 static void timer_remove(SpiceTimer *timer) 77 { 78 qemu_del_timer(timer->timer); 79 qemu_free_timer(timer->timer); 80 QTAILQ_REMOVE(&timers, timer, next); 81 g_free(timer); 82 } 83 84 struct SpiceWatch { 85 int fd; 86 int event_mask; 87 SpiceWatchFunc func; 88 void *opaque; 89 QTAILQ_ENTRY(SpiceWatch) next; 90 }; 91 static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches); 92 93 static void watch_read(void *opaque) 94 { 95 SpiceWatch *watch = opaque; 96 watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque); 97 } 98 99 static void watch_write(void *opaque) 100 { 101 SpiceWatch *watch = opaque; 102 watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque); 103 } 104 105 static void watch_update_mask(SpiceWatch *watch, int event_mask) 106 { 107 IOHandler *on_read = NULL; 108 IOHandler *on_write = NULL; 109 110 watch->event_mask = event_mask; 111 if (watch->event_mask & SPICE_WATCH_EVENT_READ) { 112 on_read = watch_read; 113 } 114 if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) { 115 on_write = watch_write; 116 } 117 qemu_set_fd_handler(watch->fd, on_read, on_write, watch); 118 } 119 120 static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque) 121 { 122 SpiceWatch *watch; 123 124 watch = g_malloc0(sizeof(*watch)); 125 watch->fd = fd; 126 watch->func = func; 127 watch->opaque = opaque; 128 QTAILQ_INSERT_TAIL(&watches, watch, next); 129 130 watch_update_mask(watch, event_mask); 131 return watch; 132 } 133 134 static void watch_remove(SpiceWatch *watch) 135 { 136 watch_update_mask(watch, 0); 137 QTAILQ_REMOVE(&watches, watch, next); 138 g_free(watch); 139 } 140 141 #if SPICE_INTERFACE_CORE_MINOR >= 3 142 143 typedef struct ChannelList ChannelList; 144 struct ChannelList { 145 SpiceChannelEventInfo *info; 146 QTAILQ_ENTRY(ChannelList) link; 147 }; 148 static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list); 149 150 static void channel_list_add(SpiceChannelEventInfo *info) 151 { 152 ChannelList *item; 153 154 item = g_malloc0(sizeof(*item)); 155 item->info = info; 156 QTAILQ_INSERT_TAIL(&channel_list, item, link); 157 } 158 159 static void channel_list_del(SpiceChannelEventInfo *info) 160 { 161 ChannelList *item; 162 163 QTAILQ_FOREACH(item, &channel_list, link) { 164 if (item->info != info) { 165 continue; 166 } 167 QTAILQ_REMOVE(&channel_list, item, link); 168 g_free(item); 169 return; 170 } 171 } 172 173 static void add_addr_info(QDict *dict, struct sockaddr *addr, int len) 174 { 175 char host[NI_MAXHOST], port[NI_MAXSERV]; 176 const char *family; 177 178 getnameinfo(addr, len, host, sizeof(host), port, sizeof(port), 179 NI_NUMERICHOST | NI_NUMERICSERV); 180 family = inet_strfamily(addr->sa_family); 181 182 qdict_put(dict, "host", qstring_from_str(host)); 183 qdict_put(dict, "port", qstring_from_str(port)); 184 qdict_put(dict, "family", qstring_from_str(family)); 185 } 186 187 static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info) 188 { 189 int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; 190 191 qdict_put(dict, "connection-id", qint_from_int(info->connection_id)); 192 qdict_put(dict, "channel-type", qint_from_int(info->type)); 193 qdict_put(dict, "channel-id", qint_from_int(info->id)); 194 qdict_put(dict, "tls", qbool_from_int(tls)); 195 } 196 197 static QList *channel_list_get(void) 198 { 199 ChannelList *item; 200 QList *list; 201 QDict *dict; 202 203 list = qlist_new(); 204 QTAILQ_FOREACH(item, &channel_list, link) { 205 dict = qdict_new(); 206 add_addr_info(dict, &item->info->paddr, item->info->plen); 207 add_channel_info(dict, item->info); 208 qlist_append(list, dict); 209 } 210 return list; 211 } 212 213 static void channel_event(int event, SpiceChannelEventInfo *info) 214 { 215 static const int qevent[] = { 216 [ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED, 217 [ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED, 218 [ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED, 219 }; 220 QDict *server, *client; 221 QObject *data; 222 223 /* 224 * Spice server might have called us from spice worker thread 225 * context (happens on display channel disconnects). Spice should 226 * not do that. It isn't that easy to fix it in spice and even 227 * when it is fixed we still should cover the already released 228 * spice versions. So detect that we've been called from another 229 * thread and grab the iothread lock if so before calling qemu 230 * functions. 231 */ 232 bool need_lock = !pthread_equal(me, pthread_self()); 233 if (need_lock) { 234 qemu_mutex_lock_iothread(); 235 } 236 237 client = qdict_new(); 238 add_addr_info(client, &info->paddr, info->plen); 239 240 server = qdict_new(); 241 add_addr_info(server, &info->laddr, info->llen); 242 243 if (event == SPICE_CHANNEL_EVENT_INITIALIZED) { 244 qdict_put(server, "auth", qstring_from_str(auth)); 245 add_channel_info(client, info); 246 channel_list_add(info); 247 } 248 if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) { 249 channel_list_del(info); 250 } 251 252 data = qobject_from_jsonf("{ 'client': %p, 'server': %p }", 253 QOBJECT(client), QOBJECT(server)); 254 monitor_protocol_event(qevent[event], data); 255 qobject_decref(data); 256 257 if (need_lock) { 258 qemu_mutex_unlock_iothread(); 259 } 260 } 261 262 #else /* SPICE_INTERFACE_CORE_MINOR >= 3 */ 263 264 static QList *channel_list_get(void) 265 { 266 return NULL; 267 } 268 269 #endif /* SPICE_INTERFACE_CORE_MINOR >= 3 */ 270 271 static SpiceCoreInterface core_interface = { 272 .base.type = SPICE_INTERFACE_CORE, 273 .base.description = "qemu core services", 274 .base.major_version = SPICE_INTERFACE_CORE_MAJOR, 275 .base.minor_version = SPICE_INTERFACE_CORE_MINOR, 276 277 .timer_add = timer_add, 278 .timer_start = timer_start, 279 .timer_cancel = timer_cancel, 280 .timer_remove = timer_remove, 281 282 .watch_add = watch_add, 283 .watch_update_mask = watch_update_mask, 284 .watch_remove = watch_remove, 285 286 #if SPICE_INTERFACE_CORE_MINOR >= 3 287 .channel_event = channel_event, 288 #endif 289 }; 290 291 /* config string parsing */ 292 293 static int name2enum(const char *string, const char *table[], int entries) 294 { 295 int i; 296 297 if (string) { 298 for (i = 0; i < entries; i++) { 299 if (!table[i]) { 300 continue; 301 } 302 if (strcmp(string, table[i]) != 0) { 303 continue; 304 } 305 return i; 306 } 307 } 308 return -1; 309 } 310 311 static int parse_name(const char *string, const char *optname, 312 const char *table[], int entries) 313 { 314 int value = name2enum(string, table, entries); 315 316 if (value != -1) { 317 return value; 318 } 319 fprintf(stderr, "spice: invalid %s: %s\n", optname, string); 320 exit(1); 321 } 322 323 static const char *stream_video_names[] = { 324 [ SPICE_STREAM_VIDEO_OFF ] = "off", 325 [ SPICE_STREAM_VIDEO_ALL ] = "all", 326 [ SPICE_STREAM_VIDEO_FILTER ] = "filter", 327 }; 328 #define parse_stream_video(_name) \ 329 name2enum(_name, stream_video_names, ARRAY_SIZE(stream_video_names)) 330 331 static const char *compression_names[] = { 332 [ SPICE_IMAGE_COMPRESS_OFF ] = "off", 333 [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz", 334 [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz", 335 [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic", 336 [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz", 337 [ SPICE_IMAGE_COMPRESS_LZ ] = "lz", 338 }; 339 #define parse_compression(_name) \ 340 parse_name(_name, "image compression", \ 341 compression_names, ARRAY_SIZE(compression_names)) 342 343 static const char *wan_compression_names[] = { 344 [ SPICE_WAN_COMPRESSION_AUTO ] = "auto", 345 [ SPICE_WAN_COMPRESSION_NEVER ] = "never", 346 [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always", 347 }; 348 #define parse_wan_compression(_name) \ 349 parse_name(_name, "wan compression", \ 350 wan_compression_names, ARRAY_SIZE(wan_compression_names)) 351 352 /* functions for the rest of qemu */ 353 354 static void info_spice_iter(QObject *obj, void *opaque) 355 { 356 QDict *client; 357 Monitor *mon = opaque; 358 359 client = qobject_to_qdict(obj); 360 monitor_printf(mon, "Channel:\n"); 361 monitor_printf(mon, " address: %s:%s%s\n", 362 qdict_get_str(client, "host"), 363 qdict_get_str(client, "port"), 364 qdict_get_bool(client, "tls") ? " [tls]" : ""); 365 monitor_printf(mon, " session: %" PRId64 "\n", 366 qdict_get_int(client, "connection-id")); 367 monitor_printf(mon, " channel: %d:%d\n", 368 (int)qdict_get_int(client, "channel-type"), 369 (int)qdict_get_int(client, "channel-id")); 370 } 371 372 void do_info_spice_print(Monitor *mon, const QObject *data) 373 { 374 QDict *server; 375 QList *channels; 376 const char *host; 377 int port; 378 379 server = qobject_to_qdict(data); 380 if (qdict_get_bool(server, "enabled") == 0) { 381 monitor_printf(mon, "Server: disabled\n"); 382 return; 383 } 384 385 monitor_printf(mon, "Server:\n"); 386 host = qdict_get_str(server, "host"); 387 port = qdict_get_try_int(server, "port", -1); 388 if (port != -1) { 389 monitor_printf(mon, " address: %s:%d\n", host, port); 390 } 391 port = qdict_get_try_int(server, "tls-port", -1); 392 if (port != -1) { 393 monitor_printf(mon, " address: %s:%d [tls]\n", host, port); 394 } 395 monitor_printf(mon, " auth: %s\n", qdict_get_str(server, "auth")); 396 monitor_printf(mon, " compiled: %s\n", 397 qdict_get_str(server, "compiled-version")); 398 399 channels = qdict_get_qlist(server, "channels"); 400 if (qlist_empty(channels)) { 401 monitor_printf(mon, "Channels: none\n"); 402 } else { 403 qlist_iter(channels, info_spice_iter, mon); 404 } 405 } 406 407 void do_info_spice(Monitor *mon, QObject **ret_data) 408 { 409 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 410 QDict *server; 411 QList *clist; 412 const char *addr; 413 int port, tls_port; 414 char version_string[20]; /* 12 = |255.255.255\0| is the max */ 415 416 if (!spice_server) { 417 *ret_data = qobject_from_jsonf("{ 'enabled': false }"); 418 return; 419 } 420 421 addr = qemu_opt_get(opts, "addr"); 422 port = qemu_opt_get_number(opts, "port", 0); 423 tls_port = qemu_opt_get_number(opts, "tls-port", 0); 424 clist = channel_list_get(); 425 426 server = qdict_new(); 427 qdict_put(server, "enabled", qbool_from_int(true)); 428 qdict_put(server, "auth", qstring_from_str(auth)); 429 qdict_put(server, "host", qstring_from_str(addr ? addr : "0.0.0.0")); 430 snprintf(version_string, sizeof(version_string), "%d.%d.%d", 431 (SPICE_SERVER_VERSION & 0xff0000) >> 16, 432 (SPICE_SERVER_VERSION & 0xff00) >> 8, 433 SPICE_SERVER_VERSION & 0xff); 434 qdict_put(server, "compiled-version", qstring_from_str(version_string)); 435 if (port) { 436 qdict_put(server, "port", qint_from_int(port)); 437 } 438 if (tls_port) { 439 qdict_put(server, "tls-port", qint_from_int(tls_port)); 440 } 441 if (clist) { 442 qdict_put(server, "channels", clist); 443 } 444 445 *ret_data = QOBJECT(server); 446 } 447 448 static void migration_state_notifier(Notifier *notifier, void *data) 449 { 450 int state = get_migration_state(); 451 452 if (state == MIG_STATE_COMPLETED) { 453 #if SPICE_SERVER_VERSION >= 0x000701 /* 0.7.1 */ 454 spice_server_migrate_switch(spice_server); 455 #endif 456 } 457 } 458 459 int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, 460 const char *subject) 461 { 462 return spice_server_migrate_info(spice_server, hostname, 463 port, tls_port, subject); 464 } 465 466 static int add_channel(const char *name, const char *value, void *opaque) 467 { 468 int security = 0; 469 int rc; 470 471 if (strcmp(name, "tls-channel") == 0) { 472 security = SPICE_CHANNEL_SECURITY_SSL; 473 } 474 if (strcmp(name, "plaintext-channel") == 0) { 475 security = SPICE_CHANNEL_SECURITY_NONE; 476 } 477 if (security == 0) { 478 return 0; 479 } 480 if (strcmp(value, "default") == 0) { 481 rc = spice_server_set_channel_security(spice_server, NULL, security); 482 } else { 483 rc = spice_server_set_channel_security(spice_server, value, security); 484 } 485 if (rc != 0) { 486 fprintf(stderr, "spice: failed to set channel security for %s\n", value); 487 exit(1); 488 } 489 return 0; 490 } 491 492 void qemu_spice_init(void) 493 { 494 QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); 495 const char *password, *str, *x509_dir, *addr, 496 *x509_key_password = NULL, 497 *x509_dh_file = NULL, 498 *tls_ciphers = NULL; 499 char *x509_key_file = NULL, 500 *x509_cert_file = NULL, 501 *x509_cacert_file = NULL; 502 int port, tls_port, len, addr_flags; 503 spice_image_compression_t compression; 504 spice_wan_compression_t wan_compr; 505 506 me = pthread_self(); 507 508 if (!opts) { 509 return; 510 } 511 port = qemu_opt_get_number(opts, "port", 0); 512 tls_port = qemu_opt_get_number(opts, "tls-port", 0); 513 if (!port && !tls_port) { 514 fprintf(stderr, "neither port nor tls-port specified for spice."); 515 exit(1); 516 } 517 if (port < 0 || port > 65535) { 518 fprintf(stderr, "spice port is out of range"); 519 exit(1); 520 } 521 if (tls_port < 0 || tls_port > 65535) { 522 fprintf(stderr, "spice tls-port is out of range"); 523 exit(1); 524 } 525 password = qemu_opt_get(opts, "password"); 526 527 if (tls_port) { 528 x509_dir = qemu_opt_get(opts, "x509-dir"); 529 if (NULL == x509_dir) { 530 x509_dir = "."; 531 } 532 len = strlen(x509_dir) + 32; 533 534 str = qemu_opt_get(opts, "x509-key-file"); 535 if (str) { 536 x509_key_file = g_strdup(str); 537 } else { 538 x509_key_file = g_malloc(len); 539 snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE); 540 } 541 542 str = qemu_opt_get(opts, "x509-cert-file"); 543 if (str) { 544 x509_cert_file = g_strdup(str); 545 } else { 546 x509_cert_file = g_malloc(len); 547 snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE); 548 } 549 550 str = qemu_opt_get(opts, "x509-cacert-file"); 551 if (str) { 552 x509_cacert_file = g_strdup(str); 553 } else { 554 x509_cacert_file = g_malloc(len); 555 snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE); 556 } 557 558 x509_key_password = qemu_opt_get(opts, "x509-key-password"); 559 x509_dh_file = qemu_opt_get(opts, "x509-dh-file"); 560 tls_ciphers = qemu_opt_get(opts, "tls-ciphers"); 561 } 562 563 addr = qemu_opt_get(opts, "addr"); 564 addr_flags = 0; 565 if (qemu_opt_get_bool(opts, "ipv4", 0)) { 566 addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY; 567 } else if (qemu_opt_get_bool(opts, "ipv6", 0)) { 568 addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY; 569 } 570 571 spice_server = spice_server_new(); 572 spice_server_set_addr(spice_server, addr ? addr : "", addr_flags); 573 if (port) { 574 spice_server_set_port(spice_server, port); 575 } 576 if (tls_port) { 577 spice_server_set_tls(spice_server, tls_port, 578 x509_cacert_file, 579 x509_cert_file, 580 x509_key_file, 581 x509_key_password, 582 x509_dh_file, 583 tls_ciphers); 584 } 585 if (password) { 586 spice_server_set_ticket(spice_server, password, 0, 0, 0); 587 } 588 if (qemu_opt_get_bool(opts, "sasl", 0)) { 589 #if SPICE_SERVER_VERSION >= 0x000900 /* 0.9.0 */ 590 if (spice_server_set_sasl_appname(spice_server, "qemu") == -1 || 591 spice_server_set_sasl(spice_server, 1) == -1) { 592 fprintf(stderr, "spice: failed to enable sasl\n"); 593 exit(1); 594 } 595 #else 596 fprintf(stderr, "spice: sasl is not available (spice >= 0.9 required)\n"); 597 exit(1); 598 #endif 599 } 600 if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) { 601 auth = "none"; 602 spice_server_set_noauth(spice_server); 603 } 604 605 #if SPICE_SERVER_VERSION >= 0x000801 606 if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) { 607 spice_server_set_agent_copypaste(spice_server, false); 608 } 609 #endif 610 611 compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ; 612 str = qemu_opt_get(opts, "image-compression"); 613 if (str) { 614 compression = parse_compression(str); 615 } 616 spice_server_set_image_compression(spice_server, compression); 617 618 wan_compr = SPICE_WAN_COMPRESSION_AUTO; 619 str = qemu_opt_get(opts, "jpeg-wan-compression"); 620 if (str) { 621 wan_compr = parse_wan_compression(str); 622 } 623 spice_server_set_jpeg_compression(spice_server, wan_compr); 624 625 wan_compr = SPICE_WAN_COMPRESSION_AUTO; 626 str = qemu_opt_get(opts, "zlib-glz-wan-compression"); 627 if (str) { 628 wan_compr = parse_wan_compression(str); 629 } 630 spice_server_set_zlib_glz_compression(spice_server, wan_compr); 631 632 str = qemu_opt_get(opts, "streaming-video"); 633 if (str) { 634 int streaming_video = parse_stream_video(str); 635 spice_server_set_streaming_video(spice_server, streaming_video); 636 } 637 638 spice_server_set_agent_mouse 639 (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); 640 spice_server_set_playback_compression 641 (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1)); 642 643 qemu_opt_foreach(opts, add_channel, NULL, 0); 644 645 if (0 != spice_server_init(spice_server, &core_interface)) { 646 fprintf(stderr, "failed to initialize spice server"); 647 exit(1); 648 }; 649 using_spice = 1; 650 651 migration_state.notify = migration_state_notifier; 652 add_migration_state_change_notifier(&migration_state); 653 654 qemu_spice_input_init(); 655 qemu_spice_audio_init(); 656 657 g_free(x509_key_file); 658 g_free(x509_cert_file); 659 g_free(x509_cacert_file); 660 } 661 662 int qemu_spice_add_interface(SpiceBaseInstance *sin) 663 { 664 if (!spice_server) { 665 if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) { 666 fprintf(stderr, "Oops: spice configured but not active\n"); 667 exit(1); 668 } 669 /* 670 * Create a spice server instance. 671 * It does *not* listen on the network. 672 * It handles QXL local rendering only. 673 * 674 * With a command line like '-vnc :0 -vga qxl' you'll end up here. 675 */ 676 spice_server = spice_server_new(); 677 spice_server_init(spice_server, &core_interface); 678 } 679 return spice_server_add_interface(spice_server, sin); 680 } 681 682 static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) 683 { 684 time_t lifetime, now = time(NULL); 685 char *passwd; 686 687 if (now < auth_expires) { 688 passwd = auth_passwd; 689 lifetime = (auth_expires - now); 690 if (lifetime > INT_MAX) { 691 lifetime = INT_MAX; 692 } 693 } else { 694 passwd = NULL; 695 lifetime = 1; 696 } 697 return spice_server_set_ticket(spice_server, passwd, lifetime, 698 fail_if_conn, disconnect_if_conn); 699 } 700 701 int qemu_spice_set_passwd(const char *passwd, 702 bool fail_if_conn, bool disconnect_if_conn) 703 { 704 free(auth_passwd); 705 auth_passwd = strdup(passwd); 706 return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn); 707 } 708 709 int qemu_spice_set_pw_expire(time_t expires) 710 { 711 auth_expires = expires; 712 return qemu_spice_set_ticket(false, false); 713 } 714 715 static void spice_register_config(void) 716 { 717 qemu_add_opts(&qemu_spice_opts); 718 } 719 machine_init(spice_register_config); 720 721 static void spice_initialize(void) 722 { 723 qemu_spice_init(); 724 } 725 device_init(spice_initialize); 726