1 /* $NetBSD: xsess.c,v 1.8 2015/08/08 10:38:35 shm 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.8 2015/08/08 10:38:35 shm 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 *
skip_WS(char * p)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
normalize_list_string(char * opts)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 + 1);
95 }
96 }
97
98 /**
99 * @brief get the security flags from a comma delimited string.
100 * @param sec_opt the security option comman delimited string.
101 * @return the flags on success, or -1 on error (no memory).
102 */
103 static int
get_security_flags(const char * sec_opts)104 get_security_flags(const char *sec_opts)
105 {
106 static const named_flag_t flag_tbl[] = {
107 { "noanonymous", FLAG_ANONYMOUS },
108 { "nodictionary", FLAG_DICTIONARY },
109 { "noplaintext", FLAG_PLAINTEXT },
110 { "noactive", FLAG_ACTIVE },
111 { "mutual", FLAG_MUTUAL },
112 { NULL, FLAG_NONE }
113 };
114 list_t *list;
115 char *opts;
116 uint32_t flags;
117 int rv;
118
119 if (sec_opts == NULL)
120 return 0;
121
122 if ((opts = strdup(sec_opts)) == NULL)
123 return -1;
124
125 normalize_list_string(opts);
126 rv = saslc__list_parse(&list, opts);
127 free(opts);
128 if (rv == -1)
129 return -1;
130 flags = saslc__list_flags(list, flag_tbl);
131 saslc__list_free(list);
132 return flags;
133 }
134
135 /**
136 * @brief compare the mechanism flags with the security option flags
137 * passed by the user and make sure the mechanism is OK.
138 * @param mech mechanism to check.
139 * @param flags security option flags passed by saslc_sess_init().
140 * @return true if the mechanism is permitted and false if not.
141 */
142 static bool
mechanism_flags_OK(const saslc__mech_list_node_t * mech,uint32_t flags)143 mechanism_flags_OK(const saslc__mech_list_node_t *mech, uint32_t flags)
144 {
145 uint32_t reqflags, rejflags;
146
147 if (mech == NULL)
148 return false;
149
150 reqflags = flags & REQ_FLAGS;
151 rejflags = flags & REJ_FLAGS;
152
153 if ((mech->mech->flags & rejflags) != 0)
154 return false;
155
156 if ((mech->mech->flags & reqflags) != reqflags)
157 return false;
158
159 return true;
160 }
161
162 /**
163 * @brief chooses first supported mechanism from the mechs list for
164 * the sasl session.
165 * @param ctx sasl context
166 * @param mechs comma or space separated list of mechanisms
167 * e.g., "PLAIN,LOGIN" or "PLAIN LOGIN".
168 * @param sec_opts comma or space separated list of security options
169 * @return pointer to the mech on success, NULL if none mechanism is chosen
170 *
171 * Note: this uses SASLC_PROP_SECURITY from the context dictionary.
172 * Note: this function is not case sensitive with regard to mechs or sec_opts.
173 */
174 static const saslc__mech_t *
saslc__sess_choose_mech(saslc_t * ctx,const char * mechs,const char * sec_opts)175 saslc__sess_choose_mech(saslc_t *ctx, const char *mechs, const char *sec_opts)
176 {
177 list_t *list, *l;
178 char *tmpstr;
179 const saslc__mech_list_node_t *m;
180 uint32_t flags;
181 int rv;
182
183 rv = get_security_flags(sec_opts);
184 if (rv == -1)
185 goto nomem;
186 flags = rv;
187
188 sec_opts = saslc__dict_get(ctx->prop, SASLC_PROP_SECURITY);
189 if (sec_opts != NULL) {
190 rv = get_security_flags(sec_opts);
191 if (rv == -1)
192 goto nomem;
193 flags |= rv;
194 }
195 if ((tmpstr = strdup(mechs)) == NULL)
196 goto nomem;
197
198 normalize_list_string(tmpstr);
199 rv = saslc__list_parse(&list, tmpstr);
200 free(tmpstr);
201 if (rv == -1)
202 goto nomem;
203
204 m = NULL;
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 if (m == NULL) {
213 saslc__error_set(ERR(ctx), ERROR_MECH,
214 "mechanism not supported");
215 return NULL;
216 }
217 return m->mech;
218 nomem:
219 saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
220 return NULL;
221 }
222
223 /**
224 * @brief sasl session initializaion. Function initializes session
225 * property dictionary, chooses best mechanism, creates mech session.
226 * @param ctx sasl context
227 * @param mechs comma or space separated list of mechanisms eg. "PLAIN,LOGIN"
228 * or "PLAIN LOGIN". Note that this function is not case sensitive.
229 * @return pointer to the sasl session on success, NULL on failure
230 */
231 saslc_sess_t *
saslc_sess_init(saslc_t * ctx,const char * mechs,const char * sec_opts)232 saslc_sess_init(saslc_t *ctx, const char *mechs, const char *sec_opts)
233 {
234 saslc_sess_t *sess;
235 const char *debug;
236 saslc__mech_list_node_t *m;
237
238 if ((sess = calloc(1, sizeof(*sess))) == NULL) {
239 saslc__error_set_errno(ERR(ctx), ERROR_NOMEM);
240 return NULL;
241 }
242
243 /* mechanism initialization */
244 if ((sess->mech = saslc__sess_choose_mech(ctx, mechs, sec_opts))
245 == NULL)
246 goto error;
247
248 /* XXX: special early check of mechanism dictionary for debug flag */
249 m = saslc__mech_list_get(ctx->mechanisms, sess->mech->name);
250 if (m != NULL) {
251 debug = saslc__dict_get(m->prop, SASLC_PROP_DEBUG);
252 if (debug != NULL)
253 saslc_debug = saslc__parser_is_true(debug);
254 }
255
256 /* create mechanism session */
257 if (sess->mech->create(sess) == -1)
258 goto error;
259
260 /* properties */
261 if ((sess->prop = saslc__dict_create()) == NULL) {
262 saslc__error_set(ERR(ctx), ERROR_NOMEM, NULL);
263 goto error;
264 }
265
266 sess->context = ctx;
267 ctx->refcnt++;
268
269 saslc__msg_dbg("mechanism: %s\n", saslc_sess_getmech(sess));
270
271 return sess;
272 error:
273 free(sess);
274 return NULL;
275 }
276
277 /**
278 * @brief ends sasl session, destroys and deallocates internal
279 * resources
280 * @param sess sasl session
281 */
282 void
saslc_sess_end(saslc_sess_t * sess)283 saslc_sess_end(saslc_sess_t *sess)
284 {
285
286 sess->mech->destroy(sess);
287 saslc__dict_destroy(sess->prop);
288 sess->context->refcnt--;
289 free(sess);
290 }
291
292 /**
293 * @brief sets property for the session. If property already exists in
294 * the session, then previous value is replaced by the new value.
295 * @param sess sasl session
296 * @param name property name
297 * @param value property value (if NULL, simply remove previous key)
298 * @return 0 on success, -1 on failure
299 */
300 int
saslc_sess_setprop(saslc_sess_t * sess,const char * key,const char * value)301 saslc_sess_setprop(saslc_sess_t *sess, const char *key, const char *value)
302 {
303
304 /* if the key exists in the session dictionary, remove it */
305 (void)saslc__dict_remove(sess->prop, key);
306
307 if (value == NULL) /* simply remove previous value and return */
308 return 0;
309
310 switch (saslc__dict_insert(sess->prop, key, value)) {
311 case DICT_OK:
312 return 0;
313
314 case DICT_VALBAD:
315 saslc__error_set(ERR(sess), ERROR_BADARG, "bad value");
316 break;
317 case DICT_KEYINVALID:
318 saslc__error_set(ERR(sess), ERROR_BADARG, "bad key");
319 break;
320 case DICT_NOMEM:
321 saslc__error_set(ERR(sess), ERROR_NOMEM, NULL);
322 break;
323 case DICT_KEYEXISTS:
324 case DICT_KEYNOTFOUND:
325 assert(/*CONSTCOND*/0); /* impossible */
326 break;
327 }
328 return -1;
329 }
330
331 /**
332 * @brief gets property from the session. Dictionaries are used
333 * in following order: session dictionary, context dictionary (global
334 * configuration), mechanism dicionary.
335 * @param sess sasl session
336 * @param key property name
337 * @return property value on success, NULL on failure.
338 */
339 const char *
saslc_sess_getprop(saslc_sess_t * sess,const char * key)340 saslc_sess_getprop(saslc_sess_t *sess, const char *key)
341 {
342 const char *r;
343 saslc__mech_list_node_t *m;
344
345 /* get property from the session dictionary */
346 if ((r = saslc__dict_get(sess->prop, key)) != NULL) {
347 saslc__msg_dbg("%s: session dict: %s=%s", __func__, key, r);
348 return r;
349 }
350
351 /* get property from the context dictionary */
352 if ((r = saslc__dict_get(sess->context->prop, key)) != NULL) {
353 saslc__msg_dbg("%s: context dict: %s=%s", __func__, key, r);
354 return r;
355 }
356
357 /* get property from the mechanism dictionary */
358 if ((m = saslc__mech_list_get(sess->context->mechanisms,
359 sess->mech->name)) == NULL)
360 return NULL;
361
362 if ((r = saslc__dict_get(m->prop, key)) != NULL)
363 saslc__msg_dbg("%s: mech %s dict: %s=%s", __func__,
364 saslc_sess_getmech(sess), key, r);
365 else
366 saslc__msg_dbg("%s: %s not found", __func__, key);
367 return r;
368 }
369
370 /**
371 * @brief set the sess->flags accordingly according to the properties.
372 * @param sess saslc session
373 */
374 static uint32_t
saslc__sess_get_flags(saslc_sess_t * sess)375 saslc__sess_get_flags(saslc_sess_t *sess)
376 {
377 const char *base64io;
378 uint32_t flags;
379
380 /* set default flags */
381 flags = SASLC_FLAGS_DEFAULT;
382
383 base64io = saslc_sess_getprop(sess, SASLC_PROP_BASE64IO);
384 if (base64io != NULL) {
385 if (saslc__parser_is_true(base64io))
386 flags |= SASLC_FLAGS_BASE64;
387 else
388 flags &= ~SASLC_FLAGS_BASE64;
389 }
390 return flags;
391 }
392
393 /**
394 * @brief does one step of the sasl authentication, input data
395 * and its lenght are stored in in and inlen, output is stored in out and
396 * outlen. This function is a wrapper for mechanism step functions.
397 * Additionaly it checks if session is not already authorized and handles
398 * steps mech_sess structure.
399 * @param sess saslc session
400 * @param in input data
401 * @param inlen input data length
402 * @param out output data
403 * @param outlen output data length
404 * @return MECH_OK - on success, no more steps are needed
405 * MECH_ERROR - on error, additionaly errno in sess is setup
406 * MECH_STEP - more steps are needed
407 */
408 int
saslc_sess_cont(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)409 saslc_sess_cont(saslc_sess_t *sess, const void *in, size_t inlen,
410 void **out, size_t *outlen)
411 {
412 saslc__mech_sess_t *ms;
413 const char *debug;
414 void *dec;
415 int rv;
416
417 ms = sess->mech_sess;
418 if (ms->status == STATUS_AUTHENTICATED) {
419 saslc__error_set(ERR(sess), ERROR_MECH,
420 "session authenticated");
421 return MECH_ERROR;
422 }
423 if (ms->step == 0) {
424 sess->flags = saslc__sess_get_flags(sess);
425
426 /* XXX: final check for any session debug flag setting */
427 debug = saslc__dict_get(sess->prop, SASLC_PROP_DEBUG);
428 if (debug != NULL)
429 saslc_debug = saslc__parser_is_true(debug);
430 }
431
432 saslc__msg_dbg("%s: encoded: inlen=%zu in='%s'", __func__, inlen,
433 in ? (const char *)in : "<null>");
434 if (inlen == 0 || (sess->flags & SASLC_FLAGS_BASE64_IN) == 0)
435 dec = NULL;
436 else {
437 if (saslc__crypto_decode_base64(in, inlen, &dec, &inlen)
438 == -1) {
439 saslc__error_set(ERR(sess), ERROR_MECH,
440 "base64 decode failed");
441 return MECH_ERROR;
442 }
443 in = dec;
444 }
445 saslc__msg_dbg("%s: decoded: inlen=%zu in='%s'", __func__, inlen,
446 in ? (const char *)in : "<null>");
447 rv = sess->mech->cont(sess, in, inlen, out, outlen);
448 if (dec != NULL)
449 free(dec);
450 if (rv == MECH_ERROR)
451 return MECH_ERROR;
452
453 saslc__msg_dbg("%s: out='%s'", __func__,
454 *outlen ? (char *)*out : "<null>");
455 if (*outlen == 0)
456 *out = NULL; /* XXX: unnecessary? */
457 else if ((sess->flags & SASLC_FLAGS_BASE64_OUT) != 0) {
458 char *enc;
459 size_t enclen;
460
461 if (saslc__crypto_encode_base64(*out, *outlen, &enc, &enclen)
462 == -1) {
463 free(*out);
464 return MECH_ERROR;
465 }
466 free(*out);
467 *out = enc;
468 *outlen = enclen;
469 }
470 if (rv == MECH_OK)
471 ms->status = STATUS_AUTHENTICATED;
472
473 ms->step++;
474 return rv;
475 }
476
477 /**
478 * @brief copies input data to an allocated buffer. The caller is
479 * responsible for freeing the buffer.
480 * @param sess sasl session
481 * @param xxcode codec to encode or decode one block of data
482 * @param in input data
483 * @param inlen input data length
484 * @param out output data
485 * @param outlen output data length
486 * @return number of bytes copied on success, -1 on failure
487 */
488 static ssize_t
saslc__sess_copyout(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)489 saslc__sess_copyout(saslc_sess_t *sess, const void *in, size_t inlen,
490 void **out, size_t *outlen)
491 {
492
493 *out = malloc(inlen);
494 if (*out == NULL) {
495 *outlen = 0;
496 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
497 return -1;
498 }
499 *outlen = inlen;
500 memcpy(*out, in, inlen);
501 return inlen;
502 }
503
504 /**
505 * @brief encodes or decode data using method established during the
506 * authentication. Input data is stored in in and inlen and output
507 * data is stored in out and outlen. The caller is responsible for
508 * freeing the output buffer.
509 * @param sess sasl session
510 * @param xxcode codec to encode or decode one block of data
511 * @param in input data
512 * @param inlen input data length
513 * @param out output data
514 * @param outlen output data length
515 * @return number of bytes consumed on success, 0 if insufficient data
516 * to process, -1 on failure
517 *
518 * 'xxcode' encodes or decodes a single block of data and stores the
519 * resulting block and its length in 'out' and 'outlen', respectively.
520 * It should return the number of bytes it digested or -1 on error.
521 * If it was unable to process a complete block, it should return zero
522 * and remember the partial block internally. If it is called with
523 * 'inlen' = 0, it should flush out any remaining partial block data
524 * and return the number of stored bytes it flushed or zero if there
525 * were none (relevant for the encoder only).
526 */
527 static ssize_t
saslc__sess_xxcode(saslc_sess_t * sess,saslc__mech_xxcode_t xxcode,const void * in,size_t inlen,void ** out,size_t * outlen)528 saslc__sess_xxcode(saslc_sess_t *sess, saslc__mech_xxcode_t xxcode,
529 const void *in, size_t inlen, void **out, size_t *outlen)
530 {
531 saslc__mech_sess_t *ms;
532 unsigned char *p;
533 void *buf, *pkt;
534 size_t buflen, pktlen;
535 ssize_t len, ate;
536
537 ms = sess->mech_sess;
538
539 if (xxcode == NULL) {
540 saslc__error_set(ERR(sess), ERROR_MECH,
541 "security layer is not supported by mechanism");
542 return -1;
543 }
544 if (ms->status != STATUS_AUTHENTICATED) {
545 saslc__error_set(ERR(sess), ERROR_MECH,
546 "session is not authenticated");
547 return -1;
548 }
549
550 if (ms->qop == QOP_NONE)
551 return saslc__sess_copyout(sess, in, inlen, out, outlen);
552
553 p = NULL;
554 buf = NULL;
555 buflen = 0;
556 ate = 0;
557 do {
558 len = xxcode(sess, in, inlen, &pkt, &pktlen);
559 if (len == -1) {
560 free(buf);
561 return -1;
562 }
563
564 ate += len;
565 in = (const char *)in + len;
566 if (inlen < (size_t)len)
567 inlen = 0;
568 else
569 inlen -= len;
570
571 if (pktlen == 0) /* nothing processed, done */
572 continue;
573
574 buflen += pktlen;
575 p = buf;
576 if ((buf = realloc(buf, buflen)) == NULL) {
577 /* we should free memory if realloc(2) failed */
578 free(p);
579 saslc__error_set_errno(ERR(sess), ERROR_NOMEM);
580 return -1;
581 }
582 p = buf;
583 p += buflen - pktlen;
584 memcpy(p, pkt, pktlen);
585 free(pkt);
586 } while (inlen > 0);
587
588 *out = buf;
589 *outlen = buflen;
590 return ate;
591 }
592
593 /**
594 * @brief encodes data using method established during the
595 * authentication. Input data is stored in in and inlen and output
596 * data is stored in out and outlen. The caller is responsible for
597 * freeing the output buffer.
598 * @param sess sasl session
599 * @param in input data
600 * @param inlen input data length
601 * @param out output data
602 * @param outlen output data length
603 * @return 0 on success, -1 on failure
604 *
605 * This will output a sequence of full blocks. When all data has been
606 * processed, this should be called one more time with inlen = 0 to
607 * flush any partial block left in the encoder.
608 */
609 ssize_t
saslc_sess_encode(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)610 saslc_sess_encode(saslc_sess_t *sess, const void *in, size_t inlen,
611 void **out, size_t *outlen)
612 {
613
614 return saslc__sess_xxcode(sess, sess->mech->encode,
615 in, inlen, out, outlen);
616 }
617
618 /**
619 * @brief decodes data using method established during the
620 * authentication. Input data is stored in in and inlen and output
621 * data is stored in out and outlen. The caller is responsible for
622 * freeing the output buffer.
623 * @param sess sasl session
624 * @param in input data
625 * @param inlen input data length
626 * @param out output data
627 * @param outlen output data length
628 * @return 0 on success, -1 on failure
629 */
630 ssize_t
saslc_sess_decode(saslc_sess_t * sess,const void * in,size_t inlen,void ** out,size_t * outlen)631 saslc_sess_decode(saslc_sess_t *sess, const void *in, size_t inlen,
632 void **out, size_t *outlen)
633 {
634
635 return saslc__sess_xxcode(sess, sess->mech->decode,
636 in, inlen, out, outlen);
637 }
638
639 /**
640 * @brief gets string message of the error.
641 * @param sess sasl session
642 * @return pointer to the error message
643 */
644 const char *
saslc_sess_strerror(saslc_sess_t * sess)645 saslc_sess_strerror(saslc_sess_t *sess)
646 {
647
648 return saslc__error_get_strerror(ERR(sess));
649 }
650
651 /**
652 * @brief gets name of the mechanism used in the sasl session
653 * @param sess sasl session
654 * @return pointer to the mechanism name
655 */
656 const char *
saslc_sess_getmech(saslc_sess_t * sess)657 saslc_sess_getmech(saslc_sess_t *sess)
658 {
659
660 return sess->mech->name;
661 }
662