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