1/*++ 2/* NAME 3/* xsasl_cyrus_server 3 4/* SUMMARY 5/* Cyrus SASL server-side plug-in 6/* SYNOPSIS 7/* #include <xsasl_cyrus_server.h> 8/* 9/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) 10/* const char *server_type; 11/* const char *path_info; 12/* DESCRIPTION 13/* This module implements the Cyrus SASL server-side authentication 14/* plug-in. 15/* 16/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and 17/* returns an implementation handle that can be used to generate 18/* SASL server instances. 19/* 20/* Arguments: 21/* .IP server_type 22/* The server type (cyrus). This argument is ignored, but it 23/* could be used when one implementation provides multiple 24/* variants. 25/* .IP path_info 26/* The base name of the SASL server configuration file (example: 27/* smtpd becomes /usr/lib/sasl2/smtpd.conf). 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_server(3). 35/* LICENSE 36/* .ad 37/* .fi 38/* The Secure Mailer license must be distributed with this software. 39/* AUTHOR(S) 40/* Initial implementation by: 41/* Till Franke 42/* SuSE Rhein/Main AG 43/* 65760 Eschborn, Germany 44/* 45/* Adopted by: 46/* Wietse Venema 47/* IBM T.J. Watson Research 48/* P.O. Box 704 49/* Yorktown Heights, NY 10598, USA 50/*--*/ 51 52/* System library. */ 53 54#include <sys_defs.h> 55#include <stdlib.h> 56#include <string.h> 57 58/* Utility library. */ 59 60#include <msg.h> 61#include <mymalloc.h> 62#include <name_mask.h> 63#include <stringops.h> 64 65/* Global library. */ 66 67#include <mail_params.h> 68 69/* Application-specific. */ 70 71#include <xsasl.h> 72#include <xsasl_cyrus.h> 73#include <xsasl_cyrus_common.h> 74 75#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) 76 77#include <sasl.h> 78#include <saslutil.h> 79 80/* 81 * Silly little macros. 82 */ 83#define STR(s) vstring_str(s) 84 85 /* 86 * Macros to handle API differences between SASLv1 and SASLv2. Specifics: 87 * 88 * The SASL_LOG_* constants were renamed in SASLv2. 89 * 90 * SASLv2's sasl_server_new takes two new parameters to specify local and 91 * remote IP addresses for auth mechs that use them. 92 * 93 * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr 94 * parameter. 95 * 96 * SASLv2's sasl_decode64 function takes an extra parameter for the length of 97 * the output buffer. 98 * 99 * The other major change is that SASLv2 now takes more responsibility for 100 * deallocating memory that it allocates internally. Thus, some of the 101 * function parameters are now 'const', to make sure we don't try to free 102 * them too. This is dealt with in the code later on. 103 */ 104 105#if SASL_VERSION_MAJOR < 2 106/* SASL version 1.x */ 107#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ 108 sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) 109#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ 110 sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) 111#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ 112 sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) 113#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 114 sasl_decode64(in, inlen, out, outlen) 115typedef char *MECHANISM_TYPE; 116typedef unsigned MECHANISM_COUNT_TYPE; 117typedef char *SERVEROUT_TYPE; 118typedef void *VOID_SERVEROUT_TYPE; 119 120#endif 121 122#if SASL_VERSION_MAJOR >= 2 123/* SASL version > 2.x */ 124#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ 125 sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) 126#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ 127 sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) 128#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ 129 sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) 130#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ 131 sasl_decode64(in, inlen, out, outmaxlen, outlen) 132typedef const char *MECHANISM_TYPE; 133typedef int MECHANISM_COUNT_TYPE; 134typedef const char *SERVEROUT_TYPE; 135typedef const void *VOID_SERVEROUT_TYPE; 136 137#endif 138 139 /* 140 * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER 141 * object. 142 */ 143typedef struct { 144 XSASL_SERVER xsasl; /* generic members, must be first */ 145 VSTREAM *stream; /* client-server connection */ 146 sasl_conn_t *sasl_conn; /* SASL context */ 147 VSTRING *decoded; /* decoded challenge or response */ 148 char *username; /* authenticated user */ 149 char *mechanism_list; /* applicable mechanisms */ 150} XSASL_CYRUS_SERVER; 151 152 /* 153 * Forward declarations. 154 */ 155static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); 156static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, 157 XSASL_SERVER_CREATE_ARGS *); 158static void xsasl_cyrus_server_free(XSASL_SERVER *); 159static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, 160 const char *, VSTRING *); 161static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); 162static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); 163static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); 164static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); 165 166 /* 167 * SASL callback interface structure. These call-backs have no per-session 168 * context. 169 */ 170#define NO_CALLBACK_CONTEXT 0 171 172static sasl_callback_t callbacks[] = { 173 {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, 174 {SASL_CB_LIST_END, 0, 0} 175}; 176 177/* xsasl_cyrus_server_init - create implementation handle */ 178 179XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, 180 const char *path_info) 181{ 182 const char *myname = "xsasl_cyrus_server_init"; 183 XSASL_SERVER_IMPL *xp; 184 int sasl_status; 185 186#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ 187 || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) 188 int sasl_major; 189 int sasl_minor; 190 int sasl_step; 191 192 /* 193 * DLL hell guard. 194 */ 195 sasl_version_info((const char **) 0, (const char **) 0, 196 &sasl_major, &sasl_minor, 197 &sasl_step, (int *) 0); 198 if (sasl_major != SASL_VERSION_MAJOR 199#if 0 200 || sasl_minor != SASL_VERSION_MINOR 201 || sasl_step != SASL_VERSION_STEP 202#endif 203 ) { 204 msg_warn("incorrect SASL library version. " 205 "Postfix was built with include files from version %d.%d.%d, " 206 "but the run-time library version is %d.%d.%d", 207 SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, 208 sasl_major, sasl_minor, sasl_step); 209 return (0); 210 } 211#endif 212 213 if (*var_cyrus_conf_path) { 214#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ 215 if (sasl_set_path(SASL_PATH_TYPE_CONFIG, 216 var_cyrus_conf_path) != SASL_OK) 217 msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", 218 var_cyrus_conf_path); 219#else 220 msg_warn("%s is not empty, but setting the Cyrus SASL configuration " 221 "path is not supported with SASL library version %d.%d.%d", 222 VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, 223 SASL_VERSION_MINOR, SASL_VERSION_STEP); 224#endif 225 } 226 227 /* 228 * Initialize the library: load SASL plug-in routines, etc. 229 */ 230 if (msg_verbose) 231 msg_info("%s: SASL config file is %s.conf", myname, path_info); 232 if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { 233 msg_warn("SASL per-process initialization failed: %s", 234 xsasl_cyrus_strerror(sasl_status)); 235 return (0); 236 } 237 238 /* 239 * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it 240 * with our own methods or data. 241 */ 242 xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); 243 xp->create = xsasl_cyrus_server_create; 244 xp->done = xsasl_cyrus_server_done; 245 return (xp); 246} 247 248/* xsasl_cyrus_server_done - dispose of implementation */ 249 250static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) 251{ 252 myfree((char *) impl); 253 sasl_done(); 254} 255 256/* xsasl_cyrus_server_create - create server instance */ 257 258static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, 259 XSASL_SERVER_CREATE_ARGS *args) 260{ 261 const char *myname = "xsasl_cyrus_server_create"; 262 char *server_address; 263 char *client_address; 264 sasl_conn_t *sasl_conn = 0; 265 XSASL_CYRUS_SERVER *server = 0; 266 int sasl_status; 267 268 if (msg_verbose) 269 msg_info("%s: SASL service=%s, realm=%s", 270 myname, args->service, args->user_realm ? 271 args->user_realm : "(null)"); 272 273 /* 274 * The optimizer will eliminate code duplication and/or dead code. 275 */ 276#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ 277 do { \ 278 if (server) { \ 279 xsasl_cyrus_server_free(&server->xsasl); \ 280 } else { \ 281 if (sasl_conn) \ 282 sasl_dispose(&sasl_conn); \ 283 } \ 284 return (x); \ 285 } while (0) 286 287 /* 288 * Set up a new server context. 289 */ 290#define NO_SECURITY_LAYERS (0) 291#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) 292#define NO_AUTH_REALM ((char *) 0) 293 294#if SASL_VERSION_MAJOR >= 2 && defined(USE_SASL_IP_AUTH) 295 296 /* 297 * Get IP addresses of local and remote endpoints for SASL. 298 */ 299#error "USE_SASL_IP_AUTH is not implemented" 300 301#else 302 303 /* 304 * Don't give any IP address information to SASL. SASLv1 doesn't use it, 305 * and in SASLv2 this will disable any mechanisms that do. 306 */ 307 server_address = 0; 308 client_address = 0; 309#endif 310 311 if ((sasl_status = 312 SASL_SERVER_NEW(args->service, var_myhostname, 313 args->user_realm ? args->user_realm : NO_AUTH_REALM, 314 server_address, client_address, 315 NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, 316 &sasl_conn)) != SASL_OK) { 317 msg_warn("SASL per-connection server initialization: %s", 318 xsasl_cyrus_strerror(sasl_status)); 319 XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); 320 } 321 322 /* 323 * Extend the XSASL_SERVER object with our own data. We use long-lived 324 * conversion buffers rather than local variables to avoid memory leaks 325 * in case of read/write timeout or I/O error. 326 */ 327 server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); 328 server->xsasl.free = xsasl_cyrus_server_free; 329 server->xsasl.first = xsasl_cyrus_server_first; 330 server->xsasl.next = xsasl_cyrus_server_next; 331 server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; 332 server->xsasl.get_username = xsasl_cyrus_server_get_username; 333 server->stream = args->stream; 334 server->sasl_conn = sasl_conn; 335 server->decoded = vstring_alloc(20); 336 server->username = 0; 337 server->mechanism_list = 0; 338 339 if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) 340 != XSASL_AUTH_OK) 341 XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); 342 343 return (&server->xsasl); 344} 345 346/* xsasl_cyrus_server_set_security - set security properties */ 347 348static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, 349 const char *sasl_opts_val) 350{ 351 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 352 sasl_security_properties_t sec_props; 353 int sasl_status; 354 355 /* 356 * Security options. Some information can be found in the sasl.h include 357 * file. 358 */ 359 memset(&sec_props, 0, sizeof(sec_props)); 360 sec_props.min_ssf = 0; 361 sec_props.max_ssf = 0; /* don't allow real SASL 362 * security layer */ 363 if (*sasl_opts_val == 0) { 364 sec_props.security_flags = 0; 365 } else { 366 sec_props.security_flags = 367 xsasl_cyrus_security_parse_opts(sasl_opts_val); 368 if (sec_props.security_flags == 0) { 369 msg_warn("bad per-session SASL security properties"); 370 return (XSASL_AUTH_FAIL); 371 } 372 } 373 sec_props.maxbufsize = 0; 374 sec_props.property_names = 0; 375 sec_props.property_values = 0; 376 377 if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, 378 &sec_props)) != SASL_OK) { 379 msg_warn("SASL per-connection security setup; %s", 380 xsasl_cyrus_strerror(sasl_status)); 381 return (XSASL_AUTH_FAIL); 382 } 383 return (XSASL_AUTH_OK); 384} 385 386/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ 387 388static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) 389{ 390 const char *myname = "xsasl_cyrus_server_get_mechanism_list"; 391 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 392 MECHANISM_TYPE mechanism_list; 393 MECHANISM_COUNT_TYPE mechanism_count; 394 int sasl_status; 395 396 /* 397 * Get the list of authentication mechanisms. 398 */ 399#define UNSUPPORTED_USER ((char *) 0) 400#define IGNORE_MECHANISM_LEN ((unsigned *) 0) 401 402 if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, 403 "", " ", "", 404 &mechanism_list, 405 IGNORE_MECHANISM_LEN, 406 &mechanism_count)) != SASL_OK) { 407 msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); 408 return (0); 409 } 410 if (mechanism_count <= 0) { 411 msg_warn("%s: no applicable SASL mechanisms", myname); 412 return (0); 413 } 414 server->mechanism_list = mystrdup(mechanism_list); 415#if SASL_VERSION_MAJOR < 2 416 /* SASL version 1 doesn't free memory that it allocates. */ 417 free(mechanism_list); 418#endif 419 return (server->mechanism_list); 420} 421 422/* xsasl_cyrus_server_free - destroy server instance */ 423 424static void xsasl_cyrus_server_free(XSASL_SERVER *xp) 425{ 426 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 427 428 sasl_dispose(&server->sasl_conn); 429 vstring_free(server->decoded); 430 if (server->username) 431 myfree(server->username); 432 if (server->mechanism_list) 433 myfree(server->mechanism_list); 434 myfree((char *) server); 435} 436 437/* xsasl_cyrus_server_auth_response - encode server first/next response */ 438 439static int xsasl_cyrus_server_auth_response(int sasl_status, 440 SERVEROUT_TYPE serverout, 441 unsigned serveroutlen, 442 VSTRING *reply) 443{ 444 const char *myname = "xsasl_cyrus_server_auth_response"; 445 unsigned enc_length; 446 unsigned enc_length_out; 447 448 /* 449 * Encode the server first/next non-error response; otherwise return the 450 * unencoded error text that corresponds to the SASL error status. 451 * 452 * Regarding the hairy expression below: output from sasl_encode64() comes 453 * in multiples of four bytes for each triple of input bytes, plus four 454 * bytes for any incomplete last triple, plus one byte for the null 455 * terminator. 456 */ 457 if (sasl_status == SASL_OK) { 458 vstring_strcpy(reply, ""); 459 return (XSASL_AUTH_DONE); 460 } else if (sasl_status == SASL_CONTINUE) { 461 if (msg_verbose) 462 msg_info("%s: uncoded server challenge: %.*s", 463 myname, (int) serveroutlen, serverout); 464 enc_length = ((serveroutlen + 2) / 3) * 4 + 1; 465 VSTRING_RESET(reply); /* Fix 200512 */ 466 VSTRING_SPACE(reply, enc_length); 467 if ((sasl_status = sasl_encode64(serverout, serveroutlen, 468 STR(reply), vstring_avail(reply), 469 &enc_length_out)) != SASL_OK) 470 msg_panic("%s: sasl_encode64 botch: %s", 471 myname, xsasl_cyrus_strerror(sasl_status)); 472 return (XSASL_AUTH_MORE); 473 } else { 474 if (sasl_status == SASL_NOUSER) /* privacy */ 475 sasl_status = SASL_BADAUTH; 476 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 477 return (XSASL_AUTH_FAIL); 478 } 479} 480 481/* xsasl_cyrus_server_first - per-session authentication */ 482 483int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, 484 const char *init_response, VSTRING *reply) 485{ 486 const char *myname = "xsasl_cyrus_server_first"; 487 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 488 char *dec_buffer; 489 unsigned dec_length; 490 unsigned reply_len; 491 unsigned serveroutlen; 492 int sasl_status; 493 SERVEROUT_TYPE serverout = 0; 494 int xsasl_status; 495 496#if SASL_VERSION_MAJOR < 2 497 const char *errstr = 0; 498 499#endif 500 501#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) 502 503 if (msg_verbose) 504 msg_info("%s: sasl_method %s%s%s", myname, sasl_method, 505 IFELSE(init_response, ", init_response ", ""), 506 IFELSE(init_response, init_response, "")); 507 508 /* 509 * SASL authentication protocol start-up. Process any initial client 510 * response that was sent along in the AUTH command. 511 */ 512 if (init_response) { 513 reply_len = strlen(init_response); 514 VSTRING_RESET(server->decoded); /* Fix 200512 */ 515 VSTRING_SPACE(server->decoded, reply_len); 516 if ((sasl_status = SASL_DECODE64(init_response, reply_len, 517 dec_buffer = STR(server->decoded), 518 vstring_avail(server->decoded), 519 &dec_length)) != SASL_OK) { 520 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 521 return (XSASL_AUTH_FORM); 522 } 523 if (msg_verbose) 524 msg_info("%s: decoded initial response %s", myname, dec_buffer); 525 } else { 526 dec_buffer = 0; 527 dec_length = 0; 528 } 529 sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, 530 dec_length, &serverout, 531 &serveroutlen, &errstr); 532 xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, 533 serveroutlen, reply); 534#if SASL_VERSION_MAJOR < 2 535 /* SASL version 1 doesn't free memory that it allocates. */ 536 free(serverout); 537#endif 538 return (xsasl_status); 539} 540 541/* xsasl_cyrus_server_next - continue authentication */ 542 543static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, 544 VSTRING *reply) 545{ 546 const char *myname = "xsasl_cyrus_server_next"; 547 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 548 unsigned dec_length; 549 unsigned request_len; 550 unsigned serveroutlen; 551 int sasl_status; 552 SERVEROUT_TYPE serverout = 0; 553 int xsasl_status; 554 555#if SASL_VERSION_MAJOR < 2 556 const char *errstr = 0; 557 558#endif 559 560 request_len = strlen(request); 561 VSTRING_RESET(server->decoded); /* Fix 200512 */ 562 VSTRING_SPACE(server->decoded, request_len); 563 if ((sasl_status = SASL_DECODE64(request, request_len, 564 STR(server->decoded), 565 vstring_avail(server->decoded), 566 &dec_length)) != SASL_OK) { 567 vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); 568 return (XSASL_AUTH_FORM); 569 } 570 if (msg_verbose) 571 msg_info("%s: decoded response: %.*s", 572 myname, (int) dec_length, STR(server->decoded)); 573 sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), 574 dec_length, &serverout, 575 &serveroutlen, &errstr); 576 xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, 577 serveroutlen, reply); 578#if SASL_VERSION_MAJOR < 2 579 /* SASL version 1 doesn't free memory that it allocates. */ 580 free(serverout); 581#endif 582 return (xsasl_status); 583} 584 585/* xsasl_cyrus_server_get_username - get authenticated username */ 586 587static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) 588{ 589 const char *myname = "xsasl_cyrus_server_get_username"; 590 XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; 591 VOID_SERVEROUT_TYPE serverout = 0; 592 int sasl_status; 593 594 /* 595 * XXX Do not free(serverout). 596 */ 597 sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); 598 if (sasl_status != SASL_OK || serverout == 0) { 599 msg_warn("%s: sasl_getprop SASL_USERNAME botch: %s", 600 myname, xsasl_cyrus_strerror(sasl_status)); 601 return (0); 602 } 603 if (server->username) 604 myfree(server->username); 605 server->username = mystrdup(serverout); 606 printable(server->username, '?'); 607 return (server->username); 608} 609 610#endif 611