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 * 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 + 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 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 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 * 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 * 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 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 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 * 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 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 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 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 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 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 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 * 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 * 657 saslc_sess_getmech(saslc_sess_t *sess) 658 { 659 660 return sess->mech->name; 661 } 662