#include "aod.h"
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <syslog.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <dtrace-postfix.h>
#include <CoreFoundation/CFData.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFPropertyList.h>
#include <OpenDirectory/OpenDirectory.h>
#include <OpenDirectory/OpenDirectoryPriv.h>
#include <DirectoryService/DirServicesConst.h>
static void print_cf_error ( CFErrorRef in_cf_err_ref, const char *in_default_str );
static int get_user_attributes ( ODNodeRef in_node_ref, const char *in_user_name, struct od_user_opts *in_out_opts );
static void get_mail_attribute_values ( const char *in_mail_attribute, struct od_user_opts *in_out_opts );
static void get_auto_forward_addr ( CFDictionaryRef inCFDictRef, struct od_user_opts *in_out_opts );
void get_acct_state ( CFDictionaryRef inCFDictRef, struct od_user_opts *in_out_opts );
static CFStringRef get_attr_from_record ( ODRecordRef in_rec_ref, CFStringRef in_attr );
#include <kvbuf.h>
#include <DSlibinfoMIG.h>
#include <DirectoryService/DirectoryService.h>
extern mach_port_t _ds_port;
extern int _ds_running();
__private_extern__ kern_return_t
get_procno( const char *procname, int32_t *procno )
{
kern_return_t status;
security_token_t token;
bool lookAgain;
uid_t uid;
do {
lookAgain = false;
if (_ds_running() == 0) return KERN_FAILURE;
if (_ds_port == MACH_PORT_NULL) return KERN_FAILURE;
status = libinfoDSmig_GetProcedureNumber( _ds_port, (char *) procname, procno, &token );
switch( status )
{
case MACH_SEND_INVALID_DEST:
case MIG_SERVER_DIED:
mach_port_mod_refs( mach_task_self(), _ds_port, MACH_PORT_RIGHT_SEND, -1 );
_ds_port = MACH_PORT_NULL;
lookAgain = true;
break;
case KERN_SUCCESS:
if ( token.val[0] != 0 ) {
(*procno) = -1;
status = KERN_FAILURE;
}
break;
default:
break;
}
} while ( lookAgain == true );
return status;
}
__private_extern__ kern_return_t
ds_lookup( int32_t procno, kvbuf_t *request, kvarray_t **answer )
{
kern_return_t status;
security_token_t token;
bool lookAgain;
uid_t uid;
mach_msg_type_number_t oolen = 0;
vm_address_t oobuf = 0;
char ilbuf[MAX_MIG_INLINE_DATA];
mach_msg_type_number_t illen = 0;
do {
lookAgain = false;
if ( _ds_running() == 0 ) return KERN_FAILURE;
if ( _ds_port == MACH_PORT_NULL ) return KERN_FAILURE;
if ( request == NULL ) return KERN_FAILURE;
status = libinfoDSmig_Query( _ds_port, procno, request->databuf, request->datalen, ilbuf, &illen, &oobuf, &oolen, &token );
switch( status )
{
case MACH_SEND_INVALID_DEST:
case MIG_SERVER_DIED:
mach_port_mod_refs( mach_task_self(), _ds_port, MACH_PORT_RIGHT_SEND, -1 );
_ds_port = MACH_PORT_NULL;
lookAgain = true;
break;
case KERN_SUCCESS:
if ( token.val[0] == 0 ) {
if ( answer != NULL ) {
kvbuf_t *tempBuf;
if ( oolen != 0 ) {
tempBuf = kvbuf_init( (char *)oobuf, (uint32_t) oolen );
}
else {
tempBuf = kvbuf_init( ilbuf, illen );
}
(*answer) = kvbuf_decode( tempBuf );
if ( (*answer) == NULL ) {
kvbuf_free( tempBuf );
}
}
}
else {
procno = -1;
status = KERN_FAILURE;
}
break;
default:
break;
}
} while ( lookAgain == true );
if ( oolen != 0 ) {
vm_deallocate( mach_task_self(), oobuf, oolen );
}
return status;
}
static kvarray_t *
getpwnam_ext_real( const char *name )
{
static int32_t procno = -1;
static int32_t initProc = -1;
static bool setupList = FALSE;
kvarray_t *response = NULL;
kern_return_t status;
if ( name == NULL ) {
procno = -1;
initProc = -1;
setupList = FALSE;
return NULL;
}
if ( procno == -1 ) {
status = get_procno( "getpwnam_ext", &procno );
if ( status != KERN_SUCCESS ) return NULL;
}
if ( initProc == -1 ) {
status = get_procno( "getpwnam_initext", &initProc );
if ( status != KERN_SUCCESS ) return NULL;
}
if (!setupList) {
kvbuf_t *reqTypes = kvbuf_new();
kvbuf_add_dict( reqTypes );
kvbuf_add_key( reqTypes, "additionalAttrs" );
kvbuf_add_val( reqTypes, kDS1AttrMailAttribute );
kvbuf_add_val( reqTypes, kDSNAttrEMailAddress );
kvbuf_add_val( reqTypes, kDS1AttrFirstName );
kvbuf_add_val( reqTypes, kDS1AttrLastName );
status = ds_lookup( initProc, reqTypes, NULL );
kvbuf_free(reqTypes);
if ( status != KERN_SUCCESS ) return NULL;
setupList = TRUE;
}
kvbuf_t *request = kvbuf_query_key_val( "login", name );
if ( request != NULL ) {
ds_lookup( procno, request, &response );
kvbuf_free( request );
}
return response;
}
kvarray_t *
getpwnam_ext(const char *name)
{
kvarray_t *response = NULL;
if (name != NULL) {
response = getpwnam_ext_real(name);
if (response == NULL) {
(void) getpwnam_ext_real(NULL);
response = getpwnam_ext_real(name);
}
}
return response;
}
static const char *ds_get_value(const char *inUserID, const kvdict_t *in_dict, const char *in_attr, bool first_of_many)
{
const char *value = NULL;
int32_t i;
for (i = 0; i < in_dict->kcount; i++) {
if (!strcmp(in_dict->key[i], in_attr)) {
if (in_dict->vcount[i] == 1)
value = in_dict->val[i][0];
else if (in_dict->vcount[i] == 0)
msg_info("od[getpwnam_ext]: no value found for attribute %s in record for user %s", in_attr, inUserID);
else if (first_of_many) {
if ( strcmp(in_attr, "pw_name") )
value = in_dict->val[i][0];
else {
int32_t j;
value = in_dict->val[i][0];
for (j = 0; j < in_dict->vcount[i]; j++) {
if ( strchr(in_dict->val[i][j], '@') == 0 ) {
value = in_dict->val[i][j];
break;
}
}
}
}
else
msg_info("od[getpwnam_ext]: multiple values (%u) found for attribute %s in record for user %s", in_dict->vcount[i], in_attr, inUserID);
break;
}
}
if (i >= in_dict->kcount)
msg_info("od[getpwnam_ext]: no attribute %s in record for user %s", in_attr, inUserID);
return value;
}
const char *ads_getpwnam ( const char *inUserID )
{
kvarray_t *user_data;
static char rec_name[512];
assert(inUserID != NULL);
memset(rec_name, 0, sizeof rec_name);
errno = 0;
user_data = getpwnam_ext(inUserID);
if (user_data != NULL) {
if (user_data->count == 1) {
kvdict_t *user_dict = &user_data->dict[0];
const char *value = ds_get_value(inUserID, user_dict, "pw_name", TRUE);
if (value)
strlcpy(rec_name, value, sizeof rec_name);
} else if (user_data->count == 0)
msg_error("od[getpwnam_ext]: no record found for user %s", inUserID);
else
msg_error("od[getpwnam_ext]: multiple records (%u) found for user %s", user_data->count, inUserID);
kvarray_free(user_data);
} else if (errno)
msg_error("od[getpwnam_ext]: Unable to look up user record %s: %m", inUserID);
if (strlen(rec_name))
return(rec_name);
return( NULL );
}
int ads_get_user_options(const char *inUserID, struct od_user_opts *in_out_opts)
{
int out_status = 0;
kvarray_t *user_data;
assert(inUserID != NULL && in_out_opts != NULL);
memset(in_out_opts, 0, sizeof *in_out_opts);
in_out_opts->fAcctState = eUnknownAcctState;
if (POSTFIX_OD_LOOKUP_START_ENABLED())
POSTFIX_OD_LOOKUP_START((char *) inUserID, in_out_opts);
errno = 0;
user_data = getpwnam_ext(inUserID);
if (user_data != NULL) {
if (user_data->count == 1) {
kvdict_t *user_dict = &user_data->dict[0];
const char *value;
value = ds_get_value(inUserID, user_dict, kDS1AttrMailAttribute, FALSE);
if (value)
get_mail_attribute_values(value, in_out_opts);
value = ds_get_value(inUserID, user_dict, "pw_name", TRUE);
if (value)
strlcpy(in_out_opts->fRecName, value, sizeof in_out_opts->fRecName);
} else if (user_data->count == 0)
msg_error("od[getpwnam_ext]: no record found for user %s", inUserID);
else
msg_error("od[getpwnam_ext]: multiple records (%u) found for user %s", user_data->count, inUserID);
kvarray_free(user_data);
} else if (errno)
msg_error("od[getpwnam_ext]: Unable to look up user record %s: %m", inUserID);
else
msg_error("od[getpwnam_ext]: No record for user %s", inUserID);
if (POSTFIX_OD_LOOKUP_FINISH_ENABLED())
POSTFIX_OD_LOOKUP_FINISH((char *) inUserID, in_out_opts, out_status);
return out_status;
}
int aod_get_user_options ( const char *inUserID, struct od_user_opts *in_out_opts )
{
int out_status = 0;
ODSessionRef od_session_ref;
ODNodeRef od_node_ref;
CFErrorRef cf_err_ref = NULL;
assert((inUserID != NULL) && (in_out_opts != NULL));
memset( in_out_opts, 0, sizeof( struct od_user_opts ) );
in_out_opts->fAcctState = eUnknownAcctState;
if (POSTFIX_OD_LOOKUP_START_ENABLED())
POSTFIX_OD_LOOKUP_START((char *) inUserID, in_out_opts);
od_session_ref = ODSessionCreate( kCFAllocatorDefault, NULL, &cf_err_ref );
if ( od_session_ref == NULL )
{
print_cf_error( cf_err_ref, "Unable to create OD Session" );
return( -1 );
}
od_node_ref = ODNodeCreateWithNodeType( kCFAllocatorDefault, od_session_ref, kODNodeTypeAuthentication, &cf_err_ref );
if ( od_node_ref == NULL )
{
print_cf_error( cf_err_ref, "Unable to create OD Node Reference" );
CFRelease( od_session_ref );
od_session_ref = NULL;
return( -1 );
}
out_status = get_user_attributes( od_node_ref, inUserID, in_out_opts );
CFRelease( od_node_ref );
CFRelease( od_session_ref );
if (POSTFIX_OD_LOOKUP_FINISH_ENABLED())
POSTFIX_OD_LOOKUP_FINISH((char *) inUserID, in_out_opts, out_status);
return( out_status );
}
static void print_cf_error ( CFErrorRef in_cf_err_ref, const char *in_default_str )
{
CFStringRef cf_str_ref;
if ( in_cf_err_ref != NULL )
{
cf_str_ref = CFErrorCopyFailureReason( in_cf_err_ref );
if ( cf_str_ref != NULL )
{
const char *err_str = CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingUTF8 );
if ( err_str != NULL )
{
syslog( LOG_ERR, "od: %s", err_str );
CFRelease(cf_str_ref);
return;
}
CFRelease(cf_str_ref);
}
}
syslog( LOG_ERR, "od: %s", in_default_str );
}
static CFStringRef get_attr_from_record ( ODRecordRef in_rec_ref, CFStringRef in_attr )
{
CFArrayRef cf_arry_values = NULL;
CFErrorRef cf_err_ref = NULL;
CFStringRef cf_str_out = NULL;
cf_arry_values = ODRecordCopyValues( in_rec_ref, in_attr, &cf_err_ref );
if ( cf_arry_values == NULL )
{
return( NULL );
}
if ( CFArrayGetCount( cf_arry_values ) > 1 )
{
msg_error( "aod: multiple attribute values (%d) found in record user record: %s for attribute: %s",
(int)CFArrayGetCount( cf_arry_values ),
CFStringGetCStringPtr( ODRecordGetRecordName( in_rec_ref ), kCFStringEncodingUTF8 ),
CFStringGetCStringPtr( in_attr, kCFStringEncodingUTF8 ) );
CFRelease( cf_arry_values );
return( NULL );
}
cf_str_out = CFArrayGetValueAtIndex( cf_arry_values, 0 );
CFRetain( cf_str_out );
CFRelease( cf_arry_values );
return( cf_str_out );
}
static int get_user_attributes ( ODNodeRef in_node_ref, const char *in_user_name, struct od_user_opts *in_out_opts )
{
char *c_str = NULL;
size_t str_size = 0;
ODQueryRef cf_query_ref = NULL;
ODRecordRef od_rec_ref = NULL;
CFStringRef cf_str_ref = NULL;
CFStringRef cf_str_value = NULL;
CFTypeRef cf_type_ref[] = { CFSTR(kDSAttributesStandardAll) };
CFArrayRef cf_arry_ref = CFArrayCreate( NULL, cf_type_ref, 1, &kCFTypeArrayCallBacks );
CFArrayRef cf_arry_result = NULL;
CFErrorRef cf_err_ref = NULL;
cf_str_ref = CFStringCreateWithCString( NULL, in_user_name, kCFStringEncodingUTF8 );
if ( cf_str_ref == NULL )
{
msg_error( "aod: unable to create user name CFStringRef");
CFRelease( cf_arry_ref );
return( -1 );
}
cf_query_ref = ODQueryCreateWithNode( NULL, in_node_ref, CFSTR(kDSStdRecordTypeUsers), CFSTR(kDSNAttrRecordName),
kODMatchInsensitiveEqualTo, cf_str_ref, cf_arry_ref, 100, &cf_err_ref );
if ( cf_query_ref )
{
cf_arry_result = ODQueryCopyResults( cf_query_ref, false, &cf_err_ref );
if ( cf_arry_result )
{
if ( CFArrayGetCount( cf_arry_result ) == 1 )
{
od_rec_ref = (ODRecordRef)CFArrayGetValueAtIndex( cf_arry_result, 0 );
CFRetain(od_rec_ref);
}
else
{
if ( CFArrayGetCount( cf_arry_result ) == 0 )
{
msg_error( "aod: no user record found for: %s", in_user_name );
}
else
{
msg_error( "aod: multiple user records (%ld) found for: %s", CFArrayGetCount( cf_arry_result ), in_user_name );
}
}
CFRelease(cf_arry_result);
}
else
{
print_cf_error( cf_err_ref, "aod: OD Query Copy Results failed" );
}
CFRelease( cf_query_ref );
}
else
{
print_cf_error( cf_err_ref, "aod: OD Query Create With Node failed" );
}
CFRelease( cf_str_ref );
CFRelease( cf_arry_ref );
if ( od_rec_ref == NULL )
{
print_cf_error( cf_err_ref, "aod: Unable to lookup user record" );
return( -1 );
}
cf_str_value = get_attr_from_record( od_rec_ref, CFSTR(kDS1AttrMailAttribute) );
if ( cf_str_value != NULL )
{
str_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength( cf_str_value ), kCFStringEncodingUTF8) + 1;
c_str = malloc( str_size );
if ( c_str != NULL )
{
if( CFStringGetCString( cf_str_value, c_str, str_size, kCFStringEncodingUTF8 ) )
{
get_mail_attribute_values( c_str, in_out_opts );
}
free( c_str );
}
CFRelease( cf_str_value );
}
cf_str_value = ODRecordGetRecordName( od_rec_ref );
if ( cf_str_value != NULL )
str_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength( cf_str_value ), kCFStringEncodingUTF8) + 1;
if (str_size )
CFStringGetCString( cf_str_value, in_out_opts->fRecName, sizeof(in_out_opts->fRecName), kCFStringEncodingUTF8 );
CFRelease(od_rec_ref);
return( 0 );
}
static void get_mail_attribute_values ( const char *in_mail_attribute, struct od_user_opts *in_out_opts )
{
unsigned long ul_size = 0;
CFDataRef cf_data_ref = NULL;
CFPropertyListRef cf_plist_ref = NULL;
CFDictionaryRef cf_dict_ref = NULL;
ul_size = strlen( in_mail_attribute );
cf_data_ref = CFDataCreate( NULL, (const UInt8 *)in_mail_attribute, ul_size );
if ( cf_data_ref != NULL )
{
cf_plist_ref = CFPropertyListCreateFromXMLData( kCFAllocatorDefault, cf_data_ref, kCFPropertyListImmutable, NULL );
if ( cf_plist_ref != NULL )
{
if ( CFDictionaryGetTypeID() == CFGetTypeID( cf_plist_ref ) )
{
cf_dict_ref = (CFDictionaryRef)cf_plist_ref;
get_acct_state( cf_dict_ref, in_out_opts );
}
CFRelease( cf_plist_ref );
}
CFRelease( cf_data_ref );
}
}
void get_acct_state ( CFDictionaryRef inCFDictRef, struct od_user_opts *in_out_opts )
{
char *p_value = NULL;
CFStringRef cf_str_ref = NULL;
in_out_opts->fAcctState = eUnknownAcctState;
if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyAcctState ) ) )
{
cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyAcctState ) );
if ( cf_str_ref != NULL )
{
if ( CFGetTypeID( cf_str_ref ) == CFStringGetTypeID() )
{
p_value = (char *)CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingMacRoman );
if ( p_value != NULL )
{
if ( strcasecmp( p_value, kXMLValueAcctEnabled ) == 0 )
{
in_out_opts->fAcctState = eAcctEnabled;
}
else if ( strcasecmp( p_value, kXMLValueAcctDisabled ) == 0 )
{
in_out_opts->fAcctState = eAcctDisabled;
}
else if ( strcasecmp( p_value, kXMLValueAcctFwd ) == 0 )
{
get_auto_forward_addr( inCFDictRef, in_out_opts );
}
}
}
}
}
}
void get_auto_forward_addr ( CFDictionaryRef inCFDictRef, struct od_user_opts *in_out_opts )
{
char *p_value = NULL;
CFStringRef cf_str_ref = NULL;
if ( CFDictionaryContainsKey( inCFDictRef, CFSTR( kXMLKeyAutoFwd ) ) )
{
cf_str_ref = (CFStringRef)CFDictionaryGetValue( inCFDictRef, CFSTR( kXMLKeyAutoFwd ) );
if ( cf_str_ref != NULL )
{
if ( CFGetTypeID( cf_str_ref ) == CFStringGetTypeID() )
{
p_value = (char *)CFStringGetCStringPtr( cf_str_ref, kCFStringEncodingMacRoman );
if ( p_value != NULL )
{
in_out_opts->fAcctState = eAcctForwarded;
strlcpy( in_out_opts->fAutoFwdAddr, p_value, sizeof(in_out_opts->fAutoFwdAddr) );
}
}
}
}
}