1/*
2 * Copyright (c) 2004-2011 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "sys_defs.h"
25#include "aod.h"
26#include "msg.h"
27#include "mail_params.h"
28#include "sacl_cache_clnt.h"
29
30#include <string.h>
31#include <stdbool.h>
32#include <stdlib.h>
33#include <syslog.h>
34#include <stdio.h>
35#include <fcntl.h>
36#include <sys/stat.h>
37#include <sys/types.h>
38#include <sys/sysctl.h>
39#include <mach/mig_errors.h>
40
41#include <dtrace-postfix.h>
42
43#include <membership.h>
44#include <membershipPriv.h>
45
46#include <CoreDaemon/CoreDaemon.h>
47
48#include <CoreFoundation/CFData.h>
49#include <CoreFoundation/CFString.h>
50#include <CoreFoundation/CFNumber.h>
51#include <CoreFoundation/CFPropertyList.h>
52
53#include <OpenDirectory/OpenDirectory.h>
54#include <OpenDirectory/OpenDirectoryPriv.h>
55#include <DirectoryService/DirServicesConst.h>
56
57/* -----------------------------------------------------------------
58	Prototypes
59   ----------------------------------------------------------------- */
60
61static void	print_cf_error			( CFErrorRef in_cf_err_ref, const char *in_user_name, const char *in_default_str );
62static int	get_user_attributes		( ODNodeRef in_node_ref, const char *in_user_name, struct od_user_opts *in_out_opts );
63void		get_acct_state			( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict, struct od_user_opts *in_out_opts );
64static void	get_auto_forward_addr	( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict, struct od_user_opts *in_out_opts );
65static void	get_alt_loc				( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict );
66static void	get_mail_quota			( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict );
67static bool get_attributes_local	( struct od_user_opts *in_out_opts, const char *in_user_guid );
68static void set_attributes_local	( CFMutableDictionaryRef in_user_dict, const char *in_user_guid );
69
70static CFStringRef get_attr_from_record ( ODRecordRef in_rec_ref, CFStringRef in_attr );
71static CFMutableDictionaryRef get_mail_attribute_values	( const char *in_mail_attribute, struct od_user_opts *in_out_opts );
72
73char *user_settings_path = NULL;
74
75/* Begin DS SPI Glue */
76#include <kvbuf.h>
77#include <opendirectory/DSlibinfoMIG.h>
78#include <opendirectory/DSmemberdMIG_types.h>
79#include <DirectoryService/DirectoryService.h>
80
81extern mach_port_t _ds_port;
82extern int _ds_running();
83
84int g_bad_recip_cntr	= 0;
85char g_client_addr[16] = "";
86XSEventPortRef	gEventPort = NULL;
87
88/* send server events
89 *	event code 1: bad recipient, possible directory harvesting attack
90 *	event code 2: failed authentication, possible SMTP relay password attach
91 */
92
93void send_server_event ( const eEventCode in_event_code, const char *in_name, const char *in_addr )
94{
95	CFTypeRef keys[2];
96	CFTypeRef values[2];
97	CFStringRef cfstr_addr = NULL;
98	CFStringRef cfstr_event = NULL;
99
100	if ( !strlen(g_client_addr) || (strcmp(g_client_addr, in_addr) != 0) ) {
101		strlcpy(g_client_addr, in_addr, sizeof g_client_addr);
102		g_bad_recip_cntr = 0;
103	}
104
105	if ( g_bad_recip_cntr++ < 4 )
106		return;
107	else
108		sleep( g_bad_recip_cntr >= 10 ? 10 : g_bad_recip_cntr );
109
110	/* create a port to the event server */
111	if ( gEventPort == NULL )
112		gEventPort = XSEventPortCreate(nil);
113
114	keys[0] = CFSTR("eventType");
115	keys[1] = CFSTR("host_address");
116
117	/* set event code string */
118	switch ( in_event_code ) {
119		case eBadRecipient:
120			cfstr_event = CFStringCreateWithCString(NULL, "smtp.receive.badrecipient", kCFStringEncodingMacRoman);
121			break;
122		case eAuthFailure:
123			cfstr_event = CFStringCreateWithCString(NULL, "auth.failure", kCFStringEncodingMacRoman);
124			break;
125		case eAuthSuccess:
126			cfstr_event = CFStringCreateWithCString(NULL, "auth.success", kCFStringEncodingMacRoman);
127			break;
128		default:
129			msg_warn("Warning: unknown sever event: %d", in_event_code);
130			return;
131	}
132
133	cfstr_addr = CFStringCreateWithCString(NULL, in_addr, kCFStringEncodingMacRoman);
134
135	values[0] = cfstr_event;
136	values[1] = cfstr_addr;
137
138     CFDictionaryRef dict_event = CFDictionaryCreate(NULL, keys, values,
139                                               sizeof(keys) / sizeof(keys[0]),
140                                               &kCFTypeDictionaryKeyCallBacks,
141                                               &kCFTypeDictionaryValueCallBacks);
142
143	/* send the event */
144	(void)XSEventPortPostEvent(gEventPort, cfstr_event, dict_event);
145
146	CFRelease(cfstr_addr);
147	CFRelease(cfstr_event);
148	CFRelease(dict_event);
149} /* send_server_event */
150
151
152void close_server_event_port ( void )
153{
154	if ( gEventPort != NULL )
155		XSEventPortDelete(gEventPort);
156} /* close_server_event_port */
157
158
159__private_extern__ kern_return_t
160get_procno( const char *procname, int32_t *procno )
161{
162	kern_return_t		status;
163	security_token_t	token;
164	bool				lookAgain;
165
166	do {
167		lookAgain = false;
168
169    	if (_ds_running() == 0) return KERN_FAILURE;
170    	if (_ds_port == MACH_PORT_NULL) return KERN_FAILURE;
171
172		status = libinfoDSmig_GetProcedureNumber( _ds_port, (char *) procname, procno, &token );
173		switch( status )
174		{
175			case MACH_SEND_INVALID_DEST:
176			case MIG_SERVER_DIED:
177				mach_port_mod_refs( mach_task_self(), _ds_port, MACH_PORT_RIGHT_SEND, -1 );
178				_ds_port = MACH_PORT_NULL;
179				lookAgain = true;
180				break;
181
182			case KERN_SUCCESS:
183				// is there a security call to parse this private token?
184				if ( token.val[0] != 0 ) {
185					(*procno) = -1;
186					status = KERN_FAILURE;
187				}
188				break;
189
190			default:
191				break;
192		}
193	} while ( lookAgain == true );
194
195	return status;
196}
197
198__private_extern__ kern_return_t
199ds_lookup( int32_t procno, kvbuf_t *request, kvarray_t **answer )
200{
201	kern_return_t			status;
202	security_token_t		token;
203	bool					lookAgain;
204	mach_msg_type_number_t	oolen		= 0;
205	vm_address_t			oobuf		= 0;
206	char					ilbuf[MAX_MIG_INLINE_DATA];
207	mach_msg_type_number_t	illen		= 0;
208
209	do {
210		lookAgain = false;
211
212    	if ( _ds_running() == 0 ) return KERN_FAILURE;
213    	if ( _ds_port == MACH_PORT_NULL ) return KERN_FAILURE;
214		if ( request == NULL ) return KERN_FAILURE;
215
216		status = libinfoDSmig_Query( _ds_port, procno, request->databuf, request->datalen, ilbuf, &illen, &oobuf, &oolen, &token );
217		switch( status )
218		{
219			case MACH_SEND_INVALID_DEST:
220			case MIG_SERVER_DIED:
221				mach_port_mod_refs( mach_task_self(), _ds_port, MACH_PORT_RIGHT_SEND, -1 );
222				_ds_port = MACH_PORT_NULL;
223				lookAgain = true;
224				break;
225
226			case KERN_SUCCESS:
227				// is there a security call to parse this private token?
228				if ( token.val[0] == 0 ) {
229					if ( answer != NULL ) {
230						kvbuf_t *tempBuf;
231
232						if ( oolen != 0 ) {
233							tempBuf = kvbuf_init( (char *)oobuf, (uint32_t) oolen );
234						}
235						else {
236							tempBuf = kvbuf_init( ilbuf, illen );
237						}
238
239						(*answer) = kvbuf_decode( tempBuf );
240						if ( (*answer) == NULL ) {
241							kvbuf_free( tempBuf );
242						}
243					}
244				}
245				else {
246					// response came from a process not running as root
247					procno = -1;
248					status = KERN_FAILURE;
249				}
250				break;
251
252			default:
253				break;
254		}
255	} while ( lookAgain == true );
256
257	if ( oolen != 0 ) {
258		vm_deallocate( mach_task_self(), oobuf, oolen );
259	}
260
261	return status;
262}
263
264static kvarray_t *
265getpwnam_ext_real( const char *name )
266{
267	static int32_t 			procno		= -1;
268	static int32_t			initProc	= -1;
269	static bool				setupList	= FALSE;
270	kvarray_t				*response	= NULL;
271	kern_return_t			status;
272
273	if ( name == NULL ) {
274		/* reset cached state */
275		procno = -1;
276		initProc = -1;
277		setupList = FALSE;
278		return NULL;
279	}
280
281	if ( procno == -1 ) {
282		status = get_procno( "getpwnam_ext", &procno );
283		if ( status != KERN_SUCCESS ) return NULL;
284	}
285
286	if ( initProc == -1 ) {
287		status = get_procno( "getpwnam_initext", &initProc );
288		if ( status != KERN_SUCCESS ) return NULL;
289	}
290
291	if (!setupList) {
292		kvbuf_t *reqTypes = kvbuf_new();
293
294		/* The following are already included by default:
295		 * kDSNAttrRecordName		- pw_name
296		 * kDS1AttrPassword			- pw_pass
297		 * kDS1AttrUniqueID			- pw_uid
298		 * kDS1AttrPrimaryGroupID	- pw_gid
299		 * kDS1AttrNFSHomeDirectory	- pw_dir
300		 * kDS1AttrUserShell		- pw_shell
301		 * kDS1AttrDistinguishedName- pw_gecos
302		 * kDS1AttrGeneratedUID		- pw_uuid
303		 *
304		 * kDSNAttrKeywords			- not included, please file radar against DirectoryService
305		 * kDSNAttrMetaNodeLocation	- as-is
306		 */
307
308		kvbuf_add_dict( reqTypes );
309		kvbuf_add_key( reqTypes, "additionalAttrs" );
310		kvbuf_add_val( reqTypes, kDS1AttrMailAttribute );
311		kvbuf_add_val( reqTypes, kDS1AttrFirstName );
312		kvbuf_add_val( reqTypes, kDS1AttrLastName );
313
314		status = ds_lookup( initProc, reqTypes, NULL );
315		kvbuf_free(reqTypes);
316		if ( status != KERN_SUCCESS ) return NULL;
317		setupList = TRUE;
318	}
319
320	kvbuf_t *request = kvbuf_query_key_val( "login", name );
321	if ( request != NULL ) {
322		ds_lookup( procno, request, &response );
323		kvbuf_free( request );
324	}
325
326	return response;
327}
328
329kvarray_t *
330getpwnam_ext(const char *name)
331{
332	kvarray_t *response = NULL;
333
334	if (name != NULL) {
335		response = getpwnam_ext_real(name);
336		if (response == NULL) {
337			/* reset cached state */
338			(void) getpwnam_ext_real(NULL);
339
340			/* retry once */
341			response = getpwnam_ext_real(name);
342		}
343	}
344
345	return response;
346}
347/* End DS SPI Glue */
348
349/* ------------------------------------------------------------------
350 *	ds_get_value ()
351 */
352static const char *ds_get_value(const char *inUserID, const kvdict_t *in_dict, const char *in_attr, bool first_of_many)
353{
354	const char *value = NULL;
355	int32_t i;
356
357	for (i = 0; i < in_dict->kcount; i++) {
358		if (!strcmp(in_dict->key[i], in_attr)) {
359			if (in_dict->vcount[i] == 1)
360				value = in_dict->val[i][0];
361			else if (in_dict->vcount[i] == 0) {
362				if ( strcmp( in_attr, kDS1AttrMailAttribute) != 0 )
363					msg_info("od[getpwnam_ext]: no value found for attribute %s in record for user %s", in_attr, inUserID);
364			} else if (first_of_many) {
365				if ( strcmp(in_attr, "pw_name") )
366					value = in_dict->val[i][0];
367				else {
368					int32_t j;
369					value = in_dict->val[i][0];
370					for (j = 0; j < in_dict->vcount[i]; j++) {
371						if ( strchr(in_dict->val[i][j], '@') == 0 ) {
372							value = in_dict->val[i][j];
373							break;
374						}
375					}
376				}
377			}
378			else
379				msg_info("od[getpwnam_ext]: multiple values (%u) found for attribute %s in record for user %s", in_dict->vcount[i], in_attr, inUserID);
380			break;
381		}
382	}
383	if (i >= in_dict->kcount && strcmp(in_attr, kDS1AttrMailAttribute) != 0)
384		msg_info("od[getpwnam_ext]: no attribute %s in record for user %s", in_attr, inUserID);
385
386	return value;
387} /* ds_get_value */
388
389/* ------------------------------------------------------------------
390 *	ads_get_uid ()
391 */
392uid_t ads_get_uid ( const char *inUserID )
393{
394	kvarray_t *user_data;
395	uid_t uid = 0;
396
397	assert(inUserID != NULL);
398
399	errno = 0;
400	user_data = getpwnam_ext(inUserID);
401	if (user_data != NULL) {
402		if (user_data->count == 1) {
403			kvdict_t *user_dict = &user_data->dict[0];
404			const char *value = ds_get_value(inUserID, user_dict, "pw_uid", TRUE);
405			if (value)
406				uid = atoi(value);
407		} else if (user_data->count > 1)
408			msg_error("od[getpwnam_ext]: multiple records (%u) found for user %s", user_data->count, inUserID);
409		else if (!user_data->count)
410			msg_error("od[getpwnam_ext]: no record found for user %s", inUserID);
411
412		kvarray_free(user_data);
413	} else if (errno)
414		msg_error("od[getpwnam_ext]: Unable to look up user record %s: %m", inUserID);
415
416	return(uid);
417} /* ads_get_uid */
418
419/* ------------------------------------------------------------------
420 *	ads_getpwnam ()
421 */
422const char *ads_getpwnam ( const char *inUserID )
423{
424	kvarray_t *user_data;
425	static char rec_name[512];
426
427	assert(inUserID != NULL);
428	memset(rec_name, 0, sizeof rec_name);
429
430	errno = 0;
431	user_data = getpwnam_ext(inUserID);
432	if (user_data != NULL) {
433		if (user_data->count == 1) {
434			kvdict_t *user_dict = &user_data->dict[0];
435			const char *value = ds_get_value(inUserID, user_dict, "pw_name", TRUE);
436			if (value)
437				strlcpy(rec_name, value, sizeof rec_name);
438		} else if (user_data->count > 1)
439			msg_error("od[getpwnam_ext]: multiple records (%u) found for user %s", user_data->count, inUserID);
440		else if (!user_data->count)
441			msg_error("od[getpwnam_ext]: no record found for user %s", inUserID);
442
443		kvarray_free(user_data);
444	} else if (errno)
445		msg_error("od[getpwnam_ext]: Unable to look up user record %s: %m", inUserID);
446
447	if (strlen(rec_name))
448		return(rec_name);
449	return( NULL );
450} /* ads_getpwnam */
451
452/* ------------------------------------------------------------------
453 *	ads_get_user_options ()
454 */
455int ads_get_user_options(const char *inUserID, struct od_user_opts *in_out_opts)
456{
457	int out_status = 0;
458	kvarray_t *user_data;
459	char user_guid[ 64 ];
460
461	assert(inUserID != NULL && in_out_opts != NULL);
462	memset(in_out_opts, 0, sizeof *in_out_opts);
463	in_out_opts->fAcctState = eUnknownAcctState;
464
465	if (POSTFIX_OD_LOOKUP_START_ENABLED())
466		POSTFIX_OD_LOOKUP_START((char *) inUserID, in_out_opts);
467
468	errno = 0;
469	user_data = getpwnam_ext(inUserID);
470	if (user_data) {
471		if (user_data->count == 1) {
472			kvdict_t *user_dict = &user_data->dict[0];
473			const char *value;
474
475			/* get guid */
476			memset(user_guid, 0, sizeof user_guid);
477			value = ds_get_value(inUserID, user_dict, "pw_uuid", FALSE);
478			if (value)
479				strlcpy(user_guid, value, sizeof user_guid);
480
481			struct stat st;
482			int i_stat = stat(kServerUserPath, &st);
483			if ( stat(kServerUserPath, &st) == 0 )
484				user_settings_path = kServerUserPath;
485			else
486				user_settings_path = kClientUserPath;
487
488			if ( !get_attributes_local(in_out_opts, user_guid) ) {
489				value = ds_get_value(inUserID, user_dict, kDS1AttrMailAttribute, FALSE);
490				if (value) {
491					CFMutableDictionaryRef user_dict = get_mail_attribute_values(value, in_out_opts);
492					set_attributes_local(user_dict, user_guid);
493					CFRelease(user_dict);
494				}
495			}
496
497			// kDSNAttrRecordName
498			value = ds_get_value(inUserID, user_dict, "pw_name", TRUE);
499			if (value)
500				strlcpy(in_out_opts->fRecName, value, sizeof in_out_opts->fRecName);
501		} else if (user_data->count > 1)
502			msg_error("od[getpwnam_ext]: multiple records (%u) found for user %s", user_data->count, inUserID);
503		else if (!user_data->count)
504			msg_error("od[getpwnam_ext]: no record found for user %s", inUserID);
505
506		kvarray_free(user_data);
507	} else if (errno)
508		msg_error("od[getpwnam_ext]: unable to look up user record %s: %m", inUserID);
509	else
510		msg_error("od[getpwnam_ext]: no record for user %s", inUserID);
511
512	if (POSTFIX_OD_LOOKUP_FINISH_ENABLED())
513		POSTFIX_OD_LOOKUP_FINISH((char *) inUserID, in_out_opts, out_status);
514
515	return out_status;
516} /* ads_get_user_options */
517
518/* ------------------------------------------------------------------
519 *	aod_get_user_options ()
520 */
521int aod_get_user_options ( const char *inUserID, struct od_user_opts *in_out_opts )
522{
523	assert((inUserID != NULL) && (in_out_opts != NULL));
524
525	memset( in_out_opts, 0, sizeof( struct od_user_opts ) );
526	in_out_opts->fAcctState = eUnknownAcctState;
527
528	if ( POSTFIX_OD_LOOKUP_START_ENABLED() )
529		POSTFIX_OD_LOOKUP_START((char *) inUserID, in_out_opts);
530
531	/* create default session */
532	CFErrorRef cf_err_ref = NULL;
533	ODSessionRef od_session_ref = ODSessionCreate( kCFAllocatorDefault, NULL, &cf_err_ref );
534	if ( !od_session_ref ) {
535		/* print the error and bail */
536		print_cf_error( cf_err_ref, inUserID, "Unable to create OD Session" );
537		return( -1 );
538	}
539
540	/* get seach node */
541	ODNodeRef od_node_ref = ODNodeCreateWithNodeType( kCFAllocatorDefault, od_session_ref, kODNodeTypeAuthentication, &cf_err_ref );
542	if ( !od_node_ref ) {
543		/* print the error and bail */
544		print_cf_error( cf_err_ref, inUserID, "Unable to create OD Node Reference" );
545
546		/* release OD session */
547		CFRelease( od_session_ref );
548		return( -1 );
549	}
550
551	/* get account state and auto-forward address, if any */
552	int out_status = get_user_attributes( od_node_ref, inUserID, in_out_opts );
553
554	CFRelease( od_node_ref );
555	CFRelease( od_session_ref );
556
557	if ( POSTFIX_OD_LOOKUP_FINISH_ENABLED() )
558		POSTFIX_OD_LOOKUP_FINISH((char *) inUserID, in_out_opts, out_status);
559
560	return( out_status );
561} /* aod_get_user_options */
562
563
564/* -----------------------------------------------------------------
565	Static functions
566   ----------------------------------------------------------------- */
567
568/* ------------------------------------------------------------------
569 *	print_cf_error ()
570 *
571 *		print error returned in CFErrorRef
572 */
573static void print_cf_error ( CFErrorRef in_cf_err_ref, const char *in_user_name, const char *in_default_str )
574{
575	if ( in_cf_err_ref != NULL ) {
576		CFStringRef cf_str_ref = CFErrorCopyFailureReason( in_cf_err_ref );
577		if ( cf_str_ref != NULL ) {
578			const char *err_str = CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingUTF8 );
579			if ( err_str != NULL ) {
580				syslog( LOG_ERR, "od: user %s: %s", in_user_name, err_str );
581				CFRelease(cf_str_ref);
582				return;
583			}
584			CFRelease(cf_str_ref);
585		}
586	}
587	syslog( LOG_ERR, "od: user %s: %s", in_user_name, in_default_str );
588} /* print_cf_error */
589
590/* ------------------------------------------------------------------
591 *	get_attr_from_record ()
592 */
593static CFStringRef get_attr_from_record ( ODRecordRef in_rec_ref, CFStringRef in_attr )
594{
595	CFErrorRef cf_err_ref = NULL;
596	CFArrayRef cf_arry_values = ODRecordCopyValues( in_rec_ref, in_attr, &cf_err_ref );
597	if ( !cf_arry_values )
598		return( NULL );
599
600	if ( CFArrayGetCount( cf_arry_values ) > 1 ) {
601		msg_error( "aod: multiple attribute values (%d) found in record user record: %s for attribute: %s",
602					(int)CFArrayGetCount( cf_arry_values ),
603					CFStringGetCStringPtr( ODRecordGetRecordName( in_rec_ref ), kCFStringEncodingUTF8 ),
604					CFStringGetCStringPtr( in_attr, kCFStringEncodingUTF8 ) );
605		CFRelease( cf_arry_values );
606		return( NULL );
607	}
608	CFStringRef cf_str_out = CFArrayGetValueAtIndex( cf_arry_values, 0 );
609	CFRetain( cf_str_out );
610
611	CFRelease( cf_arry_values );
612
613	return( cf_str_out );
614} /*  get_attr_from_record */
615
616/* ------------------------------------------------------------------
617 *	read_user_settings ()
618 */
619static CFPropertyListRef read_user_settings ( const char *in_file, CFPropertyListMutabilityOptions in_opts )
620{
621	CFPropertyListRef	cf_prop_list	= NULL;
622
623	CFURLRef cf_url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)in_file, strlen(in_file), FALSE);
624	if ( !cf_url ) {
625		msg_error( "aod: could not create URL from %s", in_file);
626		return( NULL );
627	}
628
629	SInt32 err;
630	CFDataRef cf_data = NULL;
631	if ( !CFURLCreateDataAndPropertiesFromResource(NULL, cf_url, &cf_data, NULL, NULL, &err) ) {
632		if ( msg_verbose )
633			msg_info("aod: no local user settings (%s), using defaults", in_file);
634		CFRelease(cf_url);
635		return( NULL );
636	}
637
638	CFStringRef cf_str_err = NULL;
639	if ( cf_data ) {
640		cf_prop_list = CFPropertyListCreateFromXMLData( kCFAllocatorDefault, cf_data, in_opts, &cf_str_err);
641		if ( cf_prop_list )
642			CFRetain(cf_prop_list);
643	} else
644		msg_error("aod: enable to create CFData ref for %s", in_file);
645
646	CFRelease(cf_url);
647	if ( cf_str_err )
648		CFRelease( cf_str_err );
649	if ( cf_data )
650		CFRelease( cf_data );
651
652	return( cf_prop_list );
653} /* read_user_settings */
654
655/* ------------------------------------------------------------------
656 *	write_user_settings ()
657 */
658static void write_user_settings ( const char *in_file, CFPropertyListRef in_data )
659{
660	CFURLRef cf_url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)in_file, strlen(in_file), FALSE);
661	if ( !cf_url ) {
662		msg_error("aod: could not create URL from: %s", in_file);
663		return;
664	}
665
666	CFDataRef cf_data = CFPropertyListCreateXMLData(NULL, in_data);
667	if ( cf_data ) {
668		SInt32 write_err = noErr;
669		if ( !CFURLWriteDataAndPropertiesToResource(cf_url, cf_data, NULL, &write_err) )
670			if ( msg_verbose )
671				msg_warn("aod: could not write to %s (error: %d)", in_file, (int) write_err);
672		CFRelease(cf_data);
673	}
674	CFRelease(cf_url);
675} /* write_user_settings */
676
677/* ------------------------------------------------------------------
678 *	get_attributes_local ()
679 */
680static bool get_attributes_local ( struct od_user_opts *in_out_opts, const char *in_user_guid )
681{
682	bool b_out = FALSE;
683
684	/* look in local file first */
685	CFDictionaryRef cf_dict_data = (CFDictionaryRef)read_user_settings( user_settings_path, kCFPropertyListImmutable );
686	if ( !cf_dict_data )
687		return( FALSE );
688
689	CFStringRef cf_str = CFStringCreateWithCString( NULL, in_user_guid, kCFStringEncodingUTF8 );
690	if ( cf_str ) {
691		if ( CFDictionaryContainsKey( cf_dict_data, cf_str ) ) {
692			CFDictionaryRef cf_dict_user = (CFDictionaryRef)CFDictionaryGetValue( cf_dict_data, cf_str );
693			if ( cf_dict_user && (CFGetTypeID( cf_dict_user ) == CFDictionaryGetTypeID()) ) {
694				get_acct_state( cf_dict_user, NULL, in_out_opts );
695				get_alt_loc( cf_dict_user, NULL );
696				get_mail_quota( cf_dict_user, NULL );
697				b_out = TRUE;
698			}
699		}
700		CFRelease( cf_str );
701	}
702	CFRelease( cf_dict_data );
703
704	return( b_out );
705} /* get_attributes_local */
706
707/* ------------------------------------------------------------------
708 *	set_attributes_local ()
709 */
710static void set_attributes_local ( CFMutableDictionaryRef in_user_dict, const char *in_user_guid )
711{
712	/* Get the file data */
713	CFDictionaryRef cf_dict_data = (CFDictionaryRef)read_user_settings(user_settings_path, kCFPropertyListMutableContainers);
714	if ( !cf_dict_data )
715			cf_dict_data = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
716								&kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
717
718	if ( cf_dict_data && in_user_dict ) {
719		CFStringRef cf_str = CFStringCreateWithCString( NULL, in_user_guid, kCFStringEncodingUTF8 );
720		if ( cf_str != NULL ) {
721			CFMutableDictionaryRef cf_mut_dict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, cf_dict_data);
722			if ( cf_mut_dict != NULL ) {
723				CFDictionaryAddValue( cf_mut_dict, cf_str, in_user_dict );
724				write_user_settings(user_settings_path, (CFPropertyListRef)cf_mut_dict );
725				CFRelease(cf_mut_dict);
726			}
727			CFRelease(cf_str);
728		}
729	}
730} /* set_attributes_local */
731
732/* ------------------------------------------------------------------
733 *	get_user_attributes ()
734 */
735static int get_user_attributes ( ODNodeRef in_node_ref, const char *in_user_name, struct od_user_opts *in_out_opts )
736{
737	CFStringRef cf_str_ref = CFStringCreateWithCString( NULL, in_user_name, kCFStringEncodingUTF8 );
738	if ( !cf_str_ref ) {
739		msg_error( "aod: unable to create user name CFStringRef");
740		return( -1 );
741	}
742
743	/* look up user record */
744	ODRecordRef od_rec_ref = NULL;
745	CFErrorRef cf_err_ref = NULL;
746	CFTypeRef cf_type_ref[] = { CFSTR(kDSAttributesStandardAll) };
747	CFArrayRef cf_arry_ref = CFArrayCreate( NULL, cf_type_ref, 1, &kCFTypeArrayCallBacks );
748	ODQueryRef cf_query_ref = ODQueryCreateWithNode( NULL, in_node_ref, CFSTR(kDSStdRecordTypeUsers), CFSTR(kDSNAttrRecordName),
749											kODMatchInsensitiveEqualTo, cf_str_ref, cf_arry_ref, 100, &cf_err_ref );
750	if ( cf_query_ref ) {
751		CFArrayRef cf_arry_result = ODQueryCopyResults( cf_query_ref, false, &cf_err_ref );
752		if ( cf_arry_result ) {
753			if ( CFArrayGetCount( cf_arry_result ) == 1 ) {
754				od_rec_ref = (ODRecordRef)CFArrayGetValueAtIndex( cf_arry_result, 0 );
755				CFRetain(od_rec_ref);
756			} else {
757				if ( CFArrayGetCount( cf_arry_result ) == 0 )
758					msg_error( "aod: no user record found for: %s", in_user_name );
759				else
760					msg_error( "aod: multiple user records (%ld) found for: %s", CFArrayGetCount( cf_arry_result ), in_user_name );
761			}
762			CFRelease(cf_arry_result);
763		} else
764			print_cf_error( cf_err_ref, in_user_name, "aod: ODQueryCopyResults() failed" );
765
766		CFRelease( cf_query_ref );
767	} else
768		print_cf_error( cf_err_ref, in_user_name, "aod: ODQueryCreateWithNode() failed" );
769
770	CFRelease( cf_str_ref );
771	CFRelease( cf_arry_ref );
772
773	if ( !od_rec_ref ) {
774		/* print the error and bail */
775		print_cf_error( cf_err_ref, in_user_name, "aod: unable to lookup user record" );
776		return( -1 );
777	}
778
779	/* get guid */
780	char user_guid[ 64 ];
781	size_t str_size = 0;
782	memset(user_guid, 0, sizeof user_guid);
783	CFStringRef cf_str_value = get_attr_from_record( od_rec_ref, CFSTR(kDS1AttrGeneratedUID) );
784	if ( cf_str_value ) {
785		str_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength( cf_str_value ), kCFStringEncodingUTF8) + 1;
786		CFStringGetCString( cf_str_value, user_guid, str_size, kCFStringEncodingUTF8 );
787
788		CFRelease( cf_str_value );
789	}
790
791	/* get record name */
792	cf_str_value = ODRecordGetRecordName( od_rec_ref );
793	if ( cf_str_value )
794		str_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength( cf_str_value ), kCFStringEncodingUTF8) + 1;
795		if (str_size )
796			CFStringGetCString( cf_str_value, in_out_opts->fRecName, sizeof(in_out_opts->fRecName), kCFStringEncodingUTF8 );
797
798	/* get mail attribute */
799	if ( !get_attributes_local(in_out_opts, user_guid) ) {
800		cf_str_value = get_attr_from_record( od_rec_ref, CFSTR(kDS1AttrMailAttribute) );
801		if ( cf_str_value != NULL ) {
802			str_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength( cf_str_value ), kCFStringEncodingUTF8) + 1;
803			char *c_str = malloc( str_size );
804			if ( c_str ) {
805				if( CFStringGetCString( cf_str_value, c_str, str_size, kCFStringEncodingUTF8 ) ) {
806					CFMutableDictionaryRef user_dict = get_mail_attribute_values( c_str, in_out_opts );
807					set_attributes_local(user_dict, user_guid);
808					CFRelease(user_dict);
809				}
810				free( c_str );
811			}
812			CFRelease( cf_str_value );
813		}
814	}
815
816	CFRelease(od_rec_ref);
817
818	return( 0 );
819} /* get_user_attributes */
820
821/* ------------------------------------------------------------------
822 *	get_mail_attribute_values ()
823 */
824static CFMutableDictionaryRef get_mail_attribute_values ( const char *in_mail_attribute,
825															struct od_user_opts *in_out_opts )
826{
827	CFMutableDictionaryRef cf_mut_dict_ref = NULL;
828	unsigned long ul_size = strlen( in_mail_attribute );
829	CFDataRef cf_data_ref = CFDataCreate( NULL, (const UInt8 *)in_mail_attribute, ul_size );
830	if ( !cf_data_ref )
831		return( NULL );
832
833	CFPropertyListRef cf_plist_ref = CFPropertyListCreateFromXMLData( kCFAllocatorDefault,
834										cf_data_ref, kCFPropertyListImmutable, NULL );
835	if ( cf_plist_ref ) {
836		if ( CFDictionaryGetTypeID() == CFGetTypeID( cf_plist_ref ) ) {
837			cf_mut_dict_ref = CFDictionaryCreateMutable( kCFAllocatorDefault,
838														0, &kCFCopyStringDictionaryKeyCallBacks,
839														&kCFTypeDictionaryValueCallBacks);
840			CFRetain(cf_mut_dict_ref);
841			CFDictionaryAddValue( cf_mut_dict_ref, CFSTR(kXMLKeyAttrVersion), CFSTR(kXMLValueVersion2) );
842
843			CFDictionaryRef cf_dict_ref = (CFDictionaryRef)cf_plist_ref;
844			get_acct_state( cf_dict_ref, cf_mut_dict_ref, in_out_opts );
845			get_alt_loc( cf_dict_ref, cf_mut_dict_ref );
846			get_mail_quota( cf_dict_ref, cf_mut_dict_ref );
847		}
848	}
849	CFRelease( cf_data_ref );
850
851	return(cf_mut_dict_ref);
852} /* get_mail_attribute_values */
853
854/* ------------------------------------------------------------------
855 *	get_acct_state ()
856 */
857void get_acct_state ( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict, struct od_user_opts *in_out_opts )
858{
859	/* enabled by default */
860	in_out_opts->fAcctState = eAcctEnabled;
861
862	if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyAcctState ) ) ) {
863		CFStringRef cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyAcctState ) );
864		if ( cf_str_ref ) {
865			if ( CFGetTypeID( cf_str_ref ) == CFStringGetTypeID() ) {
866				char *p_value = (char *)CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingMacRoman );
867				if ( p_value ) {
868					if ( strcasecmp( p_value, kXMLValueAcctEnabled ) == 0 )
869						in_out_opts->fAcctState = eAcctEnabled;
870					else if ( strcasecmp( p_value, kXMLValueAcctDisabled ) == 0 )
871						in_out_opts->fAcctState = eAcctDisabled;
872					else if ( strcasecmp( p_value, kXMLValueAcctFwd ) == 0 )
873						get_auto_forward_addr( inCFDictRef, out_user_dict, in_out_opts );
874
875					if ( out_user_dict )
876						CFDictionaryAddValue( out_user_dict, CFSTR(kXMLKeyAcctState), cf_str_ref);
877				}
878			}
879		}
880	}
881} /* get_acct_state */
882
883/* ------------------------------------------------------------------
884 *	get_auto_forward_addr ()
885 */
886void get_auto_forward_addr ( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict, struct od_user_opts *in_out_opts )
887{
888	if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyAutoFwd ) ) ) {
889		CFStringRef cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyAutoFwd ) );
890		if ( cf_str_ref ) {
891			if ( CFGetTypeID( cf_str_ref ) == CFStringGetTypeID() ) {
892				char *p_value = (char *)CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingMacRoman );
893				if ( p_value ) {
894					in_out_opts->fAcctState = eAcctForwarded;
895					strlcpy( in_out_opts->fAutoFwdAddr, p_value, sizeof(in_out_opts->fAutoFwdAddr) );
896
897					if ( out_user_dict )
898						CFDictionaryAddValue( out_user_dict, CFSTR(kXMLKeyAutoFwd), cf_str_ref);
899				}
900			}
901		}
902	}
903} /* get_auto_forward_addr */
904
905/* ------------------------------------------------------------------
906 *	get_alt_loc ()
907 */
908static void get_alt_loc ( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict )
909{
910	if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyAltDataStoreLoc ) ) ) {
911		CFStringRef cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyAltDataStoreLoc ) );
912		if ( cf_str_ref && (CFGetTypeID( cf_str_ref ) == CFStringGetTypeID()) ) {
913			if ( out_user_dict )
914				CFDictionaryAddValue( out_user_dict, CFSTR(kXMLKeyAltDataStoreLoc), cf_str_ref);
915		}
916	}
917} /* get_alt_loc */
918
919/* ------------------------------------------------------------------
920 *	get_mail_quota ()
921 */
922static void get_mail_quota ( CFDictionaryRef inCFDictRef, CFMutableDictionaryRef out_user_dict )
923{
924	if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyDiskQuota ) ) ) {
925		CFStringRef cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyDiskQuota ) );
926		if ( cf_str_ref && (CFGetTypeID( cf_str_ref ) == CFStringGetTypeID()) ) {
927			if ( out_user_dict )
928				CFDictionaryAddValue( out_user_dict, CFSTR(kXMLKeyDiskQuota), cf_str_ref);
929		}
930	}
931} /* get_mail_quota */
932
933
934int sacl_check(const char *inUserID)
935{
936	int err, result;
937	uuid_t guid;
938
939	if (POSTFIX_SACL_START_ENABLED())
940		POSTFIX_SACL_START((char *) inUserID);
941
942	if (var_use_sacl_cache) {
943		/* look up in cache */
944		result = SACL_CHECK_STATUS_UNKNOWN;
945		switch (sacl_cache_clnt_get(inUserID, &result)) {
946		case SACL_CACHE_STAT_OK:
947			if (result == SACL_CHECK_STATUS_AUTHORIZED) {
948				if (POSTFIX_SACL_CACHED_ENABLED())
949					POSTFIX_SACL_CACHED((char *) inUserID, 1);
950				return 1;
951			} else if (result == SACL_CHECK_STATUS_UNAUTHORIZED) {
952				if (POSTFIX_SACL_CACHED_ENABLED())
953					POSTFIX_SACL_CACHED((char *) inUserID, 0);
954				return 0;
955			} else if (result == SACL_CHECK_STATUS_NO_SACL) {
956				if (POSTFIX_SACL_CACHED_ENABLED())
957					POSTFIX_SACL_CACHED((char *) inUserID, -1);
958				return 1;
959			}
960			break;
961		case SACL_CACHE_STAT_BAD:
962			msg_warn("sacl_check: %s protocol error", var_sacl_cache_service);
963			break;
964		case SACL_CACHE_STAT_FAIL:
965			msg_warn("sacl_check: %s service failure", var_sacl_cache_service);
966			break;
967		}
968	}
969
970	/* cache miss; perform SACL check */
971	err = mbr_user_name_to_uuid(inUserID, guid);
972	if (err) {
973		if (POSTFIX_SACL_RESOLVE_ENABLED())
974			POSTFIX_SACL_RESOLVE((char *) inUserID, 0);
975		if ( msg_verbose )
976			msg_info("sacl_check: mbr_user_name_to_uuid(%s) failed: %s",
977					 inUserID, strerror(err));
978
979		return( -1 );
980	}
981
982	if (POSTFIX_SACL_RESOLVE_ENABLED())
983		POSTFIX_SACL_RESOLVE((char *) inUserID, 1);
984
985	result = 0;
986	err = mbr_check_service_membership(guid, "mail", &result);
987	if (err) {
988		if (POSTFIX_SACL_FINISH_ENABLED())
989			POSTFIX_SACL_FINISH((char *) inUserID, -1);
990
991		if (err != ENOENT) {
992			msg_error("sacl_check: mbr_check_service_membership(%s, mail) failed: %s",
993					  inUserID, strerror(err));
994			return -1;
995		}
996
997		if (var_use_sacl_cache) {
998			/* mail SACL is off.  tell cache */
999			(void) sacl_cache_clnt_no_sacl();
1000		}
1001		return 1;
1002	}
1003
1004	if (POSTFIX_SACL_FINISH_ENABLED())
1005		POSTFIX_SACL_FINISH((char *) inUserID, result);
1006
1007	if (var_use_sacl_cache) {
1008		/* update cache */
1009		switch (sacl_cache_clnt_put(inUserID, result ?
1010					    SACL_CHECK_STATUS_AUTHORIZED :
1011					    SACL_CHECK_STATUS_UNAUTHORIZED)) {
1012		case SACL_CACHE_STAT_OK:
1013			break;
1014		case SACL_CACHE_STAT_BAD:
1015			msg_warn("sacl_check: %s protocol error", var_sacl_cache_service);
1016			break;
1017		case SACL_CACHE_STAT_FAIL:
1018			msg_warn("sacl_check: %s service failure", var_sacl_cache_service);
1019			break;
1020		}
1021	}
1022
1023	return result;
1024} /* sacl_check */
1025