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