qemu/ui/vnc-auth-sasl.c
<<
>>
Prefs
   1/*
   2 * QEMU VNC display driver: SASL auth protocol
   3 *
   4 * Copyright (C) 2009 Red Hat, Inc
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24
  25#include "qemu/osdep.h"
  26#include "qapi/error.h"
  27#include "authz/base.h"
  28#include "vnc.h"
  29#include "trace.h"
  30
  31/* Max amount of data we send/recv for SASL steps to prevent DOS */
  32#define SASL_DATA_MAX_LEN (1024 * 1024)
  33
  34
  35void vnc_sasl_client_cleanup(VncState *vs)
  36{
  37    if (vs->sasl.conn) {
  38        vs->sasl.runSSF = false;
  39        vs->sasl.wantSSF = false;
  40        vs->sasl.waitWriteSSF = 0;
  41        vs->sasl.encodedLength = vs->sasl.encodedOffset = 0;
  42        vs->sasl.encoded = NULL;
  43        g_free(vs->sasl.username);
  44        g_free(vs->sasl.mechlist);
  45        vs->sasl.username = vs->sasl.mechlist = NULL;
  46        sasl_dispose(&vs->sasl.conn);
  47        vs->sasl.conn = NULL;
  48    }
  49}
  50
  51
  52size_t vnc_client_write_sasl(VncState *vs)
  53{
  54    size_t ret;
  55
  56    VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
  57              "Encoded: %p size %d offset %d\n",
  58              vs->output.buffer, vs->output.capacity, vs->output.offset,
  59              vs->sasl.encoded, vs->sasl.encodedLength, vs->sasl.encodedOffset);
  60
  61    if (!vs->sasl.encoded) {
  62        int err;
  63        err = sasl_encode(vs->sasl.conn,
  64                          (char *)vs->output.buffer,
  65                          vs->output.offset,
  66                          (const char **)&vs->sasl.encoded,
  67                          &vs->sasl.encodedLength);
  68        if (err != SASL_OK)
  69            return vnc_client_io_error(vs, -1, NULL);
  70
  71        vs->sasl.encodedRawLength = vs->output.offset;
  72        vs->sasl.encodedOffset = 0;
  73    }
  74
  75    ret = vnc_client_write_buf(vs,
  76                               vs->sasl.encoded + vs->sasl.encodedOffset,
  77                               vs->sasl.encodedLength - vs->sasl.encodedOffset);
  78    if (!ret)
  79        return 0;
  80
  81    vs->sasl.encodedOffset += ret;
  82    if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
  83        bool throttled = vs->force_update_offset != 0;
  84        size_t offset;
  85        if (vs->sasl.encodedRawLength >= vs->force_update_offset) {
  86            vs->force_update_offset = 0;
  87        } else {
  88            vs->force_update_offset -= vs->sasl.encodedRawLength;
  89        }
  90        if (throttled && vs->force_update_offset == 0) {
  91            trace_vnc_client_unthrottle_forced(vs, vs->ioc);
  92        }
  93        offset = vs->output.offset;
  94        buffer_advance(&vs->output, vs->sasl.encodedRawLength);
  95        if (offset >= vs->throttle_output_offset &&
  96            vs->output.offset < vs->throttle_output_offset) {
  97            trace_vnc_client_unthrottle_incremental(vs, vs->ioc,
  98                                                    vs->output.offset);
  99        }
 100        vs->sasl.encoded = NULL;
 101        vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
 102    }
 103
 104    /* Can't merge this block with one above, because
 105     * someone might have written more unencrypted
 106     * data in vs->output while we were processing
 107     * SASL encoded output
 108     */
 109    if (vs->output.offset == 0) {
 110        if (vs->ioc_tag) {
 111            g_source_remove(vs->ioc_tag);
 112        }
 113        vs->ioc_tag = qio_channel_add_watch(
 114            vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
 115            vnc_client_io, vs, NULL);
 116    }
 117
 118    return ret;
 119}
 120
 121
 122size_t vnc_client_read_sasl(VncState *vs)
 123{
 124    size_t ret;
 125    uint8_t encoded[4096];
 126    const char *decoded;
 127    unsigned int decodedLen;
 128    int err;
 129
 130    ret = vnc_client_read_buf(vs, encoded, sizeof(encoded));
 131    if (!ret)
 132        return 0;
 133
 134    err = sasl_decode(vs->sasl.conn,
 135                      (char *)encoded, ret,
 136                      &decoded, &decodedLen);
 137
 138    if (err != SASL_OK)
 139        return vnc_client_io_error(vs, -1, NULL);
 140    VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n",
 141              encoded, ret, decoded, decodedLen);
 142    buffer_reserve(&vs->input, decodedLen);
 143    buffer_append(&vs->input, decoded, decodedLen);
 144    return decodedLen;
 145}
 146
 147
 148static int vnc_auth_sasl_check_access(VncState *vs)
 149{
 150    const void *val;
 151    int rv;
 152    Error *err = NULL;
 153    bool allow;
 154
 155    rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
 156    if (rv != SASL_OK) {
 157        trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
 158                            sasl_errstring(rv, NULL, NULL));
 159        return -1;
 160    }
 161    if (val == NULL) {
 162        trace_vnc_auth_fail(vs, vs->auth, "No SASL username set", "");
 163        return -1;
 164    }
 165
 166    vs->sasl.username = g_strdup((const char*)val);
 167    trace_vnc_auth_sasl_username(vs, vs->sasl.username);
 168
 169    if (vs->vd->sasl.authzid == NULL) {
 170        trace_vnc_auth_sasl_acl(vs, 1);
 171        return 0;
 172    }
 173
 174    allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
 175                                    vs->sasl.username, &err);
 176    if (err) {
 177        trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
 178                            error_get_pretty(err));
 179        error_free(err);
 180        return -1;
 181    }
 182
 183    trace_vnc_auth_sasl_acl(vs, allow);
 184    return allow ? 0 : -1;
 185}
 186
 187static int vnc_auth_sasl_check_ssf(VncState *vs)
 188{
 189    const void *val;
 190    int err, ssf;
 191
 192    if (!vs->sasl.wantSSF)
 193        return 1;
 194
 195    err = sasl_getprop(vs->sasl.conn, SASL_SSF, &val);
 196    if (err != SASL_OK)
 197        return 0;
 198
 199    ssf = *(const int *)val;
 200
 201    trace_vnc_auth_sasl_ssf(vs, ssf);
 202
 203    if (ssf < 56)
 204        return 0; /* 56 is good for Kerberos */
 205
 206    /* Only setup for read initially, because we're about to send an RPC
 207     * reply which must be in plain text. When the next incoming RPC
 208     * arrives, we'll switch on writes too
 209     *
 210     * cf qemudClientReadSASL  in qemud.c
 211     */
 212    vs->sasl.runSSF = 1;
 213
 214    /* We have a SSF that's good enough */
 215    return 1;
 216}
 217
 218/*
 219 * Step Msg
 220 *
 221 * Input from client:
 222 *
 223 * u32 clientin-length
 224 * u8-array clientin-string
 225 *
 226 * Output to client:
 227 *
 228 * u32 serverout-length
 229 * u8-array serverout-strin
 230 * u8 continue
 231 */
 232
 233static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len);
 234
 235static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t len)
 236{
 237    uint32_t datalen = len;
 238    const char *serverout;
 239    unsigned int serveroutlen;
 240    int err;
 241    char *clientdata = NULL;
 242
 243    /* NB, distinction of NULL vs "" is *critical* in SASL */
 244    if (datalen) {
 245        clientdata = (char*)data;
 246        clientdata[datalen-1] = '\0'; /* Wire includes '\0', but make sure */
 247        datalen--; /* Don't count NULL byte when passing to _start() */
 248    }
 249
 250    err = sasl_server_step(vs->sasl.conn,
 251                           clientdata,
 252                           datalen,
 253                           &serverout,
 254                           &serveroutlen);
 255    trace_vnc_auth_sasl_step(vs, data, len, serverout, serveroutlen, err);
 256    if (err != SASL_OK &&
 257        err != SASL_CONTINUE) {
 258        trace_vnc_auth_fail(vs, vs->auth, "Cannot step SASL auth",
 259                            sasl_errdetail(vs->sasl.conn));
 260        sasl_dispose(&vs->sasl.conn);
 261        vs->sasl.conn = NULL;
 262        goto authabort;
 263    }
 264
 265    if (serveroutlen > SASL_DATA_MAX_LEN) {
 266        trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
 267        sasl_dispose(&vs->sasl.conn);
 268        vs->sasl.conn = NULL;
 269        goto authabort;
 270    }
 271
 272    if (serveroutlen) {
 273        vnc_write_u32(vs, serveroutlen + 1);
 274        vnc_write(vs, serverout, serveroutlen + 1);
 275    } else {
 276        vnc_write_u32(vs, 0);
 277    }
 278
 279    /* Whether auth is complete */
 280    vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
 281
 282    if (err == SASL_CONTINUE) {
 283        /* Wait for step length */
 284        vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
 285    } else {
 286        if (!vnc_auth_sasl_check_ssf(vs)) {
 287            trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
 288            goto authreject;
 289        }
 290
 291        /* Check the username access control list */
 292        if (vnc_auth_sasl_check_access(vs) < 0) {
 293            goto authreject;
 294        }
 295
 296        trace_vnc_auth_pass(vs, vs->auth);
 297        vnc_write_u32(vs, 0); /* Accept auth */
 298        /*
 299         * Delay writing in SSF encoded mode until pending output
 300         * buffer is written
 301         */
 302        if (vs->sasl.runSSF)
 303            vs->sasl.waitWriteSSF = vs->output.offset;
 304        start_client_init(vs);
 305    }
 306
 307    return 0;
 308
 309 authreject:
 310    vnc_write_u32(vs, 1); /* Reject auth */
 311    vnc_write_u32(vs, sizeof("Authentication failed"));
 312    vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
 313    vnc_flush(vs);
 314    vnc_client_error(vs);
 315    return -1;
 316
 317 authabort:
 318    vnc_client_error(vs);
 319    return -1;
 320}
 321
 322static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len)
 323{
 324    uint32_t steplen = read_u32(data, 0);
 325
 326    if (steplen > SASL_DATA_MAX_LEN) {
 327        trace_vnc_auth_fail(vs, vs->auth, "SASL step len too large", "");
 328        vnc_client_error(vs);
 329        return -1;
 330    }
 331
 332    if (steplen == 0)
 333        return protocol_client_auth_sasl_step(vs, NULL, 0);
 334    else
 335        vnc_read_when(vs, protocol_client_auth_sasl_step, steplen);
 336    return 0;
 337}
 338
 339/*
 340 * Start Msg
 341 *
 342 * Input from client:
 343 *
 344 * u32 clientin-length
 345 * u8-array clientin-string
 346 *
 347 * Output to client:
 348 *
 349 * u32 serverout-length
 350 * u8-array serverout-strin
 351 * u8 continue
 352 */
 353
 354#define SASL_DATA_MAX_LEN (1024 * 1024)
 355
 356static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t len)
 357{
 358    uint32_t datalen = len;
 359    const char *serverout;
 360    unsigned int serveroutlen;
 361    int err;
 362    char *clientdata = NULL;
 363
 364    /* NB, distinction of NULL vs "" is *critical* in SASL */
 365    if (datalen) {
 366        clientdata = (char*)data;
 367        clientdata[datalen-1] = '\0'; /* Should be on wire, but make sure */
 368        datalen--; /* Don't count NULL byte when passing to _start() */
 369    }
 370
 371    err = sasl_server_start(vs->sasl.conn,
 372                            vs->sasl.mechlist,
 373                            clientdata,
 374                            datalen,
 375                            &serverout,
 376                            &serveroutlen);
 377    trace_vnc_auth_sasl_start(vs, data, len, serverout, serveroutlen, err);
 378    if (err != SASL_OK &&
 379        err != SASL_CONTINUE) {
 380        trace_vnc_auth_fail(vs, vs->auth, "Cannot start SASL auth",
 381                            sasl_errdetail(vs->sasl.conn));
 382        sasl_dispose(&vs->sasl.conn);
 383        vs->sasl.conn = NULL;
 384        goto authabort;
 385    }
 386    if (serveroutlen > SASL_DATA_MAX_LEN) {
 387        trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
 388        sasl_dispose(&vs->sasl.conn);
 389        vs->sasl.conn = NULL;
 390        goto authabort;
 391    }
 392
 393    if (serveroutlen) {
 394        vnc_write_u32(vs, serveroutlen + 1);
 395        vnc_write(vs, serverout, serveroutlen + 1);
 396    } else {
 397        vnc_write_u32(vs, 0);
 398    }
 399
 400    /* Whether auth is complete */
 401    vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
 402
 403    if (err == SASL_CONTINUE) {
 404        /* Wait for step length */
 405        vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
 406    } else {
 407        if (!vnc_auth_sasl_check_ssf(vs)) {
 408            trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
 409            goto authreject;
 410        }
 411
 412        /* Check the username access control list */
 413        if (vnc_auth_sasl_check_access(vs) < 0) {
 414            goto authreject;
 415        }
 416
 417        trace_vnc_auth_pass(vs, vs->auth);
 418        vnc_write_u32(vs, 0); /* Accept auth */
 419        start_client_init(vs);
 420    }
 421
 422    return 0;
 423
 424 authreject:
 425    vnc_write_u32(vs, 1); /* Reject auth */
 426    vnc_write_u32(vs, sizeof("Authentication failed"));
 427    vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
 428    vnc_flush(vs);
 429    vnc_client_error(vs);
 430    return -1;
 431
 432 authabort:
 433    vnc_client_error(vs);
 434    return -1;
 435}
 436
 437static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len)
 438{
 439    uint32_t startlen = read_u32(data, 0);
 440
 441    if (startlen > SASL_DATA_MAX_LEN) {
 442        trace_vnc_auth_fail(vs, vs->auth, "SASL start len too large", "");
 443        vnc_client_error(vs);
 444        return -1;
 445    }
 446
 447    if (startlen == 0)
 448        return protocol_client_auth_sasl_start(vs, NULL, 0);
 449
 450    vnc_read_when(vs, protocol_client_auth_sasl_start, startlen);
 451    return 0;
 452}
 453
 454static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len)
 455{
 456    char *mechname = g_strndup((const char *) data, len);
 457    trace_vnc_auth_sasl_mech_choose(vs, mechname);
 458
 459    if (strncmp(vs->sasl.mechlist, mechname, len) == 0) {
 460        if (vs->sasl.mechlist[len] != '\0' &&
 461            vs->sasl.mechlist[len] != ',') {
 462            goto fail;
 463        }
 464    } else {
 465        char *offset = strstr(vs->sasl.mechlist, mechname);
 466        if (!offset) {
 467            goto fail;
 468        }
 469        if (offset[-1] != ',' ||
 470            (offset[len] != '\0'&&
 471             offset[len] != ',')) {
 472            goto fail;
 473        }
 474    }
 475
 476    g_free(vs->sasl.mechlist);
 477    vs->sasl.mechlist = mechname;
 478
 479    vnc_read_when(vs, protocol_client_auth_sasl_start_len, 4);
 480    return 0;
 481
 482 fail:
 483    trace_vnc_auth_fail(vs, vs->auth, "Unsupported mechname", mechname);
 484    vnc_client_error(vs);
 485    g_free(mechname);
 486    return -1;
 487}
 488
 489static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len)
 490{
 491    uint32_t mechlen = read_u32(data, 0);
 492
 493    if (mechlen > 100) {
 494        trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too long", "");
 495        vnc_client_error(vs);
 496        return -1;
 497    }
 498    if (mechlen < 1) {
 499        trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too short", "");
 500        vnc_client_error(vs);
 501        return -1;
 502    }
 503    vnc_read_when(vs, protocol_client_auth_sasl_mechname,mechlen);
 504    return 0;
 505}
 506
 507static char *
 508vnc_socket_ip_addr_string(QIOChannelSocket *ioc,
 509                          bool local,
 510                          Error **errp)
 511{
 512    SocketAddress *addr;
 513    char *ret;
 514
 515    if (local) {
 516        addr = qio_channel_socket_get_local_address(ioc, errp);
 517    } else {
 518        addr = qio_channel_socket_get_remote_address(ioc, errp);
 519    }
 520    if (!addr) {
 521        return NULL;
 522    }
 523
 524    if (addr->type != SOCKET_ADDRESS_TYPE_INET) {
 525        error_setg(errp, "Not an inet socket type");
 526        qapi_free_SocketAddress(addr);
 527        return NULL;
 528    }
 529    ret = g_strdup_printf("%s;%s", addr->u.inet.host, addr->u.inet.port);
 530    qapi_free_SocketAddress(addr);
 531    return ret;
 532}
 533
 534void start_auth_sasl(VncState *vs)
 535{
 536    const char *mechlist = NULL;
 537    sasl_security_properties_t secprops;
 538    int err;
 539    Error *local_err = NULL;
 540    char *localAddr, *remoteAddr;
 541    int mechlistlen;
 542
 543    /* Get local & remote client addresses in form  IPADDR;PORT */
 544    localAddr = vnc_socket_ip_addr_string(vs->sioc, true, &local_err);
 545    if (!localAddr) {
 546        trace_vnc_auth_fail(vs, vs->auth, "Cannot format local IP",
 547                            error_get_pretty(local_err));
 548        goto authabort;
 549    }
 550
 551    remoteAddr = vnc_socket_ip_addr_string(vs->sioc, false, &local_err);
 552    if (!remoteAddr) {
 553        trace_vnc_auth_fail(vs, vs->auth, "Cannot format remote IP",
 554                            error_get_pretty(local_err));
 555        g_free(localAddr);
 556        goto authabort;
 557    }
 558
 559    err = sasl_server_new("vnc",
 560                          NULL, /* FQDN - just delegates to gethostname */
 561                          NULL, /* User realm */
 562                          localAddr,
 563                          remoteAddr,
 564                          NULL, /* Callbacks, not needed */
 565                          SASL_SUCCESS_DATA,
 566                          &vs->sasl.conn);
 567    g_free(localAddr);
 568    g_free(remoteAddr);
 569    localAddr = remoteAddr = NULL;
 570
 571    if (err != SASL_OK) {
 572        trace_vnc_auth_fail(vs, vs->auth,  "SASL context setup failed",
 573                            sasl_errstring(err, NULL, NULL));
 574        vs->sasl.conn = NULL;
 575        goto authabort;
 576    }
 577
 578    /* Inform SASL that we've got an external SSF layer from TLS/x509 */
 579    if (vs->auth == VNC_AUTH_VENCRYPT &&
 580        vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) {
 581        int keysize;
 582        sasl_ssf_t ssf;
 583
 584        keysize = qcrypto_tls_session_get_key_size(vs->tls,
 585                                                   &local_err);
 586        if (keysize < 0) {
 587            trace_vnc_auth_fail(vs, vs->auth, "cannot TLS get cipher size",
 588                                error_get_pretty(local_err));
 589            sasl_dispose(&vs->sasl.conn);
 590            vs->sasl.conn = NULL;
 591            goto authabort;
 592        }
 593        ssf = keysize * CHAR_BIT; /* tls key size is bytes, sasl wants bits */
 594
 595        err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
 596        if (err != SASL_OK) {
 597            trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL external SSF",
 598                                sasl_errstring(err, NULL, NULL));
 599            sasl_dispose(&vs->sasl.conn);
 600            vs->sasl.conn = NULL;
 601            goto authabort;
 602        }
 603    } else {
 604        vs->sasl.wantSSF = 1;
 605    }
 606
 607    memset (&secprops, 0, sizeof secprops);
 608    /* Inform SASL that we've got an external SSF layer from TLS.
 609     *
 610     * Disable SSF, if using TLS+x509+SASL only. TLS without x509
 611     * is not sufficiently strong
 612     */
 613    if (vs->vd->is_unix ||
 614        (vs->auth == VNC_AUTH_VENCRYPT &&
 615         vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)) {
 616        /* If we've got TLS or UNIX domain sock, we don't care about SSF */
 617        secprops.min_ssf = 0;
 618        secprops.max_ssf = 0;
 619        secprops.maxbufsize = 8192;
 620        secprops.security_flags = 0;
 621    } else {
 622        /* Plain TCP, better get an SSF layer */
 623        secprops.min_ssf = 56; /* Good enough to require kerberos */
 624        secprops.max_ssf = 100000; /* Arbitrary big number */
 625        secprops.maxbufsize = 8192;
 626        /* Forbid any anonymous or trivially crackable auth */
 627        secprops.security_flags =
 628            SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
 629    }
 630
 631    err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
 632    if (err != SASL_OK) {
 633        trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL security props",
 634                            sasl_errstring(err, NULL, NULL));
 635        sasl_dispose(&vs->sasl.conn);
 636        vs->sasl.conn = NULL;
 637        goto authabort;
 638    }
 639
 640    err = sasl_listmech(vs->sasl.conn,
 641                        NULL, /* Don't need to set user */
 642                        "", /* Prefix */
 643                        ",", /* Separator */
 644                        "", /* Suffix */
 645                        &mechlist,
 646                        NULL,
 647                        NULL);
 648    if (err != SASL_OK) {
 649        trace_vnc_auth_fail(vs, vs->auth, "cannot list SASL mechanisms",
 650                            sasl_errdetail(vs->sasl.conn));
 651        sasl_dispose(&vs->sasl.conn);
 652        vs->sasl.conn = NULL;
 653        goto authabort;
 654    }
 655    trace_vnc_auth_sasl_mech_list(vs, mechlist);
 656
 657    vs->sasl.mechlist = g_strdup(mechlist);
 658    mechlistlen = strlen(mechlist);
 659    vnc_write_u32(vs, mechlistlen);
 660    vnc_write(vs, mechlist, mechlistlen);
 661    vnc_flush(vs);
 662
 663    vnc_read_when(vs, protocol_client_auth_sasl_mechname_len, 4);
 664
 665    return;
 666
 667 authabort:
 668    error_free(local_err);
 669    vnc_client_error(vs);
 670}
 671
 672
 673