vnc-auth-sasl.c 19.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * QEMU VNC display driver: SASL auth protocol
 *
 * Copyright (C) 2009 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 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.
 */

P
Peter Maydell 已提交
25
#include "qemu/osdep.h"
26
#include "qapi/error.h"
27
#include "vnc.h"
28
#include "trace.h"
29 30 31 32 33 34 35 36

/* Max amount of data we send/recv for SASL steps to prevent DOS */
#define SASL_DATA_MAX_LEN (1024 * 1024)


void vnc_sasl_client_cleanup(VncState *vs)
{
    if (vs->sasl.conn) {
37 38 39
        vs->sasl.runSSF = false;
        vs->sasl.wantSSF = false;
        vs->sasl.waitWriteSSF = 0;
40 41
        vs->sasl.encodedLength = vs->sasl.encodedOffset = 0;
        vs->sasl.encoded = NULL;
42
        g_free(vs->sasl.username);
43
        g_free(vs->sasl.mechlist);
44 45 46
        vs->sasl.username = vs->sasl.mechlist = NULL;
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
47 48 49 50
    }
}


51
size_t vnc_client_write_sasl(VncState *vs)
52
{
53
    size_t ret;
54

55 56
    VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
              "Encoded: %p size %d offset %d\n",
57 58
              vs->output.buffer, vs->output.capacity, vs->output.offset,
              vs->sasl.encoded, vs->sasl.encodedLength, vs->sasl.encodedOffset);
59 60

    if (!vs->sasl.encoded) {
61 62 63 64 65 66 67
        int err;
        err = sasl_encode(vs->sasl.conn,
                          (char *)vs->output.buffer,
                          vs->output.offset,
                          (const char **)&vs->sasl.encoded,
                          &vs->sasl.encodedLength);
        if (err != SASL_OK)
68
            return vnc_client_io_error(vs, -1, NULL);
69

70
        vs->sasl.encodedRawLength = vs->output.offset;
71
        vs->sasl.encodedOffset = 0;
72 73 74
    }

    ret = vnc_client_write_buf(vs,
75 76
                               vs->sasl.encoded + vs->sasl.encodedOffset,
                               vs->sasl.encodedLength - vs->sasl.encodedOffset);
77
    if (!ret)
78
        return 0;
79 80 81

    vs->sasl.encodedOffset += ret;
    if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
82 83 84 85 86
        if (vs->sasl.encodedRawLength >= vs->force_update_offset) {
            vs->force_update_offset = 0;
        } else {
            vs->force_update_offset -= vs->sasl.encodedRawLength;
        }
87
        vs->output.offset -= vs->sasl.encodedRawLength;
88 89
        vs->sasl.encoded = NULL;
        vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
90 91 92 93 94 95 96 97
    }

    /* Can't merge this block with one above, because
     * someone might have written more unencrypted
     * data in vs->output while we were processing
     * SASL encoded output
     */
    if (vs->output.offset == 0) {
98 99 100 101 102
        if (vs->ioc_tag) {
            g_source_remove(vs->ioc_tag);
        }
        vs->ioc_tag = qio_channel_add_watch(
            vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
103 104 105 106 107 108
    }

    return ret;
}


109
size_t vnc_client_read_sasl(VncState *vs)
110
{
111
    size_t ret;
112 113 114 115 116 117 118
    uint8_t encoded[4096];
    const char *decoded;
    unsigned int decodedLen;
    int err;

    ret = vnc_client_read_buf(vs, encoded, sizeof(encoded));
    if (!ret)
119
        return 0;
120 121

    err = sasl_decode(vs->sasl.conn,
122 123
                      (char *)encoded, ret,
                      &decoded, &decodedLen);
124 125

    if (err != SASL_OK)
126
        return vnc_client_io_error(vs, -1, NULL);
127
    VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n",
128
              encoded, ret, decoded, decodedLen);
129 130 131 132 133 134 135 136 137 138
    buffer_reserve(&vs->input, decodedLen);
    buffer_append(&vs->input, decoded, decodedLen);
    return decodedLen;
}


