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