Home | History | Annotate | Line # | Download | only in src
      1 /* $NetBSD: xsess.c,v 1.9 2025/12/16 12:03:39 nia Exp $ */
      2 
      3 /*
      4  * Copyright (c) 2010 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Mateusz Kocielski.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. Neither the name of The NetBSD Foundation nor the names of its
     19  *    contributors may be used to endorse or promote products derived
     20  *    from this software without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     32  * POSSIBILITY OF SUCH DAMAGE.
     33  */
     34 #include <sys/cdefs.h>
     35 __RCSID("$NetBSD: xsess.c,v 1.9 2025/12/16 12:03:39 nia Exp $");
     36 
     37 #include <assert.h>
     38 #include <saslc.h>
     39 #include <stdio.h>
     40 #include <string.h>
     41 
     42 #include "crypto.h"
     43 #include "dict.h"
     44 #include "error.h"
     45 #include "list.h"
     46 #include "msg.h"
     47 #include "mech.h"
     48 #include "parser.h"
     49 #include "saslc_private.h"
     50 
     51 /*
     52  * TODO:
     53  *
     54  * 1) Add hooks to allow saslc_sess_encode() and saslc_sess_decode()
     55  * to output and input, respectively, base64 encoded data much like
     56  * what sess_saslc_cont() does according to the SASLC_FLAGS_BASE64_*
     57  * flags.  For saslc_sess_decode() it seems it would be easiest to do
     58  * this in saslc__buffer32_fetch() pushing any extra buffering into
     59  * the BIO_* routines, but I haven't thought this through carefully
     60  * yet.
     61  */
     62 
     63 static inline char *
     64 skip_WS(char *p)
     65 {
     66 
     67 	while (*p == ' ' || *p == '\t')
     68 		p++;
     69 	return p;
     70 }
     71 
     72 /**
     73  * @brief convert a comma and/or space delimited list into a comma
     74  * delimited list of the form:
     75  *   ( *LWS element *( *LWS "," *LWS element ))
     76  * @param str string to convert.
     77  */
     78 static void
     79 normalize_list_string(char *opts)
     80 {
     81 	char *p;
     82 
     83 	p = opts;
     84 	while (p != NULL) {
     85 		p = strchr(p, ' ');
     86 		if (p == NULL)
     87 			break;
     88 		if (p > opts && p[-1] != ',')
     89 			*p++ = ',';
     90 		p = skip_WS(p + 1);
     91 	}
     92 }
     93 
     94 /**
     95  * @brief get the security flags from a comma delimited string.
     96  * @param sec_opt the security option comman delimited string.
     97  * @return the flags on success, or -1 on error (no memory).
     98  */
     99 static int
    100 get_security_flags(const char *sec_opts)
    101 {
    102 	static const named_flag_t flag_tbl[] = {
    103 		{ "noanonymous",	FLAG_ANONYMOUS },
    104 		{ "nodictionary",	FLAG_DICTIONARY },
    105 		{ "noplaintext",	FLAG_PLAINTEXT },
    106 		{ "noactive",		FLAG_ACTIVE },
    107 		{ "mutual",		FLAG_MUTUAL },
    108 		{ NULL,			FLAG_NONE }
    109 	};
    110 	list_t *list;
    111 	char *opts;
    112 	uint32_t flags;
    113 	int rv;
    114 
    115 	if (sec_opts == NULL)
    116 		return 0;
    117 
    118 	if ((opts = strdup(sec_opts)) == NULL)
    119 		return -1;
    120 
    121 	normalize_list_string(opts);
    122 	rv = saslc__list_parse(&list, opts);
    123 	free(opts);
    124 	if (rv == -1)
    125 		return -1;
    126 	flags = saslc__list_flags(list, flag_tbl);
    127 	saslc__list_free(list);
    128 	return flags;
    129 }
    130 
    131 /**
    132  * @brief compare the mechanism flags with the security option flags
    133  * passed by the user and make sure the mechanism is OK.
    134  * @param mech mechanism to check.
    135  * @param flags security option flags passed by saslc_sess_init().
    136  * @return true if the mechanism is permitted and false if not.
    137  */
    138 static bool
    139 mechanism_flags_OK(const saslc__mech_list_node_t *mech, uint32_t flags)
    140 {
    141 	uint32_t reqflags, rejflags;
    142 
    143 	if (mech == NULL)
    144 		return false;
    145 
    146 	reqflags = flags & REQ_FLAGS;
    147 	rejflags = flags & REJ_FLAGS;
    148 
    149 	if ((mech->mech->flags & rejflags) != 0)
    150 		return false;
    151 
    152 	if ((mech->mech->flags & reqflags) != reqflags)
    153 		return false;
    154 
    155 	return true;
    156 }
    157 
    158 /**
    159  * @brief chooses first supported mechanism from the mechs list for
    160  * the sasl session.
    161  * @param ctx sasl context
    162  * @param mechs comma or space separated list of mechanisms
    163  * e.g., "PLAIN,LOGIN" or "PLAIN LOGIN".
    164  * @param sec_opts comma or space separated list of security options
    165  * @return pointer to the mech on success, NULL if none mechanism is chosen
    166  *
    167  * Note: this uses SASLC_PROP_SECURITY from the context dictionary.
    168  * Note: this function is not case sensitive with regard to mechs or sec_opts.
    169  */
    170 static const saslc__mech_t *
    171 saslc__sess_choose_mech(saslc_t *ctx, const char *mechs, const char *sec_opts)
    172 {
    173 	list_t *list, *l;
    174 	char *tmpstr;
    175 	const saslc__mech_list_node_t *m;
    176 	uint32_t flags;
    177 	int rv;
    178 
    179 	rv = get_security_flags(sec_opts);
    180 	if (rv == -1)
    181 		goto nomem;
    182 	flags = rv;
    183 
    184 	sec_opts = saslc__dict_get(ctx->prop, SASLC_PROP_SECURITY);
    185 	if (sec_opts != NULL) {
    186 		rv = get_security_flags(sec_opts);
    187 		if (rv == -1)
    188 			goto nomem;
    189 		flags |= rv;
    190 	}
    191 	if ((tmpstr = strdup(mechs)) == NULL)
    192 		goto nomem;
    193 
    194 	normalize_list_string(tmpstr);
    195 	rv = saslc__list_parse(&list, tmpstr);
    196 	free(tmpstr);
    197 	if (rv == -1)
    198 		goto nomem;
    199 
    200 	m = NULL;
    201 	for (l = list; l != NULL; l = l->next) {
    202 		m = saslc__mech_list_get(ctx->mechanisms, l->value);
    203 		if (mechanism_flags_OK(m, flags))
    204 			break;
    205 	}
    206 	saslc__list_free(list);
    207 
    208 	if (m == NULL) {
    209 		saslc__error_set(ERR(ctx), ERROR_MECH,
    210 		    "mechanism not supported");
    211 		return NULL;
    212 	}
    213 	return m->mech;
    214  nomem:
    215 	saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
    216 	return NULL;
    217 }
    218 
    219 /**
    220  * @brief sasl session initializaion. Function initializes session
    221  * property dictionary, chooses best mechanism, creates mech session.
    222  * @param ctx sasl context
    223  * @param mechs comma or space separated list of mechanisms eg. "PLAIN,LOGIN"
    224  * or "PLAIN LOGIN".  Note that this function is not case sensitive.
    225  * @return pointer to the sasl session on success, NULL on failure
    226  */
    227 saslc_sess_t *
    228 saslc_sess_init(saslc_t *ctx, const char *mechs, const char *sec_opts)
    229 {
    230 	saslc_sess_t *sess;
    231 	const char *debug;
    232 	saslc__mech_list_node_t *m;
    233 
    234 	if ((sess = calloc(1, sizeof(*sess))) == NULL) {
    235 		saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
    236 		return NULL;
    237 	}
    238 
    239 	/* mechanism initialization */
    240 	if ((sess->mech = saslc__sess_choose_mech(ctx, mechs, sec_opts))
    241 	    == NULL)
    242 		goto error;
    243 
    244 	/* XXX: special early check of mechanism dictionary for debug flag */
    245 	m = saslc__mech_list_get(ctx->mechanisms, sess->mech->name);
    246 	if (m != NULL) {
    247 		debug = saslc__dict_get(m->prop, SASLC_PROP_DEBUG);
    248 		if (debug != NULL)
    249 			saslc_debug = saslc__parser_is_true(debug);
    250 	}
    251 
    252 	/* create mechanism session */
    253 	if (sess->mech->create(sess) == -1)
    254 		goto error;
    255 
    256 	/* properties */
    257 	if ((sess->prop = saslc__dict_create()) == NULL) {
    258 		saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
    259 		goto error;
    260 	}
    261 
    262 	sess->context = ctx;
    263 	ctx->refcnt++;
    264 
    265 	saslc__msg_dbg("mechanism: %s\n", saslc_sess_getmech(sess));
    266 
    267 	return sess;
    268  error:
    269 	free(sess);
    270 	return NULL;
    271 }
    272 
    273 /**
    274  * @brief ends sasl session, destroys and deallocates internal
    275  * resources
    276  * @param sess sasl session
    277  */
    278 void
    279 saslc_sess_end(saslc_sess_t *sess)
    280 {
    281 
    282 	sess->mech->destroy(sess);
    283 	saslc__dict_destroy(sess->prop);
    284 	sess->context->refcnt--;
    285 	free(sess);
    286 }
    287 
    288 /**
    289  * @brief sets property for the session. If property already exists in
    290  * the session, then previous value is replaced by the new value.
    291  * @param sess sasl session
    292  * @param name property name
    293  * @param value property value (if NULL, simply remove previous key)
    294  * @return 0 on success, -1 on failure
    295  */
    296 int
    297 saslc_sess_setprop(saslc_sess_t *sess, const char *key, const char *value)
    298 {
    299 
    300 	/* if the key exists in the session dictionary, remove it */
    301 	(void)saslc__dict_remove(sess->prop, key);
    302 
    303 	if (value == NULL)	/* simply remove previous value and return */
    304 		return 0;
    305 
    306 	switch (saslc__dict_insert(sess->prop, key, value)) {
    307 	case DICT_OK:
    308 		return 0;
    309 
    310 	case DICT_VALBAD:
    311 		saslc__error_set(ERR(sess), ERROR_BADARG, "bad value");
    312 		break;
    313 	case DICT_KEYINVALID:
    314 		saslc__error_set(ERR(sess), ERROR_BADARG, "bad key");
    315 		break;
    316 	case DICT_NOMEM:
    317 		saslc__error_set(ERR(sess), ERROR_NOMEM, NULL);
    318 		break;
    319 	case DICT_KEYEXISTS:
    320 	case DICT_KEYNOTFOUND:
    321 		assert(/*CONSTCOND*/0); /* impossible */
    322 		break;
    323 	}
    324 	return -1;
    325 }
    326 
    327 /**
    328  * @brief gets property from the session. Dictionaries are used
    329  * in following order: session dictionary, context dictionary (global
    330  * configuration), mechanism dicionary.
    331  * @param sess sasl session
    332  * @param key property name
    333  * @return property value on success, NULL on failure.
    334  */
    335 const char *
    336 saslc_sess_getprop(saslc_sess_t *sess, const char *key)
    337 {
    338 	const char *r;
    339 	saslc__mech_list_node_t *m;
    340 
    341 	/* get property from the session dictionary */
    342 	if ((r = saslc__dict_get(sess->prop, key)) != NULL) {
    343 		saslc__msg_dbg("%s: session dict: %s=%s", __func__, key, r);
    344 		return r;
    345 	}
    346 
    347 	/* get property from the context dictionary */
    348 	if ((r = saslc__dict_get(sess->context->prop, key)) != NULL) {
    349 		saslc__msg_dbg("%s: context dict: %s=%s", __func__, key, r);
    350 		return r;
    351 	}
    352 
    353 	/* get property from the mechanism dictionary */
    354 	if ((m = saslc__mech_list_get(sess->context->mechanisms,
    355 	    sess->mech->name)) == NULL)
    356 		return NULL;
    357 
    358 	if ((r = saslc__dict_get(m->prop, key)) != NULL)
    359 		saslc__msg_dbg("%s: mech %s dict: %s=%s", __func__,
    360 		    saslc_sess_getmech(sess), key, r);
    361 	else
    362 		saslc__msg_dbg("%s: %s not found", __func__, key);
    363 	return r;
    364 }
    365 
    366 /**
    367  * @brief set the sess->flags accordingly according to the properties.
    368  * @param sess saslc session
    369  */
    370 static uint32_t
    371 saslc__sess_get_flags(saslc_sess_t *sess)
    372 {
    373 	const char *base64io;
    374 	uint32_t flags;
    375 
    376 	/* set default flags */
    377 	flags = SASLC_FLAGS_DEFAULT;
    378 
    379 	base64io = saslc_sess_getprop(sess, SASLC_PROP_BASE64IO);
    380 	if (base64io != NULL) {
    381 		if (saslc__parser_is_true(base64io))
    382 			flags |= SASLC_FLAGS_BASE64;
    383 		else
    384 			flags &= ~SASLC_FLAGS_BASE64;
    385 	}
    386 	return flags;
    387 }
    388 
    389 /**
    390  * @brief does one step of the sasl authentication, input data
    391  * and its lenght are stored in in and inlen, output is stored in out and
    392  * outlen. This function is a wrapper for mechanism step functions.
    393  * Additionaly it checks if session is not already authorized and handles
    394  * steps mech_sess structure.
    395  * @param sess saslc session
    396  * @param in input data
    397  * @param inlen input data length
    398  * @param out output data
    399  * @param outlen output data length
    400  * @return MECH_OK - on success, no more steps are needed
    401  * MECH_ERROR - on error, additionaly errno in sess is setup
    402  * MECH_STEP - more steps are needed
    403  */
    404 int
    405 saslc_sess_cont(saslc_sess_t *sess, const void *in, size_t inlen,
    406     void **out, size_t *outlen)
    407 {
    408 	saslc__mech_sess_t *ms;
    409 	const char *debug;
    410 	void *dec;
    411 	int rv;
    412 
    413 	ms = sess->mech_sess;
    414 	if (ms->status == STATUS_AUTHENTICATED) {
    415 		saslc__error_set(ERR(sess), ERROR_MECH,
    416 		    "session authenticated");
    417 		return MECH_ERROR;
    418 	}
    419 	if (ms->step == 0) {
    420 		sess->flags = saslc__sess_get_flags(sess);
    421 
    422 		/* XXX: final check for any session debug flag setting */
    423 		debug = saslc__dict_get(sess->prop, SASLC_PROP_DEBUG);
    424 		if (debug != NULL)
    425 			saslc_debug = saslc__parser_is_true(debug);
    426 	}
    427 
    428 	saslc__msg_dbg("%s: encoded: inlen=%zu in='%s'", __func__, inlen,
    429 	    in ? (const char *)in : "<null>");
    430 	if (inlen == 0 || (sess->flags & SASLC_FLAGS_BASE64_IN) == 0)
    431 		dec = NULL;
    432 	else {
    433 		if (saslc__crypto_decode_base64(in, inlen, &dec, &inlen)
    434 		    == -1) {
    435 			saslc__error_set(ERR(sess), ERROR_MECH,
    436 			    "base64 decode failed");
    437 			return MECH_ERROR;
    438 		}
    439 		in = dec;
    440 	}
    441 	saslc__msg_dbg("%s: decoded: inlen=%zu in='%s'", __func__, inlen,
    442 	    in ? (const char *)in : "<null>");
    443 	rv = sess->mech->cont(sess, in, inlen, out, outlen);
    444 	if (dec != NULL)
    445 		free(dec);
    446 	if (rv == MECH_ERROR)
    447 		return MECH_ERROR;
    448 
    449 	saslc__msg_dbg("%s: out='%s'", __func__,
    450 	    *outlen ? (char *)*out : "<null>");
    451 	if (*outlen == 0)
    452 		*out = NULL;	/* XXX: unnecessary? */
    453 	else if ((sess->flags & SASLC_FLAGS_BASE64_OUT) != 0) {
    454 		char *enc;
    455 		size_t enclen;
    456 
    457 		if (saslc__crypto_encode_base64(*out, *outlen, &enc, &enclen)
    458 		    == -1) {
    459 			free(*out);
    460 			return MECH_ERROR;
    461 		}
    462 		free(*out);
    463 		*out = enc;
    464 		*outlen = enclen;
    465 	}
    466 	if (rv == MECH_OK)
    467 		ms->status = STATUS_AUTHENTICATED;
    468 
    469 	ms->step++;
    470 	return rv;
    471 }
    472 
    473 /**
    474  * @brief copies input data to an allocated buffer.  The caller is
    475  * responsible for freeing the buffer.
    476  * @param sess sasl session
    477  * @param xxcode codec to encode or decode one block of data
    478  * @param in input data
    479  * @param inlen input data length
    480  * @param out output data
    481  * @param outlen output data length
    482  * @return number of bytes copied on success, -1 on failure
    483  */
    484 static ssize_t
    485 saslc__sess_copyout(saslc_sess_t *sess, const void *in, size_t inlen,
    486     void **out, size_t *outlen)
    487 {
    488 
    489 	*out = malloc(inlen);
    490 	if (*out == NULL) {
    491 		*outlen = 0;
    492 		saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
    493 		return -1;
    494 	}
    495 	*outlen = inlen;
    496 	memcpy(*out, in, inlen);
    497 	return inlen;
    498 }
    499 
    500 /**
    501  * @brief encodes or decode data using method established during the
    502  * authentication. Input data is stored in in and inlen and output
    503  * data is stored in out and outlen.  The caller is responsible for
    504  * freeing the output buffer.
    505  * @param sess sasl session
    506  * @param xxcode codec to encode or decode one block of data
    507  * @param in input data
    508  * @param inlen input data length
    509  * @param out output data
    510  * @param outlen output data length
    511  * @return number of bytes consumed on success, 0 if insufficient data
    512  * to process, -1 on failure
    513  *
    514  * 'xxcode' encodes or decodes a single block of data and stores the
    515  * resulting block and its length in 'out' and 'outlen', respectively.
    516  * It should return the number of bytes it digested or -1 on error.
    517  * If it was unable to process a complete block, it should return zero
    518  * and remember the partial block internally.  If it is called with
    519  * 'inlen' = 0, it should flush out any remaining partial block data
    520  * and return the number of stored bytes it flushed or zero if there
    521  * were none (relevant for the encoder only).
    522  */
    523 static ssize_t
    524 saslc__sess_xxcode(saslc_sess_t *sess, saslc__mech_xxcode_t xxcode,
    525     const void *in, size_t inlen, void **out, size_t *outlen)
    526 {
    527 	saslc__mech_sess_t *ms;
    528 	unsigned char *p;
    529 	void *buf, *pkt;
    530 	size_t buflen, pktlen;
    531 	ssize_t len, ate;
    532 
    533 	ms = sess->mech_sess;
    534 
    535 	if (xxcode == NULL) {
    536 		saslc__error_set(ERR(sess), ERROR_MECH,
    537 		    "security layer is not supported by mechanism");
    538 		return -1;
    539 	}
    540 	if (ms->status != STATUS_AUTHENTICATED) {
    541 		saslc__error_set(ERR(sess), ERROR_MECH,
    542 		    "session is not authenticated");
    543 		return -1;
    544 	}
    545 
    546 	if (ms->qop == QOP_NONE)
    547 		return saslc__sess_copyout(sess, in, inlen, out, outlen);
    548 
    549 	p = NULL;
    550 	buf = NULL;
    551 	buflen = 0;
    552 	ate = 0;
    553 	do {
    554 		len = xxcode(sess, in, inlen, &pkt, &pktlen);
    555 		if (len == -1) {
    556 			free(buf);
    557 			return -1;
    558 		}
    559 
    560 		ate += len;
    561 		in = (const char *)in + len;
    562 		if (inlen < (size_t)len)
    563 			inlen = 0;
    564 		else
    565 			inlen -= len;
    566 
    567 		if (pktlen == 0)	/* nothing processed, done */
    568 			continue;
    569 
    570 		buflen += pktlen;
    571 		p = buf;
    572 		if ((buf = realloc(buf, buflen)) == NULL) {
    573 			/* we should free memory if realloc(2) failed */
    574 			free(p);
    575 			saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
    576 			return -1;
    577 		}
    578 		p = buf;
    579 		p += buflen - pktlen;
    580 		memcpy(p, pkt, pktlen);
    581 		free(pkt);
    582 	} while (inlen > 0);
    583 
    584 	*out = buf;
    585 	*outlen = buflen;
    586 	return ate;
    587 }
    588 
    589 /**
    590  * @brief encodes data using method established during the
    591  * authentication. Input data is stored in in and inlen and output
    592  * data is stored in out and outlen.  The caller is responsible for
    593  * freeing the output buffer.
    594  * @param sess sasl session
    595  * @param in input data
    596  * @param inlen input data length
    597  * @param out output data
    598  * @param outlen output data length
    599  * @return 0 on success, -1 on failure
    600  *
    601  * This will output a sequence of full blocks.  When all data has been
    602  * processed, this should be called one more time with inlen = 0 to
    603  * flush any partial block left in the encoder.
    604  */
    605 ssize_t
    606 saslc_sess_encode(saslc_sess_t *sess, const void *in, size_t inlen,
    607 	void **out, size_t *outlen)
    608 {
    609 
    610 	return saslc__sess_xxcode(sess, sess->mech->encode,
    611 	    in, inlen, out, outlen);
    612 }
    613 
    614 /**
    615  * @brief decodes data using method established during the
    616  * authentication. Input data is stored in in and inlen and output
    617  * data is stored in out and outlen.  The caller is responsible for
    618  * freeing the output buffer.
    619  * @param sess sasl session
    620  * @param in input data
    621  * @param inlen input data length
    622  * @param out output data
    623  * @param outlen output data length
    624  * @return 0 on success, -1 on failure
    625  */
    626 ssize_t
    627 saslc_sess_decode(saslc_sess_t *sess, const void *in, size_t inlen,
    628     void **out, size_t *outlen)
    629 {
    630 
    631 	return saslc__sess_xxcode(sess, sess->mech->decode,
    632 	    in, inlen, out, outlen);
    633 }
    634 
    635 /**
    636  * @brief gets string message of the error.
    637  * @param sess sasl session
    638  * @return pointer to the error message
    639  */
    640 const char *
    641 saslc_sess_strerror(saslc_sess_t *sess)
    642 {
    643 
    644 	return saslc__error_get_strerror(ERR(sess));
    645 }
    646 
    647 /**
    648  * @brief gets name of the mechanism used in the sasl session
    649  * @param sess sasl session
    650  * @return pointer to the mechanism name
    651  */
    652 const char *
    653 saslc_sess_getmech(saslc_sess_t *sess)
    654 {
    655 
    656 	return sess->mech->name;
    657 }
    658