static int vnc_auth_sasl_check_access(VncState *vs)
{
    const void *val;
    int err;
139
    int allow;
140 141 142

    err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
    if (err != SASL_OK) {
143 144
        trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
                            sasl_errstring(err, NULL, NULL));
145
        return -1;
146 147
    }
    if (val == NULL) {
148
        trace_vnc_auth_fail(vs, vs->auth, "No SASL username set", "");
149
        return -1;
150 151
    }

152
    vs->sasl.username = g_strdup((const char*)val);
153
    trace_vnc_auth_sasl_username(vs, vs->sasl.username);
154

155
    if (vs->vd->sasl.acl == NULL) {
156
        trace_vnc_auth_sasl_acl(vs, 1);
157
        return 0;
158 159 160 161
    }

    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);

162
    trace_vnc_auth_sasl_acl(vs, allow);
163
    return allow ? 0 : -1;
164 165 166 167 168 169 170 171
}

static int vnc_auth_sasl_check_ssf(VncState *vs)
{
    const void *val;
    int err, ssf;

    if (!vs->sasl.wantSSF)
172
        return 1;
173 174 175

    err = sasl_getprop(vs->sasl.conn, SASL_SSF, &val);
    if (err != SASL_OK)
176
        return 0;
177 178

    ssf = *(const int *)val;
179 180 181

    trace_vnc_auth_sasl_ssf(vs, ssf);

182
    if (ssf < 56)
183
        return 0; /* 56 is good for Kerberos */
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

    /* Only setup for read initially, because we're about to send an RPC
     * reply which must be in plain text. When the next incoming RPC
     * arrives, we'll switch on writes too
     *
     * cf qemudClientReadSASL  in qemud.c
     */
    vs->sasl.runSSF = 1;

    /* We have a SSF that's good enough */
    return 1;
}

/*
 * Step Msg
 *
 * Input from client:
 *
 * u32 clientin-length
 * u8-array clientin-string
 *
 * Output to client:
 *
 * u32 serverout-length
 * u8-array serverout-strin
 * u8 continue
 */

static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len);

static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t len)
{
    uint32_t datalen = len;
    const char *serverout;
    unsigned int serveroutlen;
    int err;
    char *clientdata = NULL;

    /* NB, distinction of NULL vs "" is *critical* in SASL */
    if (datalen) {
224 225 226
        clientdata = (char*)data;
        clientdata[datalen-1] = '\0'; /* Wire includes '\0', but make sure */
        datalen--; /* Don't count NULL byte when passing to _start() */
227 228 229
    }

    err = sasl_server_step(vs->sasl.conn,
230 231 232 233
                           clientdata,
                           datalen,
                           &serverout,
                           &serveroutlen);
234
    trace_vnc_auth_sasl_step(vs, data, len, serverout, serveroutlen, err);
235
    if (err != SASL_OK &&
236
        err != SASL_CONTINUE) {
237 238
        trace_vnc_auth_fail(vs, vs->auth, "Cannot step SASL auth",
                            sasl_errdetail(vs->sasl.conn));
239 240 241
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
242 243 244
    }

    if (serveroutlen > SASL_DATA_MAX_LEN) {
245
        trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
246 247 248
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
249 250 251
    }

    if (serveroutlen) {
252 253
        vnc_write_u32(vs, serveroutlen + 1);
        vnc_write(vs, serverout, serveroutlen + 1);
254
    } else {
255
        vnc_write_u32(vs, 0);
256 257 258 259 260 261
    }

    /* Whether auth is complete */
    vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);

    if (err == SASL_CONTINUE) {
262 263
        /* Wait for step length */
        vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
264
    } else {
265
        if (!vnc_auth_sasl_check_ssf(vs)) {
266
            trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
267 268 269 270 271 272 273 274
            goto authreject;
        }

        /* Check username whitelist ACL */
        if (vnc_auth_sasl_check_access(vs) < 0) {
            goto authreject;
        }

275
        trace_vnc_auth_pass(vs, vs->auth);
276 277 278 279 280 281 282 283
        vnc_write_u32(vs, 0); /* Accept auth */
        /*
         * Delay writing in SSF encoded mode until pending output
         * buffer is written
         */
        if (vs->sasl.runSSF)
            vs->sasl.waitWriteSSF = vs->output.offset;
        start_client_init(vs);
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    }

    return 0;

 authreject:
    vnc_write_u32(vs, 1); /* Reject auth */
    vnc_write_u32(vs, sizeof("Authentication failed"));
    vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
    vnc_flush(vs);
    vnc_client_error(vs);
    return -1;

 authabort:
    vnc_client_error(vs);
    return -1;
}

