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