xref: /netbsd-src/crypto/external/bsd/libsaslc/dist/src/xsess.c (revision daf6c4152fcddc27c445489775ed1f66ab4ea9a9)
1 /* $NetBSD: xsess.c,v 1.5 2011/02/12 23:21:32 christos 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. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 #include <sys/cdefs.h>
39 __RCSID("$NetBSD: xsess.c,v 1.5 2011/02/12 23:21:32 christos Exp $");
40 
41 #include <assert.h>
42 #include <saslc.h>
43 #include <stdio.h>
44 #include <string.h>
45 
46 #include "crypto.h"
47 #include "dict.h"
48 #include "error.h"
49 #include "list.h"
50 #include "msg.h"
51 #include "mech.h"
52 #include "parser.h"
53 #include "saslc_private.h"
54 
55 /*
56  * TODO:
57  *
58  * 1) Add hooks to allow saslc_sess_encode() and saslc_sess_decode()
59  * to output and input, respectively, base64 encoded data much like
60  * what sess_saslc_cont() does according to the SASLC_FLAGS_BASE64_*
61  * flags.  For saslc_sess_decode() it seems it would be easiest to do
62  * this in saslc__buffer32_fetch() pushing any extra buffering into
63  * the BIO_* routines, but I haven't thought this through carefully
64  * yet.
65  */
66 
67 static inline char *
68 skip_WS(char *p)
69 {
70 
71 	while (*p == ' ' || *p == '\t')
72 		p++;
73 	return p;
74 }
75 
76 /**
77  * @brief convert a comma and/or space delimited list into a comma
78  * delimited list of the form:
79  *   ( *LWS element *( *LWS "," *LWS element ))
80  * @param str string to convert.
81  */
82 static void
83 normalize_list_string(char *opts)
84 {
85 	char *p;
86 
87 	p = opts;
88 	while (p != NULL) {
89 		p = strchr(p, ' ');
90 		if (p == NULL)
91 			break;
92 		if (p > opts && p[-1] != ',')
93 			*p++ = ',';
94 		p = skip_WS(++p);
95 	}
96 }
97 
98 static int
99 get_security_flags(const char *sec_opts)
100 {
101 	static const named_flag_t flag_tbl[] = {
102 		{ "noanonymous",	FLAG_ANONYMOUS },
103 		{ "nodictionary",	FLAG_DICTIONARY },
104 		{ "noplaintext",	FLAG_PLAINTEXT },
105 		{ "noactive",		FLAG_ACTIVE },
106 		{ "mutual",		FLAG_MUTUAL },
107 		{ NULL,			FLAG_NONE }
108 	};
109 	list_t *list;
110 	char *opts;
111 	uint32_t flags;
112 
113 	if (sec_opts == NULL)
114 		return 0;
115 
116 	if ((opts = strdup(sec_opts)) == NULL)
117 		return -1;
118 
119 	normalize_list_string(opts);
120 	list = saslc__list_parse(opts);
121 	free(opts);
122 	if (list == NULL)
123 		return -1;
124 
125 	flags = saslc__list_flags(list, flag_tbl);
126 	saslc__list_free(list);
127 	return flags;
128 }
129 
130 /**
131  * @brief compare the mechanism flags with the security option flags
132  * passed by the user and make sure the mechanism is OK.
133  * @param mech mechanism to check.
134  * @param flags security option flags passed by saslc_sess_init().
135  * @return true if the mechanism is permitted and false if not.
136  */
137 static bool
138 mechanism_flags_OK(const saslc__mech_list_node_t *mech, uint32_t flags)
139 {
140 	uint32_t reqflags, rejflags;
141 
142 	if (mech == NULL)
143 		return false;
144 
145 	reqflags = flags & REQ_FLAGS;
146 	rejflags = flags & REJ_FLAGS;
147 
148 	if ((mech->mech->flags & rejflags) != 0)
149 		return false;
150 
151 	if ((mech->mech->flags & reqflags) != reqflags)
152 		return false;
153 
154 	return true;
155 }
156 
157 /**
158  * @brief chooses first supported mechanism from the mechs list for
159  * the sasl session.
160  * @param ctx sasl context
161  * @param mechs comma or space separated list of mechanisms
162  * e.g., "PLAIN,LOGIN" or "PLAIN LOGIN".
163  * @param sec_opts comma or space separated list of security options
164  * @return pointer to the mech on success, NULL if none mechanism is chosen
165  *
166  * Note: this uses SASLC_PROP_SECURITY from the context dictionary.
167  * Note: this function is not case sensitive with regard to mechs or sec_opts.
168  */
169 static const saslc__mech_t *
170 saslc__sess_choose_mech(saslc_t *ctx, const char *mechs, const char *sec_opts)
171 {
172 	list_t *list, *l;
173 	char *tmpstr;
174 	const saslc__mech_list_node_t *m;
175 	uint32_t flags;
176 	int rv;
177 
178 	rv = get_security_flags(sec_opts);
179 	if (rv == -1) {
180 		saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
181 		return NULL;
182 	}
183 	flags = rv;
184 
185 	sec_opts = saslc__dict_get(ctx->prop, SASLC_PROP_SECURITY);
186 	if (sec_opts != NULL) {
187 		rv = get_security_flags(sec_opts);
188 		if (rv == -1) {
189 			saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
190 			return NULL;
191 		}
192 		flags |= rv;
193 	}
194 	if ((tmpstr = strdup(mechs)) == NULL) {
195 		saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
196 		return NULL;
197 	}
198 	normalize_list_string(tmpstr);
199 	list = saslc__list_parse(tmpstr);
200 	free(tmpstr);
201 	if (list == NULL) {
202 		saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
203 		return NULL;
204 	}
205 	for (l = list; l != NULL; l = l->next) {
206 		m = saslc__mech_list_get(ctx->mechanisms, l->value);
207 		if (mechanism_flags_OK(m, flags))
208 			break;
209 	}
210 	saslc__list_free(list);
211 
212 	return m != NULL ? m->mech : NULL;
213 }
214 
215 /**
216  * @brief sasl session initializaion. Function initializes session
217  * property dictionary, chooses best mechanism, creates mech session.
218  * @param ctx sasl context
219  * @param mechs comma or space separated list of mechanisms eg. "PLAIN,LOGIN"
220  * or "PLAIN LOGIN".  Note that this function is not case sensitive.
221  * @return pointer to the sasl session on success, NULL on failure
222  */
223 saslc_sess_t *
224 saslc_sess_init(saslc_t *ctx, const char *mechs, const char *sec_opts)
225 {
226 	saslc_sess_t *sess;
227 	const char *debug;
228 	saslc__mech_list_node_t *m;
229 
230 	if ((sess = calloc(1, sizeof(*sess))) == NULL) {
231 		saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
232 		return NULL;
233 	}
234 
235 	/* mechanism initialization */
236 	if ((sess->mech = saslc__sess_choose_mech(ctx, mechs, sec_opts))
237 	    == NULL) {
238 		saslc__error_set(ERR(ctx), ERROR_MECH,
239 		    "mechanism is not supported");
240 		goto error;
241 	}
242 
243 	/* XXX: special early check of mechanism dictionary for debug flag */
244 	m = saslc__mech_list_get(ctx->mechanisms, sess->mech->name);
245 	if (m != NULL) {
246 		debug = saslc__dict_get(m->prop, SASLC_PROP_DEBUG);
247 		if (debug != NULL)
248 			saslc_debug = saslc__parser_is_true(debug);
249 	}
250 
251 	/* create mechanism session */
252 	if (sess->mech->create(sess) == -1)
253 		goto error;
254 
255 	/* properties */
256 	if ((sess->prop = saslc__dict_create()) == NULL) {
257 		saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
258 		goto error;
259 	}
260 
261 	sess->context = ctx;
262 	ctx->refcnt++;
263 
264 	saslc__msg_dbg("mechanism: %s\n", saslc_sess_getmech(sess));
265 
266 	return sess;
267 
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)  /* error */
556 			return -1;
557 
558 		ate += len;
559 		in = (const char *)in + len;
560 		if (inlen < (size_t)len)
561 			inlen = 0;
562 		else
563 			inlen -= len;
564 
565 		if (pktlen == 0)	/* nothing processed, done */
566 			continue;
567 
568 		buflen += pktlen;
569 		if ((buf = realloc(buf, buflen)) == NULL) {
570 			saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
571 			return -1;
572 		}
573 		p = buf;
574 		p += buflen - pktlen;
575 		memcpy(p, pkt, pktlen);
576 		free(pkt);
577 	} while (inlen > 0);
578 
579 	*out = buf;
580 	*outlen = buflen;
581 	return ate;
582 }
583 
584 /**
585  * @brief encodes data using method established during the
586  * authentication. Input data is stored in in and inlen and output
587  * data is stored in out and outlen.  The caller is responsible for
588  * freeing the output buffer.
589  * @param sess sasl session
590  * @param in input data
591  * @param inlen input data length
592  * @param out output data
593  * @param outlen output data length
594  * @return 0 on success, -1 on failure
595  *
596  * This will output a sequence of full blocks.  When all data has been
597  * processed, this should be called one more time with inlen = 0 to
598  * flush any partial block left in the encoder.
599  */
600 ssize_t
601 saslc_sess_encode(saslc_sess_t *sess, const void *in, size_t inlen,
602 	void **out, size_t *outlen)
603 {
604 
605 	return saslc__sess_xxcode(sess, sess->mech->encode,
606 	    in, inlen, out, outlen);
607 }
608 
609 /**
610  * @brief decodes data using method established during the
611  * authentication. Input data is stored in in and inlen and output
612  * data is stored in out and outlen.  The caller is responsible for
613  * freeing the output buffer.
614  * @param sess sasl session
615  * @param in input data
616  * @param inlen input data length
617  * @param out output data
618  * @param outlen output data length
619  * @return 0 on success, -1 on failure
620  */
621 ssize_t
622 saslc_sess_decode(saslc_sess_t *sess, const void *in, size_t inlen,
623     void **out, size_t *outlen)
624 {
625 
626 	return saslc__sess_xxcode(sess, sess->mech->decode,
627 	    in, inlen, out, outlen);
628 }
629 
630 /**
631  * @brief gets string message of the error.
632  * @param sess sasl session
633  * @return pointer to the error message
634  */
635 const char *
636 saslc_sess_strerror(saslc_sess_t *sess)
637 {
638 
639 	return saslc__error_get_strerror(ERR(sess));
640 }
641 
642 /**
643  * @brief gets name of the mechanism used in the sasl session
644  * @param sess sasl session
645  * @return pointer to the mechanism name
646  */
647 const char *
648 saslc_sess_getmech(saslc_sess_t *sess)
649 {
650 
651 	return sess->mech->name;
652 }
653