static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len)
{
    uint32_t steplen = read_u32(data, 0);
304

305
    if (steplen > SASL_DATA_MAX_LEN) {
306
        trace_vnc_auth_fail(vs, vs->auth, "SASL step len too large", "");
307 308
        vnc_client_error(vs);
        return -1;
309 310 311
    }

    if (steplen == 0)
312
        return protocol_client_auth_sasl_step(vs, NULL, 0);
313
    else
314
        vnc_read_when(vs, protocol_client_auth_sasl_step, steplen);
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    return 0;
}

/*
 * Start Msg
 *
 * Input from client:
 *
 * u32 clientin-length
 * u8-array clientin-string
 *
 * Output to client:
 *
 * u32 serverout-length
 * u8-array serverout-strin
 * u8 continue
 */

#define SASL_DATA_MAX_LEN (1024 * 1024)

static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t len)
{
    uint32_t datalen = len;
    const char *serverout;
    unsigned int serveroutlen;
    int err;
    char *clientdata = NULL;

    /* NB, distinction of NULL vs "" is *critical* in SASL */
    if (datalen) {
345 346 347
        clientdata = (char*)data;
        clientdata[datalen-1] = '\0'; /* Should be on wire, but make sure */
        datalen--; /* Don't count NULL byte when passing to _start() */
348 349 350
    }

    err = sasl_server_start(vs->sasl.conn,
351 352 353 354 355
                            vs->sasl.mechlist,
                            clientdata,
                            datalen,
                            &serverout,
                            &serveroutlen);
356
    trace_vnc_auth_sasl_start(vs, data, len, serverout, serveroutlen, err);
357
    if (err != SASL_OK &&
358
        err != SASL_CONTINUE) {
359 360
        trace_vnc_auth_fail(vs, vs->auth, "Cannot start SASL auth",
                            sasl_errdetail(vs->sasl.conn));
361 362 363
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
364 365
    }
    if (serveroutlen > SASL_DATA_MAX_LEN) {
366
        trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
367 368 369
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
370 371 372
    }

    if (serveroutlen) {
373 374
        vnc_write_u32(vs, serveroutlen + 1);
        vnc_write(vs, serverout, serveroutlen + 1);
375
    } else {
376
        vnc_write_u32(vs, 0);
377 378 379 380 381 382
    }

    /* Whether auth is complete */
    vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);

    if (err == SASL_CONTINUE) {
383 384
        /* Wait for step length */
        vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
385
    } else {
386
        if (!vnc_auth_sasl_check_ssf(vs)) {
387
            trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
388 389 390 391 392 393 394 395
            goto authreject;
        }

        /* Check username whitelist ACL */
        if (vnc_auth_sasl_check_access(vs) < 0) {
            goto authreject;
        }

396
        trace_vnc_auth_pass(vs, vs->auth);
397 398
        vnc_write_u32(vs, 0); /* Accept auth */
        start_client_init(vs);
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    }

    return 0;

 authreject:
    vnc_write_u32(vs, 1); /* Reject auth */
    vnc_write_u32(vs, sizeof("Authentication failed"));
    vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
    vnc_flush(vs);
    vnc_client_error(vs);
    return -1;

 authabort:
    vnc_client_error(vs);
    return -1;
}

static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len)
{
    uint32_t startlen = read_u32(data, 0);
419

420
    if (startlen > SASL_DATA_MAX_LEN) {
421
        trace_vnc_auth_fail(vs, vs->auth, "SASL start len too large", "");
422 423
        vnc_client_error(vs);
        return -1;
424 425 426
    }

    if (startlen == 0)
427
        return protocol_client_auth_sasl_start(vs, NULL, 0);
428 429 430 431 432 433 434

    vnc_read_when(vs, protocol_client_auth_sasl_start, startlen);
    return 0;
}

