1 /* $NetBSD: xsasl_cyrus_client.c,v 1.3 2020/03/18 19:05:22 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* xsasl_cyrus_client 3 6 /* SUMMARY 7 /* Cyrus SASL client-side plug-in 8 /* SYNOPSIS 9 /* #include <xsasl_cyrus_client.h> 10 /* 11 /* XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info) 12 /* const char *client_type; 13 /* DESCRIPTION 14 /* This module implements the Cyrus SASL client-side authentication 15 /* plug-in. 16 /* 17 /* xsasl_cyrus_client_init() initializes the Cyrus SASL library and 18 /* returns an implementation handle that can be used to generate 19 /* SASL client instances. 20 /* 21 /* Arguments: 22 /* .IP client_type 23 /* The plug-in SASL client type (cyrus). This argument is 24 /* ignored, but it could be used when one implementation 25 /* provides multiple variants. 26 /* .IP path_info 27 /* Implementation-specific information to specify the location 28 /* of a configuration file, rendez-vous point, etc. This 29 /* information is ignored by the Cyrus SASL client plug-in. 30 /* DIAGNOSTICS 31 /* Fatal: out of memory. 32 /* 33 /* Panic: interface violation. 34 /* 35 /* Other: the routines log a warning and return an error result 36 /* as specified in xsasl_client(3). 37 /* SEE ALSO 38 /* xsasl_client(3) Client API 39 /* LICENSE 40 /* .ad 41 /* .fi 42 /* The Secure Mailer license must be distributed with this software. 43 /* AUTHOR(S) 44 /* Original author: 45 /* Till Franke 46 /* SuSE Rhein/Main AG 47 /* 65760 Eschborn, Germany 48 /* 49 /* Adopted by: 50 /* Wietse Venema 51 /* IBM T.J. Watson Research 52 /* P.O. Box 704 53 /* Yorktown Heights, NY 10598, USA 54 /* 55 /* Wietse Venema 56 /* Google, Inc. 57 /* 111 8th Avenue 58 /* New York, NY 10011, USA 59 /*--*/ 60 61 /* 62 * System library. 63 */ 64 #include <sys_defs.h> 65 #include <stdlib.h> 66 #include <string.h> 67 68 /* 69 * Utility library 70 */ 71 #include <msg.h> 72 #include <mymalloc.h> 73 #include <stringops.h> 74 75 /* 76 * Global library 77 */ 78 #include <mail_params.h> 79 80 /* 81 * Application-specific 82 */ 83 #include <xsasl.h> 84 #include <xsasl_cyrus.h> 85 #include <xsasl_cyrus_common.h> 86 87 #if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) 88 89 #include <sasl.h> 90 #include <saslutil.h> 91 92 /* 93 * Silly little macros. 94 */ 95 #define STR(s) vstring_str(s) 96 97 /* 98 * Macros to handle API differences between SASLv1 and SASLv2. Specifics: 99 * 100 * The SASL_LOG_* constants were renamed in SASLv2. 101 * 102 * SASLv2's sasl_client_new takes two new parameters to specify local and 103 * remote IP addresses for auth mechs that use them. 104 * 105 * SASLv2's sasl_client_start function no longer takes the secret parameter. 106 * 107 * SASLv2's sasl_decode64 function takes an extra parameter for the length of 108 * the output buffer. 109 * 110 * The other major change is that SASLv2 now takes more responsibility for 111 * deallocating memory that it allocates internally. Thus, some of the 112 * function parameters are now 'const', to make sure we don't try to free 113 * them too. This is dealt with in the code later on. 114 */ 115 #if SASL_VERSION_MAJOR < 2 116 /* SASL version 1.x */ 117 #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ 118 sasl_client_new(srv, fqdn, prompt, secflags, pconn) 119 #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ 120 sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech) 121 #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 122 sasl_decode64(in, inlen, out, outlen) 123 typedef char *CLIENTOUT_TYPE; 124 125 #endif 126 127 #if SASL_VERSION_MAJOR >= 2 128 /* SASL version > 2.x */ 129 #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ 130 sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn) 131 #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ 132 sasl_client_start(conn, mechlst, prompt, clout, cllen, mech) 133 #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 134 sasl_decode64(in, inlen, out, outmaxlen, outlen) 135 typedef const char *CLIENTOUT_TYPE; 136 137 #endif 138 139 /* 140 * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT 141 * object. 142 */ 143 typedef struct { 144 XSASL_CLIENT xsasl; /* generic members, must be first */ 145 VSTREAM *stream; /* client-server connection */ 146 sasl_conn_t *sasl_conn; /* SASL context */ 147 VSTRING *decoded; /* decoded server challenge */ 148 sasl_callback_t *callbacks; /* user/password lookup */ 149 char *username; 150 char *password; 151 } XSASL_CYRUS_CLIENT; 152 153 /* 154 * Forward declarations. 155 */ 156 static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *); 157 static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *, 158 XSASL_CLIENT_CREATE_ARGS *); 159 static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *); 160 static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *, 161 const char *, const char **, VSTRING *); 162 static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *); 163 static void xsasl_cyrus_client_free(XSASL_CLIENT *); 164 165 /* xsasl_cyrus_client_get_user - username lookup call-back routine */ 166 167 static int xsasl_cyrus_client_get_user(void *context, int unused_id, 168 const char **result, 169 unsigned *len) 170 { 171 const char *myname = "xsasl_cyrus_client_get_user"; 172 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; 173 174 if (msg_verbose) 175 msg_info("%s: %s", myname, client->username); 176 177 /* 178 * Sanity check. 179 */ 180 if (client->password == 0) 181 msg_panic("%s: no username looked up", myname); 182 183 *result = client->username; 184 if (len) 185 *len = strlen(client->username); 186 return (SASL_OK); 187 } 188 189 /* xsasl_cyrus_client_get_passwd - password lookup call-back routine */ 190 191 static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context, 192 int id, sasl_secret_t **psecret) 193 { 194 const char *myname = "xsasl_cyrus_client_get_passwd"; 195 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; 196 int len; 197 198 if (msg_verbose) 199 msg_info("%s: %s", myname, client->password); 200 201 /* 202 * Sanity check. 203 */ 204 if (!conn || !psecret || id != SASL_CB_PASS) 205 return (SASL_BADPARAM); 206 if (client->password == 0) 207 msg_panic("%s: no password looked up", myname); 208 209 /* 210 * Convert the password into a counted string. 211 */ 212 len = strlen(client->password); 213 if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0) 214 return (SASL_NOMEM); 215 (*psecret)->len = len; 216 memcpy((*psecret)->data, client->password, len + 1); 217 218 return (SASL_OK); 219 } 220 221 /* xsasl_cyrus_client_init - initialize Cyrus SASL library */ 222 223 XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type, 224 const char *unused_path_info) 225 { 226 XSASL_CLIENT_IMPL *xp; 227 int sasl_status; 228 229 /* 230 * Global callbacks. These have no per-session context. 231 */ 232 static sasl_callback_t callbacks[] = { 233 {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0}, 234 {SASL_CB_LIST_END, 0, 0} 235 }; 236 237 #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ 238 || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) 239 int sasl_major; 240 int sasl_minor; 241 int sasl_step; 242 243 /* 244 * DLL hell guard. 245 */ 246 sasl_version_info((const char **) 0, (const char **) 0, 247 &sasl_major, &sasl_minor, 248 &sasl_step, (int *) 0); 249 if (sasl_major != SASL_VERSION_MAJOR 250 #if 0 251 || sasl_minor != SASL_VERSION_MINOR 252 || sasl_step != SASL_VERSION_STEP 253 #endif 254 ) { 255 msg_warn("incorrect SASL library version. " 256 "Postfix was built with include files from version %d.%d.%d, " 257 "but the run-time library version is %d.%d.%d", 258 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, 259 sasl_major, sasl_minor, sasl_step); 260 return (0); 261 } 262 #endif 263 264 if (*var_cyrus_conf_path) { 265 #ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ 266 if (sasl_set_path(SASL_PATH_TYPE_CONFIG, 267 var_cyrus_conf_path) != SASL_OK) 268 msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", 269 var_cyrus_conf_path); 270 #else 271 msg_warn("%s is not empty, but setting the Cyrus SASL configuration " 272 "path is not supported with SASL library version %d.%d.%d", 273 VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, 274 SASL_VERSION_MINOR, SASL_VERSION_STEP); 275 #endif 276 } 277 278 /* 279 * Initialize the SASL library. 280 */ 281 if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) { 282 msg_warn("SASL library initialization error: %s", 283 xsasl_cyrus_strerror(sasl_status)); 284 return (0); 285 } 286 287 /* 288 * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it 289 * with our own methods or data. 290 */ 291 xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp)); 292 xp->create = xsasl_cyrus_client_create; 293 xp->done = xsasl_cyrus_client_done; 294 return (xp); 295 } 296 297 /* xsasl_cyrus_client_done - dispose of implementation */ 298 299 static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl) 300 { 301 myfree((void *) impl); 302 sasl_done(); 303 } 304 305 /* xsasl_cyrus_client_create - per-session SASL initialization */ 306 307 XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl, 308 XSASL_CLIENT_CREATE_ARGS *args) 309 { 310 XSASL_CYRUS_CLIENT *client = 0; 311 static sasl_callback_t callbacks[] = { 312 {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, 313 {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, 314 {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0}, 315 {SASL_CB_LIST_END, 0, 0} 316 }; 317 sasl_conn_t *sasl_conn = 0; 318 sasl_callback_t *custom_callbacks = 0; 319 sasl_callback_t *cp; 320 int sasl_status; 321 322 /* 323 * The optimizer will eliminate code duplication and/or dead code. 324 */ 325 #define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \ 326 do { \ 327 if (client) { \ 328 xsasl_cyrus_client_free(&client->xsasl); \ 329 } else { \ 330 if (custom_callbacks) \ 331 myfree((void *) custom_callbacks); \ 332 if (sasl_conn) \ 333 sasl_dispose(&sasl_conn); \ 334 } \ 335 return (x); \ 336 } while (0) 337 338 /* 339 * Per-session initialization. Provide each session with its own callback 340 * context. 341 */ 342 #define NULL_SECFLAGS 0 343 344 custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); 345 memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks)); 346 347 #define NULL_SERVER_ADDR ((char *) 0) 348 #define NULL_CLIENT_ADDR ((char *) 0) 349 350 if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name, 351 NULL_CLIENT_ADDR, NULL_SERVER_ADDR, 352 var_cyrus_sasl_authzid ? custom_callbacks : 353 custom_callbacks + 1, NULL_SECFLAGS, 354 &sasl_conn)) != SASL_OK) { 355 msg_warn("per-session SASL client initialization: %s", 356 xsasl_cyrus_strerror(sasl_status)); 357 XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); 358 } 359 360 /* 361 * Extend the XSASL_CLIENT object with our own state. We use long-lived 362 * conversion buffers rather than local variables to avoid memory leaks 363 * in case of read/write timeout or I/O error. 364 * 365 * XXX If we enable SASL encryption, there needs to be a way to inform the 366 * application, so that they can turn off connection caching, refuse 367 * STARTTLS, etc. 368 */ 369 client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client)); 370 client->xsasl.free = xsasl_cyrus_client_free; 371 client->xsasl.first = xsasl_cyrus_client_first; 372 client->xsasl.next = xsasl_cyrus_client_next; 373 client->stream = args->stream; 374 client->sasl_conn = sasl_conn; 375 client->callbacks = custom_callbacks; 376 client->decoded = vstring_alloc(20); 377 client->username = 0; 378 client->password = 0; 379 380 for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++) 381 cp->context = (void *) client; 382 383 if (xsasl_cyrus_client_set_security(&client->xsasl, 384 args->security_options) 385 != XSASL_AUTH_OK) 386 XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); 387 388 return (&client->xsasl); 389 } 390 391 /* xsasl_cyrus_client_set_security - set security properties */ 392 393 static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp, 394 const char *sasl_opts_val) 395 { 396 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; 397 sasl_security_properties_t sec_props; 398 int sasl_status; 399 400 /* 401 * Per-session security properties. XXX This routine is not sufficiently 402 * documented. What is the purpose of all this? 403 */ 404 memset(&sec_props, 0, sizeof(sec_props)); 405 sec_props.min_ssf = 0; 406 sec_props.max_ssf = 0; /* don't allow real SASL 407 * security layer */ 408 if (*sasl_opts_val == 0) { 409 sec_props.security_flags = 0; 410 } else { 411 sec_props.security_flags = 412 xsasl_cyrus_security_parse_opts(sasl_opts_val); 413 if (sec_props.security_flags == 0) { 414 msg_warn("bad per-session SASL security properties"); 415 return (XSASL_AUTH_FAIL); 416 } 417 } 418 sec_props.maxbufsize = 0; 419 sec_props.property_names = 0; 420 sec_props.property_values = 0; 421 if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS, 422 &sec_props)) != SASL_OK) { 423 msg_warn("set per-session SASL security properties: %s", 424 xsasl_cyrus_strerror(sasl_status)); 425 return (XSASL_AUTH_FAIL); 426 } 427 return (XSASL_AUTH_OK); 428 } 429 430 /* xsasl_cyrus_client_first - run authentication protocol */ 431 432 static int xsasl_cyrus_client_first(XSASL_CLIENT *xp, 433 const char *mechanism_list, 434 const char *username, 435 const char *password, 436 const char **mechanism, 437 VSTRING *init_resp) 438 { 439 const char *myname = "xsasl_cyrus_client_first"; 440 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; 441 unsigned enc_length; 442 unsigned enc_length_out; 443 CLIENTOUT_TYPE clientout; 444 unsigned clientoutlen; 445 int sasl_status; 446 447 #define NO_SASL_SECRET 0 448 #define NO_SASL_INTERACTION 0 449 450 /* 451 * Save the username and password for the call-backs. 452 */ 453 if (client->username) 454 myfree(client->username); 455 client->username = mystrdup(username); 456 if (client->password) 457 myfree(client->password); 458 client->password = mystrdup(password); 459 460 /* 461 * Start the client side authentication protocol. 462 */ 463 sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn, 464 mechanism_list, 465 NO_SASL_SECRET, NO_SASL_INTERACTION, 466 &clientout, &clientoutlen, mechanism); 467 if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { 468 vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status)); 469 return (XSASL_AUTH_FAIL); 470 } 471 472 /* 473 * Generate the AUTH command and the optional initial client response. 474 * sasl_encode64() produces four bytes for each complete or incomplete 475 * triple of input bytes. Allocate an extra byte for string termination. 476 */ 477 #define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) 478 479 if (clientoutlen > 0) { 480 if (msg_verbose) { 481 escape(client->decoded, clientout, clientoutlen); 482 msg_info("%s: uncoded initial reply: %s", 483 myname, STR(client->decoded)); 484 } 485 enc_length = ENCODE64_LENGTH(clientoutlen) + 1; 486 VSTRING_RESET(init_resp); /* Fix 200512 */ 487 VSTRING_SPACE(init_resp, enc_length); 488 if ((sasl_status = sasl_encode64(clientout, clientoutlen, 489 STR(init_resp), 490 vstring_avail(init_resp), 491 &enc_length_out)) != SASL_OK) 492 msg_panic("%s: sasl_encode64 botch: %s", 493 myname, xsasl_cyrus_strerror(sasl_status)); 494 vstring_set_payload_size(init_resp, enc_length_out); 495 #if SASL_VERSION_MAJOR < 2 496 /* SASL version 1 doesn't free memory that it allocates. */ 497 free(clientout); 498 #endif 499 } else { 500 vstring_strcpy(init_resp, ""); 501 } 502 return (XSASL_AUTH_OK); 503 } 504 505 /* xsasl_cyrus_client_next - continue authentication */ 506 507 static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply, 508 VSTRING *client_reply) 509 { 510 const char *myname = "xsasl_cyrus_client_next"; 511 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; 512 unsigned enc_length; 513 unsigned enc_length_out; 514 CLIENTOUT_TYPE clientout; 515 unsigned clientoutlen; 516 unsigned serverinlen; 517 int sasl_status; 518 519 /* 520 * Process a server challenge. 521 */ 522 serverinlen = strlen(server_reply); 523 VSTRING_RESET(client->decoded); /* Fix 200512 */ 524 VSTRING_SPACE(client->decoded, serverinlen); 525 if ((sasl_status = SASL_DECODE64(server_reply, serverinlen, 526 STR(client->decoded), 527 vstring_avail(client->decoded), 528 &enc_length)) != SASL_OK) { 529 vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); 530 return (XSASL_AUTH_FORM); 531 } 532 if (msg_verbose) 533 msg_info("%s: decoded challenge: %.*s", 534 myname, (int) enc_length, STR(client->decoded)); 535 sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded), 536 enc_length, NO_SASL_INTERACTION, 537 &clientout, &clientoutlen); 538 if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { 539 vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); 540 return (XSASL_AUTH_FAIL); 541 } 542 543 /* 544 * Send a client response. 545 */ 546 if (clientoutlen > 0) { 547 if (msg_verbose) 548 msg_info("%s: uncoded client response %.*s", 549 myname, (int) clientoutlen, clientout); 550 enc_length = ENCODE64_LENGTH(clientoutlen) + 1; 551 VSTRING_RESET(client_reply); /* Fix 200512 */ 552 VSTRING_SPACE(client_reply, enc_length); 553 if ((sasl_status = sasl_encode64(clientout, clientoutlen, 554 STR(client_reply), 555 vstring_avail(client_reply), 556 &enc_length_out)) != SASL_OK) 557 msg_panic("%s: sasl_encode64 botch: %s", 558 myname, xsasl_cyrus_strerror(sasl_status)); 559 #if SASL_VERSION_MAJOR < 2 560 /* SASL version 1 doesn't free memory that it allocates. */ 561 free(clientout); 562 #endif 563 } else { 564 /* XXX Can't happen. */ 565 vstring_strcpy(client_reply, ""); 566 } 567 return (XSASL_AUTH_OK); 568 } 569 570 /* xsasl_cyrus_client_free - per-session cleanup */ 571 572 void xsasl_cyrus_client_free(XSASL_CLIENT *xp) 573 { 574 XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; 575 576 if (client->username) 577 myfree(client->username); 578 if (client->password) 579 myfree(client->password); 580 if (client->sasl_conn) 581 sasl_dispose(&client->sasl_conn); 582 myfree((void *) client->callbacks); 583 vstring_free(client->decoded); 584 myfree((void *) client); 585 } 586 587 #endif 588