Home | History | Annotate | Line # | Download | only in src
      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