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