1 /* $NetBSD: mech_gssapi.c,v 1.9 2025/12/17 15:58:36 nia Exp $ */ 2 3 /* Copyright (c) 2010 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Mateusz Kocielski. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of The NetBSD Foundation nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 * POSSIBILITY OF SUCH DAMAGE. 32 */ 33 #include <sys/cdefs.h> 34 __RCSID("$NetBSD: mech_gssapi.c,v 1.9 2025/12/17 15:58:36 nia Exp $"); 35 36 #include <assert.h> 37 #include <errno.h> 38 #include <endian.h> 39 #include <limits.h> /* for LINE_MAX */ 40 #include <saslc.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include <gssapi/gssapi.h> 46 47 #include "buffer.h" 48 #include "list.h" 49 #include "mech.h" 50 #include "msg.h" 51 #include "saslc_private.h" 52 53 /* See RFC 2222 section 7.2.1. */ 54 55 /* properties */ 56 #define SASLC_GSSAPI_AUTHCID SASLC_PROP_AUTHCID 57 #define SASLC_GSSAPI_HOSTNAME SASLC_PROP_HOSTNAME 58 #define SASLC_GSSAPI_SERVICE SASLC_PROP_SERVICE 59 #define SASLC_GSSAPI_QOPMASK SASLC_PROP_QOPMASK 60 61 #define DEFAULT_QOP_MASK (F_QOP_NONE | F_QOP_INT | F_QOP_CONF) 62 63 /* authentication steps */ 64 typedef enum { /* see RFC2222 7.2.1 section */ 65 GSSAPI_AUTH_FIRST, /* first authentication stage */ 66 GSSAPI_AUTH_NEXT, /* next authentication stage(s) */ 67 GSSAPI_AUTH_LAST, /* final authentication stage */ 68 GSSAPI_AUTH_DONE /* authenticated */ 69 } saslc__mech_gssapi_status_t; 70 71 /* gssapi mechanism session */ 72 typedef struct { 73 saslc__mech_sess_t mech_sess; /* mechanism session */ 74 saslc__mech_gssapi_status_t status; /* authentication status */ 75 gss_ctx_id_t gss_ctx; /* GSSAPI context */ 76 gss_name_t server_name; /* server name: service@host */ 77 gss_name_t client_name; /* client name - XXX: unused! */ 78 uint32_t qop_mask; /* available QOP services */ 79 uint32_t omaxbuf; /* maximum output buffer size */ 80 uint32_t imaxbuf; /* maximum input buffer size */ 81 saslc__buffer32_context_t *dec_ctx; /* decode buffer context */ 82 saslc__buffer_context_t *enc_ctx; /* encode buffer context */ 83 } saslc__mech_gssapi_sess_t; 84 85 /** 86 * @brief creates gssapi mechanism session. 87 * Function initializes also default options for the session. 88 * @param sess sasl session 89 * @return 0 on success, -1 on failure. 90 */ 91 static int 92 saslc__mech_gssapi_create(saslc_sess_t *sess) 93 { 94 saslc__mech_gssapi_sess_t *c; 95 96 c = sess->mech_sess = calloc(1, sizeof(*c)); 97 if (c == NULL) 98 return -1; 99 100 sess->mech_sess = c; 101 102 c->gss_ctx = GSS_C_NO_CONTEXT; 103 c->server_name = GSS_C_NO_NAME; 104 c->client_name = GSS_C_NO_NAME; 105 106 return 0; 107 } 108 109 /** 110 * @brief destroys gssapi mechanism session. 111 * Function also is freeing assigned resources to the session. 112 * @param sess sasl session 113 * @return Functions always returns 0. 114 */ 115 static int 116 saslc__mech_gssapi_destroy(saslc_sess_t *sess) 117 { 118 saslc__mech_gssapi_sess_t *ms; 119 OM_uint32 min_s; 120 121 ms = sess->mech_sess; 122 123 if (ms->gss_ctx != GSS_C_NO_CONTEXT) 124 gss_delete_sec_context(&min_s, &ms->gss_ctx, GSS_C_NO_BUFFER); 125 if (ms->server_name != GSS_C_NO_NAME) 126 gss_release_name(&min_s, &ms->server_name); 127 if (ms->client_name != GSS_C_NO_NAME) 128 gss_release_name(&min_s, &ms->client_name); 129 130 saslc__buffer_destroy(ms->enc_ctx); 131 saslc__buffer32_destroy(ms->dec_ctx); 132 free(ms); 133 sess->mech_sess = NULL; 134 135 return 0; 136 } 137 138 /** 139 * @brief translate the major and minor statuses an error message for 140 * the given mechanism 141 * @param maj_s major status 142 * @param min_s minor status 143 * @param mech mechanism 144 * @return pointer to a static buffer with error message 145 */ 146 static char * 147 saslc__mech_gssapi_err(OM_uint32 maj_s, OM_uint32 min_s, gss_OID mech) 148 { 149 static char errbuf[LINE_MAX]; 150 gss_buffer_desc maj_error_message; 151 gss_buffer_desc min_error_message; 152 OM_uint32 disp_min_s; 153 OM_uint32 msg_ctx; 154 155 msg_ctx = 0; 156 maj_error_message.length = 0; 157 maj_error_message.value = NULL; 158 min_error_message.length = 0; 159 min_error_message.value = NULL; 160 161 (void)gss_display_status(&disp_min_s, maj_s, GSS_C_GSS_CODE, 162 mech, &msg_ctx, &maj_error_message); 163 (void)gss_display_status(&disp_min_s, min_s, GSS_C_MECH_CODE, 164 mech, &msg_ctx, &min_error_message); 165 166 (void)snprintf(errbuf, sizeof(errbuf), 167 "gss-code: %lu %.*s\nmech-code: %lu %.*s", 168 (unsigned long)maj_s, 169 (int)maj_error_message.length, 170 (char *)maj_error_message.value, 171 (unsigned long)min_s, 172 (int)min_error_message.length, 173 (char *)min_error_message.value); 174 175 (void)gss_release_buffer(&disp_min_s, &maj_error_message); 176 (void)gss_release_buffer(&disp_min_s, &min_error_message); 177 178 return errbuf; 179 } 180 181 /** 182 * @brief set a session error message using saslc__mech_gssapi_err() 183 * @param sess the session 184 * @param err error number to set 185 * @param maj_s major status 186 * @param min_s minor status 187 * @return pointer to a static buffer with error message 188 */ 189 static void 190 saslc__mech_gssapi_set_err(saslc_sess_t *sess, int err, OM_uint32 maj_s, OM_uint32 min_s) 191 { 192 193 saslc__error_set(ERR(sess), err, 194 saslc__mech_gssapi_err(maj_s, min_s, GSS_C_NO_OID)); 195 } 196 197 /** 198 * @brief convert an initialization output token into the out and outlen format. 199 * Also releases the output token. 200 * @param sess saslc session 201 * @param outbuf gss buffer token 202 * @param out pointer to a void pointer 203 * @param outlen pointer to size_t length storage 204 * @returns 0 on success, -1 on failure 205 */ 206 static int 207 prep_output(saslc_sess_t *sess, gss_buffer_t outbuf, void **out, size_t *outlen) 208 { 209 OM_uint32 min_s; 210 211 if (outbuf == GSS_C_NO_BUFFER || outbuf->value == NULL) { 212 *outlen = 0; 213 *out = NULL; 214 return 0; 215 } 216 if (outbuf->length == 0) { 217 *outlen = 0; 218 *out = NULL; 219 gss_release_buffer(&min_s, outbuf); 220 return 0; 221 } 222 *out = malloc(outbuf->length); 223 if (*out == NULL) { 224 *outlen = 0; 225 gss_release_buffer(&min_s, outbuf); 226 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 227 return -1; 228 } 229 *outlen = outbuf->length; 230 memcpy(*out, outbuf->value, outbuf->length); 231 gss_release_buffer(&min_s, outbuf); 232 return 0; 233 } 234 235 /** 236 * @brief convert an output token into a valid packet where the first 237 * 4 bytes are the payload length in network byte order. 238 * Also releases the output token. 239 * @param sess saslc session 240 * @param outbuf gss buffer token 241 * @param out pointer to a void pointer 242 * @param outlen pointer to size_t length storage 243 * @returns 0 on success, -1 on failure 244 */ 245 static int 246 prep_packet(saslc_sess_t *sess, gss_buffer_t outbuf, void **out, size_t *outlen) 247 { 248 saslc__mech_gssapi_sess_t *ms; 249 OM_uint32 min_s; 250 char *buf; 251 size_t buflen; 252 253 ms = sess->mech_sess; 254 255 if (outbuf == GSS_C_NO_BUFFER || outbuf->value == NULL) { 256 *outlen = 0; 257 *out = NULL; 258 return 0; 259 } 260 if (outbuf->length == 0) { 261 *outlen = 0; 262 *out = NULL; 263 gss_release_buffer(&min_s, outbuf); 264 return 0; 265 } 266 buflen = outbuf->length + 4; 267 if (buflen > ms->omaxbuf) { 268 saslc__error_set(ERR(sess), ERROR_MECH, 269 "output exceeds server maxbuf size"); 270 gss_release_buffer(&min_s, outbuf); 271 return -1; 272 } 273 buf = malloc(buflen); 274 if (buf == NULL) { 275 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 276 return -1; 277 } 278 be32enc(buf, (uint32_t)outbuf->length); 279 memcpy(buf + 4, outbuf->value, outbuf->length); 280 gss_release_buffer(&min_s, outbuf); 281 282 *out = buf; 283 *outlen = buflen; 284 return 0; 285 } 286 287 /** 288 * @brief encodes one block of data using the negotiated security layer. 289 * @param sess sasl session 290 * @param in input data 291 * @param inlen input data length 292 * @param out place to store output data 293 * @param outlen output data length 294 * @return number of bytes consumed, zero if more needed, or -1 on failure. 295 */ 296 static ssize_t 297 saslc__mech_gssapi_encode(saslc_sess_t *sess, const void *in, size_t inlen, 298 void **out, size_t *outlen) 299 { 300 saslc__mech_gssapi_sess_t *ms; 301 gss_buffer_desc input, output; 302 OM_uint32 min_s, maj_s; 303 uint8_t *buf; 304 size_t buflen; 305 ssize_t len; 306 307 ms = sess->mech_sess; 308 assert(ms->mech_sess.qop != QOP_NONE); 309 if (ms->mech_sess.qop == QOP_NONE) 310 return -1; 311 312 len = saslc__buffer_fetch(ms->enc_ctx, in, inlen, &buf, &buflen); 313 if (len == -1) 314 return -1; 315 316 if (buflen == 0) { 317 *out = NULL; 318 *outlen = 0; 319 return len; 320 } 321 322 input.value = buf; 323 input.length = buflen; 324 output.value = NULL; 325 output.length = 0; 326 327 maj_s = gss_wrap(&min_s, ms->gss_ctx, ms->mech_sess.qop == QOP_CONF, 328 GSS_C_QOP_DEFAULT, &input, NULL, &output); 329 330 if (GSS_ERROR(maj_s)) { 331 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 332 return -1; 333 } 334 if (prep_packet(sess, &output, out, outlen) == -1) 335 return -1; 336 337 return len; 338 } 339 340 /** 341 * @brief decodes one block of data using the negotiated security layer. 342 * @param sess sasl session 343 * @param in input data 344 * @param inlen input data length 345 * @param out place to store output data 346 * @param outlen output data length 347 * @return number of bytes consumed, zero if more needed, or -1 on failure. 348 */ 349 static ssize_t 350 saslc__mech_gssapi_decode(saslc_sess_t *sess, const void *in, size_t inlen, 351 void **out, size_t *outlen) 352 { 353 saslc__mech_gssapi_sess_t *ms; 354 gss_buffer_desc input, output; 355 OM_uint32 min_s, maj_s; 356 uint8_t *buf; 357 size_t buflen; 358 ssize_t len; 359 360 ms = sess->mech_sess; 361 assert(ms->mech_sess.qop != QOP_NONE); 362 if (ms->mech_sess.qop == QOP_NONE) 363 return -1; 364 365 len = saslc__buffer32_fetch(ms->dec_ctx, in, inlen, &buf, &buflen); 366 if (len == -1) 367 return -1; 368 369 if (buflen == 0) { 370 *out = NULL; 371 *outlen = 0; 372 return len; 373 } 374 375 /* buf -> szbuf (4 bytes) followed by the payload buffer */ 376 input.value = buf + 4; 377 input.length = buflen - 4; 378 output.value = NULL; 379 output.length = 0; 380 381 maj_s = gss_unwrap(&min_s, ms->gss_ctx, &input, &output, NULL, NULL); 382 383 if (GSS_ERROR(maj_s)) { 384 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 385 return -1; 386 } 387 388 if (prep_output(sess, &output, out, outlen) == -1) 389 return -1; 390 391 return len; 392 } 393 394 /** 395 * @brief get service name from properties 396 * ("<servicename>@<hostname>") and store it in service token. 397 * @param sess the session context 398 * @param service the gs_name_t token to return service name in 399 * @return 0 on success, -1 on error 400 */ 401 static int 402 get_service(saslc_sess_t *sess, gss_name_t *service) 403 { 404 gss_buffer_desc bufdesc; 405 const char *hostname, *servicename; 406 char *buf; 407 int buflen; 408 OM_uint32 min_s, maj_s; 409 410 hostname = saslc_sess_getprop(sess, SASLC_GSSAPI_HOSTNAME); 411 if (hostname == NULL) { 412 saslc__error_set(ERR(sess), ERROR_MECH, 413 "hostname is required for an authentication"); 414 return -1; 415 } 416 servicename = saslc_sess_getprop(sess, SASLC_GSSAPI_SERVICE); 417 if (servicename == NULL) { 418 saslc__error_set(ERR(sess), ERROR_MECH, 419 "service is required for an authentication"); 420 return -1; 421 } 422 buflen = asprintf(&buf, "%s@%s", servicename, hostname); 423 if (buflen == -1) { 424 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 425 return -1; 426 } 427 bufdesc.value = buf; 428 bufdesc.length = buflen + 1; 429 430 saslc__msg_dbg("%s: buf='%s'", __func__, buf); 431 432 maj_s = gss_import_name(&min_s, &bufdesc, GSS_C_NT_HOSTBASED_SERVICE, 433 service); 434 free(buf); 435 if (GSS_ERROR(maj_s)) { 436 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 437 return -1; 438 } 439 return 0; 440 } 441 442 /** 443 * @brief gss_init_sec_context() wrapper 444 * @param sess session context 445 * @param inbuf input token 446 * @param outbuf output token 447 * @return 0 if GSS_S_COMPLETE, 1 if GSS_S_CONTINUE_NEEDED, -1 on failure 448 */ 449 static int 450 init_sec_context(saslc_sess_t *sess, gss_buffer_t inbuf, gss_buffer_t outbuf) 451 { 452 saslc__mech_gssapi_sess_t *ms; 453 OM_uint32 min_s, maj_s; 454 455 ms = sess->mech_sess; 456 457 outbuf->length = 0; 458 outbuf->value = NULL; 459 maj_s = gss_init_sec_context( 460 &min_s, /* minor status */ 461 GSS_C_NO_CREDENTIAL, /* use current login context credential */ 462 &ms->gss_ctx, /* initially GSS_C_NO_CONTEXT */ 463 ms->server_name, /* server@hostname */ 464 GSS_C_NO_OID, /* use default mechanism */ 465 #if 1 466 GSS_C_REPLAY_FLAG | /* message replay detection */ 467 GSS_C_INTEG_FLAG | /* request integrity */ 468 GSS_C_CONF_FLAG | /* request confirmation */ 469 #endif 470 GSS_C_MUTUAL_FLAG | /* mutual authentication */ 471 GSS_C_SEQUENCE_FLAG, /* message sequence checking */ 472 0, /* default lifetime (2 hrs) */ 473 GSS_C_NO_CHANNEL_BINDINGS, 474 inbuf, /* input token */ 475 /* output parameters follow */ 476 NULL, /* mechanism type for context */ 477 outbuf, /* output token */ 478 NULL, /* services available for context */ 479 NULL); /* lifetime of context */ 480 481 switch (maj_s) { 482 case GSS_S_COMPLETE: 483 return 0; 484 case GSS_S_CONTINUE_NEEDED: 485 return 1; 486 default: 487 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 488 return -1; 489 } 490 } 491 492 /** 493 * @brief unwrap the authentication token received from the server. 494 * This contains the qop_mask and maxbuf values which are updated in 495 * saslc__mech_gssapi_sess_t. 496 * @param sess the session context 497 * @param inbuf the received authentication token. 498 * @return 0 on success, -1 on error. 499 */ 500 static int 501 unwrap_input_token(saslc_sess_t *sess, gss_buffer_t inbuf) 502 { 503 saslc__mech_gssapi_sess_t *ms; 504 OM_uint32 min_s, maj_s; 505 gss_buffer_t outbuf; 506 gss_buffer_desc outdesc; 507 unsigned char *p; 508 509 /********************************************************************/ 510 /* [RFC 2222 section 7.2.1] */ 511 /* The client passes this token to GSS_Unwrap and interprets */ 512 /* the first octet of resulting cleartext as a bit-mask specifying */ 513 /* the security layers supported by the server and the second */ 514 /* through fourth octets as the maximum size output_message to send */ 515 /* to the server. */ 516 /********************************************************************/ 517 518 ms = sess->mech_sess; 519 520 outbuf = &outdesc; 521 maj_s = gss_unwrap(&min_s, ms->gss_ctx, inbuf, outbuf, NULL, NULL); 522 523 if (GSS_ERROR(maj_s)) { 524 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 525 return -1; 526 } 527 if (outbuf->length != 4) { 528 saslc__error_set(ERR(sess), ERROR_MECH, 529 "invalid unwrap length"); 530 return -1; 531 } 532 p = outbuf->value; 533 ms->qop_mask = p[0]; 534 ms->omaxbuf = (be32dec(p) & 0xffffff); 535 536 saslc__msg_dbg("%s: qop_mask=0x%02x omaxbuf=%d", 537 __func__, ms->qop_mask, ms->omaxbuf); 538 539 if (ms->qop_mask == QOP_NONE && ms->omaxbuf != 0) { 540 saslc__error_set(ERR(sess), ERROR_MECH, 541 "server has no security layer support, but maxbuf != 0"); 542 return -1; 543 } 544 maj_s = gss_release_buffer(&min_s, outbuf); 545 if (GSS_ERROR(maj_s)) { 546 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 547 return -1; 548 } 549 return 0; 550 } 551 552 /** 553 * @brief construct and wrap up an authentication token and put it in 554 * outbuf. The outbuf token data is structured as follows: 555 * struct { 556 * uint8_t qop; // qop to use 557 * uint8_t maxbuf[3] // maxbuf for client (network byte order) 558 * uint8_t authcid[] // variable length authentication id (username) 559 * } __packed; 560 * @param sess the session 561 * @param outbuf the gss_buffer_t token to return to server. 562 * @return 0 on success, -1 on error. 563 */ 564 static int 565 wrap_output_token(saslc_sess_t *sess, gss_buffer_t outbuf) 566 { 567 saslc__mech_gssapi_sess_t *ms; 568 gss_buffer_desc indesc; 569 char *input_value; 570 int len; 571 const char *authcid; 572 OM_uint32 min_s, maj_s; 573 unsigned char *p; 574 575 /********************************************************************/ 576 /* [RFC 2222 section 7.2.1] */ 577 /* The client then constructs data, with the first octet containing */ 578 /* the bit-mask specifying the selected security layer, the second */ 579 /* through fourth octets containing in network byte order the */ 580 /* maximum size output_message the client is able to receive, and */ 581 /* the remaining octets containing the authorization identity. The */ 582 /* authorization identity is optional in mechanisms where it is */ 583 /* encoded in the exchange such as GSSAPI. The client passes the */ 584 /* data to GSS_Wrap with conf_flag set to FALSE, and responds with */ 585 /* the generated output_message. The client can then consider the */ 586 /* server authenticated. */ 587 /********************************************************************/ 588 589 ms = sess->mech_sess; 590 591 authcid = saslc_sess_getprop(sess, SASLC_GSSAPI_AUTHCID); 592 593 len = asprintf(&input_value, "qmax%s", authcid ? authcid : ""); 594 if (len == -1) { 595 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 596 return -1; 597 } 598 be32enc(input_value, ms->imaxbuf); 599 input_value[0] = saslc__mech_qop_flag(ms->mech_sess.qop); 600 601 indesc.value = input_value; 602 indesc.length = len; /* XXX: don't count the '\0' */ 603 604 p = (unsigned char *)input_value; 605 saslc__msg_dbg("%s: input_value='%02x %02x %02x %02x %s", 606 __func__, p[0], p[1], p[2], p[3], input_value + 4); 607 608 maj_s = gss_wrap(&min_s, ms->gss_ctx, 0 /* FALSE - RFC2222 */, 609 GSS_C_QOP_DEFAULT, &indesc, NULL, outbuf); 610 611 free(input_value); 612 613 if (GSS_ERROR(maj_s)) { 614 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 615 return -1; 616 } 617 return 0; 618 } 619 620 /************************************************************************ 621 * XXX: Share this with mech_digestmd5.c? They are almost identical. 622 */ 623 /** 624 * @brief choose the best qop based on what was provided by the 625 * challenge and a possible user mask. 626 * @param sess the session context 627 * @param qop_flags the qop flags parsed from the challenge string 628 * @return the selected saslc__mech_sess_qop_t or -1 if no match 629 */ 630 static int 631 choose_qop(saslc_sess_t *sess, uint32_t qop_flags) 632 { 633 list_t *list; 634 const char *user_qop; 635 636 qop_flags &= DEFAULT_QOP_MASK; 637 user_qop = saslc_sess_getprop(sess, SASLC_GSSAPI_QOPMASK); 638 if (user_qop != NULL) { 639 if (saslc__list_parse(&list, user_qop) == -1) { 640 saslc__error_set_errno(ERR(sess), ERROR_NOMEM); 641 return -1; 642 } 643 qop_flags &= saslc__mech_qop_list_flags(list); 644 saslc__list_free(list); 645 } 646 647 /* 648 * Select the most secure supported qop. 649 */ 650 if ((qop_flags & F_QOP_CONF) != 0) 651 return QOP_CONF; 652 if ((qop_flags & F_QOP_INT) != 0) 653 return QOP_INT; 654 if ((qop_flags & F_QOP_NONE) != 0) 655 return QOP_NONE; 656 657 saslc__error_set(ERR(sess), ERROR_MECH, 658 "cannot choose an acceptable qop"); 659 return -1; 660 } 661 /************************************************************************/ 662 663 /** 664 * @brief compute the maximum buffer length we can use and not 665 * overflow the servers maxbuf. 666 * @param sess the session context 667 * @param maxbuf the server's maxbuf value 668 */ 669 static int 670 wrap_size_limit(saslc_sess_t *sess, OM_uint32 maxbuf) 671 { 672 saslc__mech_gssapi_sess_t *ms; 673 OM_uint32 min_s, maj_s; 674 OM_uint32 max_input; 675 676 ms = sess->mech_sess; 677 678 maj_s = gss_wrap_size_limit(&min_s, ms->gss_ctx, 1, GSS_C_QOP_DEFAULT, 679 maxbuf, &max_input); 680 681 if (GSS_ERROR(maj_s)) { 682 saslc__mech_gssapi_set_err(sess, ERROR_MECH, maj_s, min_s); 683 return -1; 684 } 685 686 /* XXX: from cyrus-sasl: gssapi.c */ 687 if (max_input > maxbuf) { 688 /* Heimdal appears to get this wrong */ 689 maxbuf -= (max_input - maxbuf); 690 } else { 691 /* This code is actually correct */ 692 maxbuf = max_input; 693 } 694 return maxbuf; 695 } 696 697 /** 698 * @brief set our imaxbuf (from omaxbuf or from properties) and 699 * then reset omaxbuf in saslc__mech_gssapi_sess_t. 700 * @param sess the session context 701 * @return 0 on success, -1 on error 702 * 703 * Note: on entry the omaxbuf is the server's maxbuf size. On exit 704 * the omaxbuf is the maximum buffer we can fill that will not 705 * overflow the servers maxbuf after it is encoded. This value is 706 * given by wrap_size_limit(). 707 */ 708 static int 709 set_maxbufs(saslc_sess_t *sess) 710 { 711 saslc__mech_gssapi_sess_t *ms; 712 const char *p; 713 char *q; 714 unsigned long val; 715 int rv; 716 717 ms = sess->mech_sess; 718 719 /* by default, we use the same input maxbuf as the server. */ 720 ms->imaxbuf = ms->omaxbuf; 721 p = saslc_sess_getprop(sess, SASLC_PROP_MAXBUF); 722 if (p != NULL) { 723 val = strtol(p, &q, 0); 724 if (p[0] == '\0' || *q != '\0') { 725 726 return MECH_ERROR; 727 } 728 if (errno == ERANGE && val == ULONG_MAX) { 729 730 return MECH_ERROR; 731 } 732 if (val > 0xffffff) 733 val = 0xffffff; 734 ms->imaxbuf = (uint32_t)val; 735 } 736 rv = wrap_size_limit(sess, ms->omaxbuf); 737 if (rv == -1) 738 return MECH_ERROR; 739 ms->omaxbuf = rv; /* maxbuf size for unencoded output data */ 740 741 return 0; 742 } 743 744 /** 745 * @brief do one step of the sasl authentication 746 * @param sess sasl session 747 * @param in input data 748 * @param inlen input data length 749 * @param out place to store output data 750 * @param outlen output data length 751 * @return MECH_OK on success, MECH_STEP if more steps are needed, 752 * MECH_ERROR on failure 753 */ 754 static int 755 saslc__mech_gssapi_cont(saslc_sess_t *sess, const void *in, size_t inlen, 756 void **out, size_t *outlen) 757 { 758 saslc__mech_gssapi_sess_t *ms; 759 gss_buffer_desc input, output; 760 int rv; 761 762 /**************************************************************************/ 763 /* [RFC 2222 section 7.2.1] */ 764 /* The client calls GSS_Init_sec_context, passing in 0 for */ 765 /* input_context_handle (initially) and a targ_name equal to output_name */ 766 /* from GSS_Import_Name called with input_name_type of */ 767 /* GSS_C_NT_HOSTBASED_SERVICE and input_name_string of */ 768 /* "service@hostname" where "service" is the service name specified in */ 769 /* the protocol's profile, and "hostname" is the fully qualified host */ 770 /* name of the server. The client then responds with the resulting */ 771 /* output_token. If GSS_Init_sec_context returns GSS_S_CONTINUE_NEEDED, */ 772 /* then the client should expect the server to issue a token in a */ 773 /* subsequent challenge. The client must pass the token to another call */ 774 /* to GSS_Init_sec_context, repeating the actions in this paragraph. */ 775 /* */ 776 /* When GSS_Init_sec_context returns GSS_S_COMPLETE, the client takes */ 777 /* the following actions: If the last call to GSS_Init_sec_context */ 778 /* returned an output_token, then the client responds with the */ 779 /* output_token, otherwise the client responds with no data. The client */ 780 /* should then expect the server to issue a token in a subsequent */ 781 /* challenge. The client passes this token to GSS_Unwrap and interprets */ 782 /* the first octet of resulting cleartext as a bit-mask specifying the */ 783 /* security layers supported by the server and the second through fourth */ 784 /* octets as the maximum size output_message to send to the server. The */ 785 /* client then constructs data, with the first octet containing the */ 786 /* bit-mask specifying the selected security layer, the second through */ 787 /* fourth octets containing in network byte order the maximum size */ 788 /* output_message the client is able to receive, and the remaining */ 789 /* octets containing the authorization identity. The client passes the */ 790 /* data to GSS_Wrap with conf_flag set to FALSE, and responds with the */ 791 /* generated output_message. The client can then consider the server */ 792 /* authenticated. */ 793 /**************************************************************************/ 794 795 ms = sess->mech_sess; 796 797 switch(ms->status) { 798 case GSSAPI_AUTH_FIRST: 799 saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_FIRST"); 800 801 if (get_service(sess, &ms->server_name) == -1) 802 return MECH_ERROR; 803 804 rv = init_sec_context(sess, GSS_C_NO_BUFFER, &output); 805 if (rv == -1) 806 return MECH_ERROR; 807 808 if (prep_output(sess, &output, out, outlen) == -1) 809 return MECH_ERROR; 810 811 ms->status = rv == 0 ? GSSAPI_AUTH_LAST : GSSAPI_AUTH_NEXT; 812 return MECH_STEP; 813 814 case GSSAPI_AUTH_NEXT: 815 saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_NEXT"); 816 817 input.value = __UNCONST(in); 818 input.length = inlen; 819 if ((rv = init_sec_context(sess, &input, &output)) == -1) 820 return MECH_ERROR; 821 822 if (prep_output(sess, &output, out, outlen) == -1) 823 return MECH_ERROR; 824 825 if (rv == 0) 826 ms->status = GSSAPI_AUTH_LAST; 827 return MECH_STEP; 828 829 case GSSAPI_AUTH_LAST: 830 saslc__msg_dbg("%s: status: %s", __func__, "GSSAPI_AUTH_LAST"); 831 832 input.value = __UNCONST(in); 833 input.length = inlen; 834 if (unwrap_input_token(sess, &input) == -1) 835 return MECH_ERROR; 836 837 if ((rv = choose_qop(sess, ms->qop_mask)) == -1) 838 return MECH_ERROR; 839 840 ms->mech_sess.qop = rv; 841 842 if (ms->mech_sess.qop != QOP_NONE) { 843 if (ms->mech_sess.qop == QOP_CONF) { 844 /* 845 * XXX: where do we negotiate the cipher, 846 * or do we? 847 */ 848 } 849 if (set_maxbufs(sess) == -1) 850 return MECH_ERROR; 851 ms->dec_ctx = saslc__buffer32_create(sess, ms->imaxbuf); 852 ms->enc_ctx = saslc__buffer_create(sess, ms->omaxbuf); 853 } 854 if (wrap_output_token(sess, &output) == -1) 855 return MECH_ERROR; 856 857 if (prep_output(sess, &output, out, outlen) == -1) 858 return MECH_ERROR; 859 860 ms->status = GSSAPI_AUTH_DONE; 861 return MECH_OK; 862 863 case GSSAPI_AUTH_DONE: 864 assert(/*CONSTCOND*/0); /* XXX: impossible */ 865 saslc__error_set(ERR(sess), ERROR_MECH, 866 "already authenticated"); 867 return MECH_ERROR; 868 869 #if 0 /* no default so the compiler can tell us if we miss an enum */ 870 default: 871 assert(/*CONSTCOND*/0); /* impossible */ 872 /*NOTREACHED*/ 873 #endif 874 } 875 /*LINTED*/ 876 assert(/*CONSTCOND*/0); /* XXX: impossible */ 877 return MECH_ERROR; 878 } 879 880 /* mechanism definition */ 881 const saslc__mech_t saslc__mech_gssapi = { 882 .name = "GSSAPI", 883 .flags = FLAG_NONE, 884 .create = saslc__mech_gssapi_create, 885 .cont = saslc__mech_gssapi_cont, 886 .encode = saslc__mech_gssapi_encode, 887 .decode = saslc__mech_gssapi_decode, 888 .destroy = saslc__mech_gssapi_destroy 889 }; 890