static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len)
{
J
Jim Meyering 已提交
435
    char *mechname = g_strndup((const char *) data, len);
436
    trace_vnc_auth_sasl_mech_choose(vs, mechname);
437 438

    if (strncmp(vs->sasl.mechlist, mechname, len) == 0) {
439 440
        if (vs->sasl.mechlist[len] != '\0' &&
            vs->sasl.mechlist[len] != ',') {
B
Blue Swirl 已提交
441
            goto fail;
442
        }
443
    } else {
444 445
        char *offset = strstr(vs->sasl.mechlist, mechname);
        if (!offset) {
B
Blue Swirl 已提交
446
            goto fail;
447 448 449 450
        }
        if (offset[-1] != ',' ||
            (offset[len] != '\0'&&
             offset[len] != ',')) {
B
Blue Swirl 已提交
451
            goto fail;
452
        }
453 454
    }

455
    g_free(vs->sasl.mechlist);
456 457 458 459
    vs->sasl.mechlist = mechname;

    vnc_read_when(vs, protocol_client_auth_sasl_start_len, 4);
    return 0;
B
Blue Swirl 已提交
460 461

 fail:
462
    trace_vnc_auth_fail(vs, vs->auth, "Unsupported mechname", mechname);
B
Blue Swirl 已提交
463
    vnc_client_error(vs);
464
    g_free(mechname);
B
Blue Swirl 已提交
465
    return -1;
466 467 468 469 470
}

static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len)
{
    uint32_t mechlen = read_u32(data, 0);
471

472
    if (mechlen > 100) {
473
        trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too long", "");
474 475
        vnc_client_error(vs);
        return -1;
476 477
    }
    if (mechlen < 1) {
478
        trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too short", "");
479 480
        vnc_client_error(vs);
        return -1;
481 482 483 484 485
    }
    vnc_read_when(vs, protocol_client_auth_sasl_mechname,mechlen);
    return 0;
}

486 487 488 489 490
static char *
vnc_socket_ip_addr_string(QIOChannelSocket *ioc,
                          bool local,
                          Error **errp)
{
491
    SocketAddress *addr;
492 493 494 495 496 497 498 499 500 501 502
    char *ret;

    if (local) {
        addr = qio_channel_socket_get_local_address(ioc, errp);
    } else {
        addr = qio_channel_socket_get_remote_address(ioc, errp);
    }
    if (!addr) {
        return NULL;
    }

503
    if (addr->type != SOCKET_ADDRESS_TYPE_INET) {
504 505 506
        error_setg(errp, "Not an inet socket type");
        return NULL;
    }
507 508
    ret = g_strdup_printf("%s;%s", addr->u.inet.host, addr->u.inet.port);
    qapi_free_SocketAddress(addr);
509 510 511
    return ret;
}

