1/*++ 2/* NAME 3/* smtpd_sasl_glue 3 4/* SUMMARY 5/* Postfix SMTP server, SASL support interface 6/* SYNOPSIS 7/* #include "smtpd_sasl_glue.h" 8/* 9/* void smtpd_sasl_state_init(state) 10/* SMTPD_STATE *state; 11/* 12/* void smtpd_sasl_initialize() 13/* 14/* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val) 15/* SMTPD_STATE *state; 16/* const char *sasl_opts_name; 17/* const char *sasl_opts_val; 18/* 19/* char *smtpd_sasl_authenticate(state, sasl_method, init_response) 20/* SMTPD_STATE *state; 21/* const char *sasl_method; 22/* const char *init_response; 23/* 24/* void smtpd_sasl_logout(state) 25/* SMTPD_STATE *state; 26/* 27/* void smtpd_sasl_login(state, sasl_username, sasl_method) 28/* SMTPD_STATE *state; 29/* const char *sasl_username; 30/* const char *sasl_method; 31/* 32/* void smtpd_sasl_deactivate(state) 33/* SMTPD_STATE *state; 34/* 35/* int smtpd_sasl_is_active(state) 36/* SMTPD_STATE *state; 37/* 38/* int smtpd_sasl_set_inactive(state) 39/* SMTPD_STATE *state; 40/* DESCRIPTION 41/* This module encapsulates most of the detail specific to SASL 42/* authentication. 43/* 44/* smtpd_sasl_state_init() performs minimal server state 45/* initialization to support external authentication (e.g., 46/* XCLIENT) without having to enable SASL in main.cf. This 47/* should always be called at process startup. 48/* 49/* smtpd_sasl_initialize() initializes the SASL library. This 50/* routine should be called once at process start-up. It may 51/* need access to the file system for run-time loading of 52/* plug-in modules. There is no corresponding cleanup routine. 53/* 54/* smtpd_sasl_activate() performs per-connection initialization. 55/* This routine should be called once at the start of every 56/* connection. The sasl_opts_name and sasl_opts_val parameters 57/* are the postfix configuration parameters setting the security 58/* policy of the SASL authentication. 59/* 60/* smtpd_sasl_authenticate() implements the authentication 61/* dialog. The result is zero in case of success, -1 in case 62/* of failure. smtpd_sasl_authenticate() updates the following 63/* state structure members: 64/* .IP sasl_method 65/* The authentication method that was successfully applied. 66/* This member is a null pointer in the absence of successful 67/* authentication. 68/* .IP sasl_username 69/* The username that was successfully authenticated. 70/* This member is a null pointer in the absence of successful 71/* authentication. 72/* .PP 73/* smtpd_sasl_login() records the result of successful external 74/* authentication, i.e. without invoking smtpd_sasl_authenticate(), 75/* but produces an otherwise equivalent result. 76/* 77/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate(). 78/* This routine exists for the sake of symmetry. 79/* 80/* smtpd_sasl_deactivate() performs per-connection cleanup. 81/* This routine should be called at the end of every connection. 82/* 83/* smtpd_sasl_is_active() is a predicate that returns true 84/* if the SMTP server session state is between smtpd_sasl_activate() 85/* and smtpd_sasl_deactivate(). 86/* 87/* smtpd_sasl_set_inactive() initializes the SMTP session 88/* state before the first smtpd_sasl_activate() call. 89/* 90/* Arguments: 91/* .IP state 92/* SMTP session context. 93/* .IP sasl_opts_name 94/* Security options parameter name. 95/* .IP sasl_opts_val 96/* Security options parameter value. 97/* .IP sasl_method 98/* A SASL mechanism name 99/* .IP init_reply 100/* An optional initial client response. 101/* DIAGNOSTICS 102/* All errors are fatal. 103/* LICENSE 104/* .ad 105/* .fi 106/* The Secure Mailer license must be distributed with this software. 107/* AUTHOR(S) 108/* Initial implementation by: 109/* Till Franke 110/* SuSE Rhein/Main AG 111/* 65760 Eschborn, Germany 112/* 113/* Adopted by: 114/* Wietse Venema 115/* IBM T.J. Watson Research 116/* P.O. Box 704 117/* Yorktown Heights, NY 10598, USA 118/*--*/ 119 120/* System library. */ 121 122#include <sys_defs.h> 123#include <stdlib.h> 124#include <string.h> 125#ifdef __APPLE_OS_X_SERVER__ 126#include <syslog.h> 127#include <stdio.h> 128#include <sys/stat.h> 129#include <sys/param.h> 130#include <get_hostname.h> 131 132#include "aod.h" 133#include "base64_code.h" 134#endif /* __APPLE_OS_X_SERVER__ */ 135 136/* Utility library. */ 137 138#include <msg.h> 139#include <mymalloc.h> 140#include <stringops.h> 141 142/* Global library. */ 143 144#include <mail_params.h> 145 146/* XSASL library. */ 147 148#include <xsasl.h> 149 150/* Application-specific. */ 151 152#include "smtpd.h" 153#include "smtpd_sasl_glue.h" 154#include "smtpd_chat.h" 155 156#ifdef __APPLE_OS_X_SERVER__ 157/* Apple Open Directory */ 158#include <OpenDirectory/OpenDirectory.h> 159#include <DirectoryService/DirServicesConst.h> 160 161/* Core Foundation (CF) */ 162#include <CoreFoundation/CFData.h> 163#include <CoreFoundation/CFString.h> 164 165/* kerberos */ 166#include <Kerberos/gssapi_krb5.h> 167 168/* mach */ 169#include <mach/boolean.h> 170 171/* system config */ 172#include <SystemConfiguration/SystemConfiguration.h> 173#endif /* __APPLE_OS_X_SERVER__ */ 174 175#ifdef USE_SASL_AUTH 176 177/* 178 * Silly little macros. 179 */ 180#define STR(s) vstring_str(s) 181 182 /* 183 * SASL server implementation handle. 184 */ 185static XSASL_SERVER_IMPL *smtpd_sasl_impl; 186 187#ifdef __APPLE_OS_X_SERVER__ 188 189/* Apple's Password Server */ 190static NAME_MASK smtpd_pw_server_mask[] = { 191 "none", PW_SERVER_NONE, 192 "login", PW_SERVER_LOGIN, 193 "plain", PW_SERVER_PLAIN, 194 "cram-md5", PW_SERVER_CRAM_MD5, 195 "digest-md5", PW_SERVER_DIGEST_MD5, 196 "gssapi", PW_SERVER_GSSAPI, 197 0, 198}; 199 200ODSessionRef od_session_ref; 201ODNodeRef od_node_ref; 202 203#define CFSafeRelease(obj) do { if ((obj) != NULL) CFRelease((obj)); obj = NULL; } while (0) 204 205#endif /* __APPLE_OS_X_SERVER__ */ 206 207/* smtpd_sasl_initialize - per-process initialization */ 208 209#ifdef __APPLE_OS_X_SERVER__ 210void smtpd_sasl_initialize( int in_use_pw_server ) 211{ 212 if ( in_use_pw_server ) 213 smtpd_pw_server_sasl_opts = name_mask( VAR_SMTPD_PW_SERVER_OPTS, smtpd_pw_server_mask, 214 var_smtpd_pw_server_opts ); 215#else /* __APPLE_OS_X_SERVER__ */ 216void smtpd_sasl_initialize(void) 217{ 218#endif /* __APPLE_OS_X_SERVER__ */ 219 220 /* 221 * Sanity check. 222 */ 223 if (smtpd_sasl_impl) 224 msg_panic("smtpd_sasl_initialize: repeated call"); 225 226 /* 227 * Initialize the SASL library. 228 */ 229 if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type, 230 var_smtpd_sasl_path)) == 0) 231 msg_fatal("SASL per-process initialization failed"); 232 233} 234 235/* smtpd_sasl_activate - per-connection initialization */ 236 237void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name, 238 const char *sasl_opts_val) 239{ 240 const char *mechanism_list; 241 XSASL_SERVER_CREATE_ARGS create_args; 242 int tls_flag; 243 244 /* 245 * Sanity check. 246 */ 247 if (smtpd_sasl_is_active(state)) 248 msg_panic("smtpd_sasl_activate: already active"); 249 250 /* 251 * Initialize SASL-specific state variables. Use long-lived storage for 252 * base 64 conversion results, rather than local variables, to avoid 253 * memory leaks when a read or write routine returns abnormally after 254 * timeout or I/O error. 255 */ 256 state->sasl_reply = vstring_alloc(20); 257 state->sasl_mechanism_list = 0; 258 259 /* 260 * Set up a new server context for this connection. 261 */ 262#define SMTPD_SASL_SERVICE "smtp" 263#ifdef USE_TLS 264 tls_flag = state->tls_context != 0; 265#else 266 tls_flag = 0; 267#endif 268#define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "") 269#define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0) 270 271 if ((state->sasl_server = 272 XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args, 273 stream = state->client, 274 server_addr = "", /* need smtpd_peer.c update */ 275 client_addr = ADDR_OR_EMPTY(state->addr, 276 CLIENT_ADDR_UNKNOWN), 277 service = SMTPD_SASL_SERVICE, 278 user_realm = REALM_OR_NULL(var_smtpd_sasl_realm), 279 security_options = sasl_opts_val, 280 tls_flag = tls_flag)) == 0) 281 msg_fatal("SASL per-connection initialization failed"); 282 283 /* 284 * Get the list of authentication mechanisms. 285 */ 286 if ((mechanism_list = 287 xsasl_server_get_mechanism_list(state->sasl_server)) == 0) 288 msg_fatal("no SASL authentication mechanisms"); 289 state->sasl_mechanism_list = mystrdup(mechanism_list); 290} 291 292/* smtpd_sasl_state_init - initialize state to allow extern authentication. */ 293 294void smtpd_sasl_state_init(SMTPD_STATE *state) 295{ 296 /* Initialization to support external authentication (e.g., XCLIENT). */ 297 state->sasl_username = 0; 298 state->sasl_method = 0; 299 state->sasl_sender = 0; 300} 301 302/* smtpd_sasl_deactivate - per-connection cleanup */ 303 304void smtpd_sasl_deactivate(SMTPD_STATE *state) 305{ 306 if (state->sasl_reply) { 307 vstring_free(state->sasl_reply); 308 state->sasl_reply = 0; 309 } 310 if (state->sasl_mechanism_list) { 311 myfree(state->sasl_mechanism_list); 312 state->sasl_mechanism_list = 0; 313 } 314 if (state->sasl_username) { 315 myfree(state->sasl_username); 316 state->sasl_username = 0; 317 } 318 if (state->sasl_method) { 319 myfree(state->sasl_method); 320 state->sasl_method = 0; 321 } 322 if (state->sasl_sender) { 323 myfree(state->sasl_sender); 324 state->sasl_sender = 0; 325 } 326 if (state->sasl_server) { 327 xsasl_server_free(state->sasl_server); 328 state->sasl_server = 0; 329 } 330} 331 332/* smtpd_sasl_authenticate - per-session authentication */ 333 334int smtpd_sasl_authenticate(SMTPD_STATE *state, 335 const char *sasl_method, 336 const char *init_response) 337{ 338 int status; 339 const char *sasl_username; 340 341 /* 342 * SASL authentication protocol start-up. Process any initial client 343 * response that was sent along in the AUTH command. 344 */ 345 for (status = xsasl_server_first(state->sasl_server, sasl_method, 346 init_response, state->sasl_reply); 347 status == XSASL_AUTH_MORE; 348 status = xsasl_server_next(state->sasl_server, STR(state->buffer), 349 state->sasl_reply)) { 350 351 /* 352 * Send a server challenge. 353 */ 354 smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply)); 355 356 /* 357 * Receive the client response. "*" means that the client gives up. 358 * XXX For now we ignore the fact that an excessively long response 359 * will be chopped into multiple reponses. To handle such responses, 360 * we need to change smtpd_chat_query() so that it returns an error 361 * indication. 362 */ 363 smtpd_chat_query(state); 364 if (strcmp(STR(state->buffer), "*") == 0) { 365 msg_warn("%s: SASL %s authentication aborted", 366 state->namaddr, sasl_method); 367 smtpd_chat_reply(state, "501 5.7.0 Authentication aborted"); 368 return (-1); 369 } 370 } 371 if (status != XSASL_AUTH_DONE) { 372 msg_warn("%s: SASL %s authentication failed: %s", 373 state->namaddr, sasl_method, 374 STR(state->sasl_reply)); 375 /* RFC 4954 Section 6. */ 376 smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s", 377 STR(state->sasl_reply)); 378 return (-1); 379 } 380 /* RFC 4954 Section 6. */ 381 smtpd_chat_reply(state, "235 2.7.0 Authentication successful"); 382 if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0) 383 msg_panic("cannot look up the authenticated SASL username"); 384 state->sasl_username = mystrdup(sasl_username); 385 printable(state->sasl_username, '?'); 386 state->sasl_method = mystrdup(sasl_method); 387 printable(state->sasl_method, '?'); 388 389 return (0); 390} 391 392/* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */ 393 394void smtpd_sasl_logout(SMTPD_STATE *state) 395{ 396 if (state->sasl_username) { 397 myfree(state->sasl_username); 398 state->sasl_username = 0; 399 } 400 if (state->sasl_method) { 401 myfree(state->sasl_method); 402 state->sasl_method = 0; 403 } 404} 405 406/* smtpd_sasl_login - set login information */ 407 408void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username, 409 const char *sasl_method) 410{ 411 if (state->sasl_username) 412 myfree(state->sasl_username); 413 state->sasl_username = mystrdup(sasl_username); 414 if (state->sasl_method) 415 myfree(state->sasl_method); 416 state->sasl_method = mystrdup(sasl_method); 417} 418 419#ifdef __APPLE_OS_X_SERVER__ 420/* ----------------------------------------------------------------- 421 * - Password Server auth methods 422 */ 423 424static char *auth_login( SMTPD_STATE *state, const char *in_method ); 425static char *auth_plain( SMTPD_STATE *state, const char *in_method, const char *in_resp ); 426static char *auth_cram_md5( SMTPD_STATE *state, const char *in_method ); 427static char *auth_digest_md5( SMTPD_STATE *state, const char *in_method ); 428 429/* ----------------------------------------------------------------- 430 * smtpd_pw_server_authenticate() 431 */ 432 433char *smtpd_pw_server_authenticate ( SMTPD_STATE *state, const char *in_method, const char *in_resp ) 434{ 435 char *myname = "smtpd_pw_server_authenticate"; 436 437 /*** Sanity check ***/ 438 if ( state->sasl_username || state->sasl_method ) 439 msg_panic( "%s: already authenticated", myname ); 440 441 if ( strcasecmp( in_method, "LOGIN" ) == 0 ) 442 return( auth_login( state, in_method ) ); 443 else if ( strcasecmp( in_method, "PLAIN" ) == 0 ) 444 return( auth_plain( state, in_method, in_resp ) ); 445 else if ( strcasecmp( in_method, "CRAM-MD5" ) == 0 ) 446 return( auth_cram_md5( state, in_method ) ); 447 else if ( strcasecmp( in_method, "DIGEST-MD5" ) == 0 ) 448 return( auth_digest_md5( state, in_method ) ); 449 450 msg_error( "authentication method: %s is not supported", in_method ); 451 return ( "504 Unsupported authentication method" ); 452} /* smtpd_pw_server_authenticate */ 453 454/* ------------------------------------------------------------------ 455 * print_cf_error() 456 */ 457 458static void print_cf_error ( CFErrorRef in_cf_err_ref, const char *in_tag ) 459{ 460 if ( !in_cf_err_ref ) 461 return; 462 463 CFStringRef cf_str_ref = CFErrorCopyFailureReason( in_cf_err_ref ); 464 if ( cf_str_ref ) { 465 char c_str[1025]; 466 CFStringGetCString( cf_str_ref, c_str, 1024, kCFStringEncodingUTF8 ); 467 468 msg_error( "%s: error: %s", in_tag, c_str ); 469 return; 470 } 471} /* print_cf_error */ 472 473/* ------------------------------------------------------------------ 474 * od_open() 475 */ 476 477bool od_open ( void ) 478{ 479 CFErrorRef cf_err_ref = NULL; 480 od_session_ref = ODSessionCreate( kCFAllocatorDefault, NULL, &cf_err_ref ); 481 if ( !od_session_ref ) { 482 print_cf_error( cf_err_ref, "initialize Open Directory" ); 483 msg_error( "init Open Directory: unable to create OD session" ); 484 CFSafeRelease(cf_err_ref); 485 return( FALSE ); 486 } 487 488 od_node_ref = ODNodeCreateWithNodeType( kCFAllocatorDefault, od_session_ref, kODNodeTypeAuthentication, &cf_err_ref ); 489 if ( !od_session_ref ) { 490 print_cf_error( cf_err_ref, "init Open Directory" ); 491 msg_error( "init Open Directory: unable to create OD node reference" ); 492 CFSafeRelease(cf_err_ref); 493 CFSafeRelease( od_session_ref ); 494 return( FALSE ); 495 } 496 497 CFRetain( od_session_ref ); 498 CFRetain( od_node_ref ); 499 500 return( TRUE ); 501} /* od_open */ 502 503/* ------------------------------------------------------------------ 504 * od_get_user_record() 505 */ 506 507static ODRecordRef od_get_user_record ( const char *in_user ) 508{ 509 CFStringRef cf_str_user = CFStringCreateWithCString( NULL, in_user, kCFStringEncodingUTF8 ); 510 if ( cf_str_user == NULL ) { 511 msg_error( "user lookup: memory allocation error" ); 512 return( NULL ); 513 } 514 515 CFErrorRef cf_err_ref = NULL; 516 CFTypeRef cf_type_val[] = { CFSTR(kDSAttributesStandardAll) }; 517 CFArrayRef cf_arry_attr = CFArrayCreate( NULL, cf_type_val, 1, &kCFTypeArrayCallBacks ); 518 ODRecordRef od_rec_ref = ODNodeCopyRecord( od_node_ref, CFSTR(kDSStdRecordTypeUsers), cf_str_user, cf_arry_attr, &cf_err_ref ); 519 if ( !od_rec_ref ) { 520 print_cf_error( cf_err_ref, "get user record" ); 521 msg_error( "get user record: unable to open user record for user=%s", in_user ); 522 } 523 524 CFSafeRelease( cf_str_user ); 525 CFSafeRelease( cf_arry_attr ); 526 527 return( od_rec_ref ); 528} /* od_get_user_record */ 529 530/* ------------------------------------------------------------------ 531 * validate_digest() 532 */ 533 534static int validate_digest ( const char *in_digest ) 535{ 536 const char *p = in_digest; 537 538 if ( !in_digest || !strlen(in_digest) ) { 539 msg_error( "null or zero length digest detected" ); 540 return 0; 541 } 542 543 for (; *p != '\0'; p++) { 544 if (isxdigit(*p)) 545 continue; 546 else { 547 msg_error( "invalid character (%c) detected in digest: %s", *p, in_digest ); 548 return 0; 549 } 550 } 551 return 1; 552} 553 554/* ------------------------------------------------------------------ 555 * get_ad_realm() 556 */ 557const char *get_ad_realm ( void ) 558{ 559 VSTRING *out_realm; 560 out_realm = vstring_alloc(10); 561 562 SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("postfix.digest.auth"), NULL, NULL); 563 if ( store ) { 564 CFDictionaryRef dict = SCDynamicStoreCopyValue(store, CFSTR("com.apple.opendirectoryd.ActiveDirectory")); 565 if (dict) { 566 CFStringRef domain = CFDictionaryGetValue(dict, CFSTR("DomainNameFlat")); 567 if (domain) { 568 const char *ad_realm = CFStringGetCStringPtr(domain, kCFStringEncodingUTF8); 569 if (ad_realm) { 570 msg_info("ad realm: %s", ad_realm); 571 vstring_strcpy(out_realm, ad_realm); 572 } 573 } 574 CFRelease(dict); 575 } 576 CFRelease(store); 577 } 578 return( VSTRING_LEN(out_realm) ? STR(out_realm) : NULL ); 579} 580 581/* ------------------------------------------------------------------ 582 * get_random_chars() 583 */ 584 585static void get_random_chars ( char *out_buf, int in_len ) 586{ 587 memset( out_buf, 0, in_len ); 588 589 /* try /dev/urandom first */ 590 int file = open( "/dev/urandom", O_RDONLY, 0 ); 591 if ( file == -1 ) { 592 msg_error( "open /dev/urandom O_RDONLY failed" ); 593 594 /* next try /dev/random */ 595 file = open( "/dev/random", O_RDONLY, 0 ); 596 } 597 598 if ( file == -1 ) { 599 msg_error( "open /dev/random O_RDONLY failed" ); 600 601 struct timeval tv; 602 struct timezone tz; 603 gettimeofday( &tv, &tz ); 604 605 unsigned long long microseconds = (unsigned long long)tv.tv_sec; 606 microseconds *= 1000000ULL; 607 microseconds += (unsigned long long)tv.tv_usec; 608 609 snprintf( out_buf, in_len, "%llu", microseconds ); 610 } else { 611 /* make sure the chars are printable */ 612 int count = 0; 613 while ( count < (in_len - 1) ) { 614 read( file, &out_buf[ count ], 1 ); 615 if ( isalnum( out_buf[ count ] ) ) 616 count++; 617 } 618 close( file ); 619 } 620} /* get_random_chars */ 621 622/* ------------------------------------------------------------------ 623 * validate_pw() 624 */ 625 626static int validate_pw ( const char *in_user, const char *in_passwd ) 627{ 628 /* sanity check */ 629 if ( !in_user || !in_passwd ) { 630 msg_error( "verify password: invalid arguments" ); 631 return( eAOD_param_error ); 632 } 633 634 /* open OD */ 635 if ( !od_open() ) { 636 msg_error( "verify password: failed to initialize open directory" ); 637 return( eAOD_open_OD_failed ); 638 } 639 640 /* do user lookup */ 641 ODRecordRef od_rec_ref = od_get_user_record( in_user ); 642 if ( !od_rec_ref ) { 643 msg_error( "verify password: unable to lookup user record for: user=%s", in_user ); 644 return( eAOD_unknown_user ); 645 } 646 647 CFStringRef cf_str_pwd = CFStringCreateWithCString( NULL, in_passwd, kCFStringEncodingUTF8 ); 648 if ( !cf_str_pwd ) { 649 CFSafeRelease( od_rec_ref ); 650 msg_error( "verify password: memory allocation error" ); 651 return( eAOD_system_error ); 652 } 653 654 int out_result = eAOD_passwd_mismatch; 655 CFErrorRef cf_err_ref = NULL; 656 if ( ODRecordVerifyPassword( od_rec_ref, cf_str_pwd, &cf_err_ref ) ) 657 out_result = eAOD_no_error; 658 else { 659 print_cf_error( cf_err_ref, "verify password" ); 660 msg_error( "verify password: authentication failed: user=%s", in_user ); 661 } 662 663 /* do some cleanup */ 664 CFSafeRelease( cf_err_ref ); 665 CFSafeRelease( od_rec_ref ); 666 CFSafeRelease( cf_str_pwd ); 667 668 return( out_result ); 669} /* validate_pw */ 670 671/* ------------------------------------------------------------------ 672 * validate_response() 673 */ 674 675int validate_response ( const char *in_user, 676 const char *in_chal, 677 const char *in_resp, 678 const char *in_auth_type, 679 VSTRING *vs_out ) 680{ 681 if ( !in_user || !in_chal || !in_resp || !in_auth_type ) { 682 msg_error( "invalid argument passed to validate response. user=%s (method=%s)", in_user, in_auth_type ); 683 return( eAOD_param_error ); 684 } 685 686 if ( !od_open() ) 687 return( eAOD_open_OD_failed ); 688 689 ODRecordRef od_rec_ref = od_get_user_record( in_user ); 690 if ( !od_rec_ref ) { 691 msg_error( "validate response: unable to lookup user record for: %s", in_user ); 692 return( eAOD_system_error ); 693 } 694 695 /* Stuff auth buffer with, user name, challenge and response */ 696 CFMutableArrayRef cf_arry_buf = CFArrayCreateMutable( NULL, 4, &kCFTypeArrayCallBacks ); 697 698 /* user */ 699 CFStringRef cf_str_user = CFStringCreateWithCString( NULL, in_user, kCFStringEncodingUTF8 ); 700 CFArrayInsertValueAtIndex( cf_arry_buf, 0, cf_str_user ); 701 CFSafeRelease( cf_str_user ); 702 703 /* challenge */ 704 CFStringRef cf_str_chal = CFStringCreateWithCString( NULL, in_chal, kCFStringEncodingUTF8 ); 705 CFArrayInsertValueAtIndex( cf_arry_buf, 1, cf_str_chal ); 706 CFSafeRelease( cf_str_chal ); 707 708 /* response */ 709 CFStringRef cf_str_resp = CFStringCreateWithCString( NULL, in_resp, kCFStringEncodingUTF8 ); 710 CFArrayInsertValueAtIndex( cf_arry_buf, 2, cf_str_resp ); 711 CFSafeRelease( cf_str_resp ); 712 713 ODAuthenticationType od_auth_type = kODAuthenticationTypeCRAM_MD5; 714 if ( strcmp( in_auth_type, "DIGEST-MD5" ) == 0 ) { 715 od_auth_type = kODAuthenticationTypeDIGEST_MD5; 716 CFStringRef cf_str_uri = CFStringCreateWithCString( NULL, "AUTHENTICATE", kCFStringEncodingUTF8 ); 717 CFArrayInsertValueAtIndex( cf_arry_buf, 3, cf_str_uri ); 718 CFSafeRelease( cf_str_uri ); 719 } 720 721 /* verify password */ 722 CFErrorRef cf_err_ref = NULL; 723 CFArrayRef cf_arry_resp = NULL; 724 ODContextRef od_context_ref = NULL; 725 bool auth_result = ODRecordVerifyPasswordExtended( od_rec_ref, od_auth_type, cf_arry_buf, 726 &cf_arry_resp, &od_context_ref, &cf_err_ref ); 727 CFSafeRelease( od_rec_ref ); 728 CFSafeRelease( cf_arry_buf ); 729 730 if ( !auth_result ) { 731 print_cf_error( cf_err_ref, "validate response" ); 732 msg_error( "validate response: authentication failed for user=%s (method=%s)", in_user, in_auth_type ); 733 } 734 735 /* if digest-md5 auth, get server response */ 736 if ( auth_result && strcmp( in_auth_type, "DIGEST-MD5" ) == 0 ) { 737 if ( !cf_arry_resp ) 738 msg_error("DIGEST-MD5 authentication error: missing server response" ); 739 else { 740 CFDataRef cf_data = CFArrayGetValueAtIndex(cf_arry_resp, 0); 741 if ( !cf_data || !CFDataGetLength(cf_data) ) 742 msg_error("DIGEST-MD5 authentication error: missing server response" ); 743 else { 744 const char *data_str = (const char *)CFDataGetBytePtr(cf_data); 745 vstring_strcpy( vs_out, data_str ); 746 } 747 } 748 } 749 750 /* do some clean up */ 751 CFSafeRelease( cf_arry_resp ); 752 CFSafeRelease( cf_err_ref ); 753 754 /* success */ 755 if ( auth_result ) 756 return( eAOD_no_error ); 757 758 return( eAOD_passwd_mismatch ); 759} /* validate_response */ 760 761/* ------------------------------------------------------------------ 762 * auth_login() 763 */ 764 765static char * auth_login ( SMTPD_STATE *state, const char *in_method ) 766{ 767 /* is LOGIN auth enabled */ 768 if ( !(smtpd_pw_server_sasl_opts & PW_SERVER_LOGIN) ) { 769 msg_error( "authentication method: LOGIN is not enabled" ); 770 return( "504 Authentication method not enabled" ); 771 } 772 773 /* encode the user name prompt and send it */ 774 static VSTRING *vs_base64; 775 vs_base64 = vstring_alloc(10); 776 base64_encode( vs_base64, "Username:", 9 ); 777 smtpd_chat_reply( state, "334 %s", STR(vs_base64) ); 778 779 /* get the user name and decode it */ 780 smtpd_chat_query( state ); 781 782 /* has the client given up */ 783 if ( strcmp(vstring_str( state->buffer ), "*") == 0 ) { 784 msg_error( "authentication aborted by client" ); 785 return ( "501 Authentication aborted" ); 786 } 787 788 /* decode user name */ 789 static VSTRING *vs_user; 790 vs_user = vstring_alloc(10); 791 if ( base64_decode( vs_user, STR(state->buffer), VSTRING_LEN(state->buffer) ) == 0 ) { 792 msg_error( "malformed response to: AUTH LOGIN" ); 793 return( "501 Authentication failed: malformed initial response" ); 794 } 795 796 /* encode the password prompt and send it */ 797 base64_encode( vs_base64, "Password:", 9 ); 798 smtpd_chat_reply( state, "334 %s", STR(vs_base64) ); 799 800 /* get the password */ 801 smtpd_chat_query( state ); 802 803 /* has the client given up */ 804 if ( strcmp(vstring_str( state->buffer ), "*") == 0 ) { 805 msg_error( "authentication aborted by client" ); 806 return ( "501 Authentication aborted" ); 807 } 808 809 /* decode the password */ 810 static VSTRING *vs_pwd; 811 vs_pwd = vstring_alloc(10); 812 if ( base64_decode( vs_pwd, STR(state->buffer), VSTRING_LEN(state->buffer) ) == 0 ) { 813 msg_error( "malformed response to: AUTH LOGIN" ); 814 return ( "501 Authentication failed: malformed response" ); 815 } 816 817 /* do the auth */ 818 if ( validate_pw( STR(vs_user), STR(vs_pwd) ) == eAOD_no_error ) { 819 state->sasl_username = mystrdup( STR(vs_user) ); 820 state->sasl_method = mystrdup( in_method ); 821 822 /* auth succeeded */ 823 msg_info( "verify password: AUTH LOGIN: authentication succeeded for user=%s", STR(vs_user) ); 824 send_server_event(eAuthSuccess, state->name, state->addr); 825 return( NULL ); 826 } else { 827 send_server_event(eAuthFailure, state->name, state->addr); 828 msg_error( "authentication failed" ); 829 return ( "535 Error: authentication failed" ); 830 } 831} /* auth_login */ 832 833/* ------------------------------------------------------------------ 834 * auth_plain() 835 */ 836 837static char *auth_plain ( SMTPD_STATE *state, const char *in_method, const char *in_resp ) 838{ 839 static VSTRING *vs_base64; 840 vs_base64 = vstring_alloc(10); 841 842 /* is PLAIN auth enabled */ 843 if ( !(smtpd_pw_server_sasl_opts & PW_SERVER_PLAIN) ) { 844 msg_error( "authentication method: PLAIN is not enabled" ); 845 return ( "504 Authentication method not enabled" ); 846 } 847 848 /* if no initial response, do the dance */ 849 if ( in_resp == NULL ) { 850 /* send 334 tag & read response */ 851 smtpd_chat_reply( state, "334" ); 852 smtpd_chat_query( state ); 853 854 /* decode response from server */ 855 if ( base64_decode( vs_base64, STR(state->buffer), VSTRING_LEN(state->buffer) ) == 0 ) { 856 msg_error( "Malformed response to: AUTH PLAIN" ); 857 return ( "501 Authentication failed: AUTH PLAIN: malformed initial response" ); 858 } 859 } else { 860 /* decode response from server */ 861 if ( base64_decode( vs_base64, in_resp, strlen( in_resp ) ) == 0 ) { 862 msg_error( "Malformed response to: AUTH PLAIN" ); 863 return ( "501 Authentication failed: AUTH PLAIN: malformed initial response" ); 864 } 865 } 866 867 /* client response */ 868 char *ptr = vstring_str(vs_base64); 869 870 /* get the authorization identity or skip if empty */ 871 char *authz_id = NULL; 872 if (*ptr == '\0') 873 ptr++; 874 else { 875 authz_id = ptr; 876 ptr = ptr + (strlen(authz_id) + 1); 877 } 878 879 /* check for bad client response */ 880 if ((ptr - vstring_str(vs_base64)) >= VSTRING_LEN(vs_base64)) { 881 msg_error( "Malformed response to: AUTH PLAIN" ); 882 return( "501 Authentication failed: AUTH PLAIN: malformed initial response" ); 883 } 884 885 /* pointer to user-id */ 886 char *user_id = ptr; 887 ptr = ptr + (strlen(user_id) + 1); 888 889 /* check for bad client response */ 890 if (((ptr - vstring_str(vs_base64)) >= VSTRING_LEN(vs_base64)) || 891 !strlen(user_id)) { 892 msg_error( "Malformed response to: AUTH PLAIN" ); 893 return( "501 Authentication failed: AUTH PLAIN: malformed initial response" ); 894 } 895 896 /* pointer to password */ 897 char *password = ptr; 898 899 /* check for bad client response */ 900 if (!strlen(password)) { 901 msg_error( "Malformed response to: AUTH PLAIN" ); 902 return( "501 Authentication failed: AUTH PLAIN: malformed initial response" ); 903 } 904 905 /* do the auth */ 906 if ( validate_pw(user_id, password) == eAOD_no_error ) { 907 state->sasl_username = mystrdup(user_id); 908 state->sasl_method = mystrdup(in_method); 909 910 /* auth succeeded */ 911 msg_info( "verify password: AUTH PLAIN: authentication succeeded for user=%s", user_id ); 912 send_server_event(eAuthSuccess, state->name, state->addr); 913 return( NULL ); 914 } 915 916 send_server_event(eAuthFailure, state->name, state->addr); 917 return ( "535 Error: authentication failed" ); 918} /* auth_plain */ 919 920/* ------------------------------------------------------------------ 921 * auth_cram_md5() 922 */ 923 924static char *auth_cram_md5 ( SMTPD_STATE *state, const char *in_method ) 925{ 926 /* is CRAM-MD5 auth enabled */ 927 if ( !(smtpd_pw_server_sasl_opts & PW_SERVER_CRAM_MD5) ) { 928 msg_error( "authentication method: CRAM-MD5 is not enabled" ); 929 return ( "504 Authentication method not enabled" ); 930 } 931 932 /* challenge host name */ 933 char host_name[ MAXHOSTNAMELEN + 1 ]; 934 gethostname( host_name, sizeof( host_name ) ); 935 936 /* get random data string */ 937 char rand_buf[ 17 ]; 938 get_random_chars( rand_buf, 17 ); 939 940 /* now make the challenge string */ 941 static VSTRING *vs_chal; 942 if (!vs_chal) vs_chal = vstring_alloc(10); 943 vstring_sprintf( vs_chal, "<%lu.-%s.-%lu-@-%s>", (unsigned long) getpid(), rand_buf, time(0), host_name ); 944 945 /* encode the challenge and send it */ 946 static VSTRING *vs_base64; 947 vs_base64 = vstring_alloc(10); 948 base64_encode( vs_base64, STR(vs_chal), VSTRING_LEN(vs_chal) ); 949 smtpd_chat_reply( state, "334 %s", STR(vs_base64) ); 950 951 /* get the client response */ 952 smtpd_chat_query( state ); 953 954 /* check if client cancelled */ 955 if ( strcmp( vstring_str( state->buffer ), "*" ) == 0 ) 956 return( "501 Authentication aborted" ); 957 958 /* decode the response */ 959 if ( base64_decode( vs_base64, STR(state->buffer), VSTRING_LEN(state->buffer) ) == 0 ) { 960 msg_error( "malformed response to: AUTH CRAM-MD5" ); 961 return( "501 Authentication failed: malformed initial response" ); 962 } 963 964 /* pointer to digest */ 965 /* get the user name */ 966 static VSTRING *vs_user; 967 vs_user = vstring_alloc(10); 968 char *resp_ptr = STR(vs_base64); 969 char *digest = strrchr(resp_ptr, ' '); 970 if (digest) { 971 vs_user = vstring_strncpy( vs_user, resp_ptr, (digest - resp_ptr) ); 972 digest++; 973 } else { 974 msg_error( "malformed response to: AUTH CRAM-MD5: missing digest" ); 975 return( "501 Authentication failed: malformed initial response" ); 976 } 977 978 /* check for valid digest */ 979 if (!validate_digest(digest)) { 980 msg_error( "malformed response to: AUTH CRAM-MD5: invalid digest" ); 981 return( "501 Authentication failed: malformed initial response" ); 982 } 983 984 /* validate the response */ 985 if ( validate_response( STR(vs_user), STR(vs_chal), digest, "CRAM-MD5", NULL ) == eAOD_no_error ) { 986 state->sasl_username = mystrdup( STR(vs_user) ); 987 state->sasl_method = mystrdup( in_method ); 988 989 /* auth succeeded */ 990 send_server_event(eAuthSuccess, state->name, state->addr); 991 return( NULL ); 992 } 993 994 send_server_event(eAuthFailure, state->name, state->addr); 995 return ( "535 Error: authentication failed" ); 996} /* auth_cram_md5 */ 997 998/* ------------------------------------------------------------------ 999 * auth_digest_md5() 1000 */ 1001 1002static char *auth_digest_md5 ( SMTPD_STATE *state, const char *in_method ) 1003{ 1004 /* is DIGEST-MD5 auth enabled */ 1005 if ( !(smtpd_pw_server_sasl_opts & PW_SERVER_DIGEST_MD5) ) { 1006 msg_error( "authentication method: DIGEST-MD5 is not enabled" ); 1007 return ( "504 Authentication method not enabled" ); 1008 } 1009 1010 /* generate challenge */ 1011 /* realm */ 1012 const char *realm = get_ad_realm(); 1013 if ( !realm || !strlen(realm) ) { 1014 realm = REALM_OR_NULL(var_smtpd_sasl_realm); 1015 if ( !realm || !strlen(realm) ) { 1016 realm = get_hostname(); 1017 } 1018 } 1019 1020 /* get random data string */ 1021 char nonce[ 32 ]; 1022 get_random_chars( nonce, 32 ); 1023 1024 /* challenge: realm="host.name.com",nonce="sDl/UquB615peBwoR6iF6A==",qop="auth",algorithm=md5-sess,charset=utf-8 */ 1025 static VSTRING *vs_chal; 1026 vs_chal = vstring_alloc(10); 1027 vstring_sprintf( vs_chal, "realm=\"%s\",nonce=\"%s\",qop=\"auth\",algorithm=md5-sess,charset=utf-8", realm, nonce ); 1028 if (msg_verbose) 1029 msg_info( "digest-md5 challenge: %s", STR(vs_chal) ); 1030 1031 /* encode the challenge and send it */ 1032 static VSTRING *vs_base64; 1033 vs_base64 = vstring_alloc(10); 1034 base64_encode( vs_base64, STR(vs_chal), VSTRING_LEN(vs_chal) ); 1035 smtpd_chat_reply( state, "334 %s", STR(vs_base64) ); 1036 1037 /* get the client response */ 1038 smtpd_chat_query( state ); 1039 1040 /* check if client cancelled */ 1041 if ( strcmp( vstring_str( state->buffer ), "*" ) == 0 ) 1042 return( "501 Authentication aborted" ); 1043 1044 if (msg_verbose) 1045 msg_info( "digest-md5 challenge: %s", STR(vs_chal) ); 1046 1047 /* decode the response */ 1048 if ( base64_decode( vs_base64, STR(state->buffer), VSTRING_LEN(state->buffer) ) == 0 ) { 1049 msg_info( "digest-md5 response: %s", STR(vs_base64) ); 1050 msg_error( "malformed response to: AUTH DIGEST-MD5" ); 1051 return( "501 Authentication failed: malformed initial response" ); 1052 } 1053 1054 if (msg_verbose) 1055 msg_info( "digest-md5 response: %s", STR(vs_base64) ); 1056 1057 /* fix response: it must include algorithm=md5-sess for OD to validate */ 1058 char *resp_ptr = STR(vs_base64); 1059 char *p = strstr(resp_ptr, "algorithm=md5-sess"); 1060 if ( !p ) 1061 vstring_strcat(vs_base64, ",algorithm=md5-sess" ); 1062 1063 /* get username from response */ 1064 static VSTRING *vs_user; 1065 vs_user = vstring_alloc(10); 1066 resp_ptr = STR(vs_base64); 1067 p = strstr(resp_ptr, "username=\""); 1068 if (p && (strlen(p) > 11)) { 1069 char *n = p + 10; 1070 char *r = strchr(n, '"'); 1071 if (r) 1072 vstring_strncpy( vs_user, n, (r - n) ); 1073 else { 1074 msg_info( "digest-md5 response: %s", STR(vs_base64) ); 1075 msg_error( "malformed response to: AUTH DIGEST-MD5: missing digest" ); 1076 return( "501 Authentication failed: malformed initial response" ); 1077 } 1078 } else { 1079 msg_info( "digest-md5 response: %s", STR(vs_base64) ); 1080 msg_error( "malformed response to: AUTH DIGEST-MD5: missing digest" ); 1081 return( "501 Authentication failed: malformed initial response" ); 1082 } 1083 1084 /* validate the response */ 1085 VSTRING *vs_resp = 0; 1086 vs_resp = vstring_alloc(10); 1087 if ( !validate_response( STR(vs_user), STR(vs_chal), STR(vs_base64), "DIGEST-MD5", vs_resp) ) { 1088 base64_encode( vs_base64, STR(vs_resp), VSTRING_LEN(vs_resp) ); 1089 smtpd_chat_reply( state, "334 %s", STR(vs_base64) ); 1090 1091 /* get the client response */ 1092 smtpd_chat_query( state ); 1093 1094 /* has the client given up */ 1095 if ( strcmp(vstring_str( state->buffer ), "*") == 0 ) { 1096 msg_error( "authentication aborted by client" ); 1097 return ( "501 Authentication aborted" ); 1098 } 1099 1100 /* save auth method and user name */ 1101 state->sasl_username = mystrdup( STR(vs_user) ); 1102 state->sasl_method = mystrdup( in_method ); 1103 1104 /* auth succeeded */ 1105 send_server_event(eAuthSuccess, state->name, state->addr); 1106 return( NULL ); 1107 } 1108 1109 send_server_event(eAuthFailure, state->name, state->addr); 1110 return ( "535 Error: authentication failed" ); 1111} /* auth_digest_md5 */ 1112 1113#endif /* __APPLE_OS_X_SERVER__ */ 1114#endif 1115