512 513 514 515 516
void start_auth_sasl(VncState *vs)
{
    const char *mechlist = NULL;
    sasl_security_properties_t secprops;
    int err;
517
    Error *local_err = NULL;
518 519 520 521
    char *localAddr, *remoteAddr;
    int mechlistlen;

    /* Get local & remote client addresses in form  IPADDR;PORT */
522
    localAddr = vnc_socket_ip_addr_string(vs->sioc, true, &local_err);
523
    if (!localAddr) {
524 525
        trace_vnc_auth_fail(vs, vs->auth, "Cannot format local IP",
                            error_get_pretty(local_err));
526
        goto authabort;
527
    }
528

529
    remoteAddr = vnc_socket_ip_addr_string(vs->sioc, false, &local_err);
530
    if (!remoteAddr) {
531 532
        trace_vnc_auth_fail(vs, vs->auth, "Cannot format remote IP",
                            error_get_pretty(local_err));
533
        g_free(localAddr);
534
        goto authabort;
535 536 537
    }

    err = sasl_server_new("vnc",
538 539 540 541 542 543 544
                          NULL, /* FQDN - just delegates to gethostname */
                          NULL, /* User realm */
                          localAddr,
                          remoteAddr,
                          NULL, /* Callbacks, not needed */
                          SASL_SUCCESS_DATA,
                          &vs->sasl.conn);
545 546
    g_free(localAddr);
    g_free(remoteAddr);
547 548 549
    localAddr = remoteAddr = NULL;

    if (err != SASL_OK) {
550 551
        trace_vnc_auth_fail(vs, vs->auth,  "SASL context setup failed",
                            sasl_errstring(err, NULL, NULL));
552 553
        vs->sasl.conn = NULL;
        goto authabort;
554 555 556
    }

    /* Inform SASL that we've got an external SSF layer from TLS/x509 */
557 558
    if (vs->auth == VNC_AUTH_VENCRYPT &&
        vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) {
559 560
        Error *local_err = NULL;
        int keysize;
561 562
        sasl_ssf_t ssf;

563 564 565
        keysize = qcrypto_tls_session_get_key_size(vs->tls,
                                                   &local_err);
        if (keysize < 0) {
566 567
            trace_vnc_auth_fail(vs, vs->auth, "cannot TLS get cipher size",
                                error_get_pretty(local_err));
568
            error_free(local_err);
569 570 571 572
            sasl_dispose(&vs->sasl.conn);
            vs->sasl.conn = NULL;
            goto authabort;
        }
573
        ssf = keysize * CHAR_BIT; /* tls key size is bytes, sasl wants bits */
574 575 576

        err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
        if (err != SASL_OK) {
577 578
            trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL external SSF",
                                sasl_errstring(err, NULL, NULL));
579 580 581 582
            sasl_dispose(&vs->sasl.conn);
            vs->sasl.conn = NULL;
            goto authabort;
        }
583
    } else {
584
        vs->sasl.wantSSF = 1;
585
    }
586 587

    memset (&secprops, 0, sizeof secprops);
588 589 590 591 592 593 594 595
    /* Inform SASL that we've got an external SSF layer from TLS.
     *
     * Disable SSF, if using TLS+x509+SASL only. TLS without x509
     * is not sufficiently strong
     */
    if (vs->vd->is_unix ||
        (vs->auth == VNC_AUTH_VENCRYPT &&
         vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)) {
596 597 598 599 600
        /* If we've got TLS or UNIX domain sock, we don't care about SSF */
        secprops.min_ssf = 0;
        secprops.max_ssf = 0;
        secprops.maxbufsize = 8192;
        secprops.security_flags = 0;
601
    } else {
602 603 604 605 606 607 608
        /* Plain TCP, better get an SSF layer */
        secprops.min_ssf = 56; /* Good enough to require kerberos */
        secprops.max_ssf = 100000; /* Arbitrary big number */
        secprops.maxbufsize = 8192;
        /* Forbid any anonymous or trivially crackable auth */
        secprops.security_flags =
            SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
609 610 611 612
    }

    err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
    if (err != SASL_OK) {
613 614
        trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL security props",
                            sasl_errstring(err, NULL, NULL));
615 616 617
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
618 619 620
    }

    err = sasl_listmech(vs->sasl.conn,
621 622 623 624 625 626 627
                        NULL, /* Don't need to set user */
                        "", /* Prefix */
                        ",", /* Separator */
                        "", /* Suffix */
                        &mechlist,
                        NULL,
                        NULL);
628
    if (err != SASL_OK) {
629 630
        trace_vnc_auth_fail(vs, vs->auth, "cannot list SASL mechanisms",
                            sasl_errdetail(vs->sasl.conn));
631 632 633
        sasl_dispose(&vs->sasl.conn);
        vs->sasl.conn = NULL;
        goto authabort;
634
    }
635
    trace_vnc_auth_sasl_mech_list(vs, mechlist);
636

637
    vs->sasl.mechlist = g_strdup(mechlist);
638 639 640 641 642 643 644 645 646 647
    mechlistlen = strlen(mechlist);
    vnc_write_u32(vs, mechlistlen);
    vnc_write(vs, mechlist, mechlistlen);
    vnc_flush(vs);

    vnc_read_when(vs, protocol_client_auth_sasl_mechname_len, 4);

    return;

 authabort:
648
    error_free(local_err);
649 650 651 652
    vnc_client_error(vs);
}