/*
 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header dsconfigldap
 */

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Authorization.h>
#include <DirectoryService/DirServices.h>
#include <DirectoryService/DirServicesUtils.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <curses.h>
#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>
#include <termios.h>
#include <pwd.h>

#include "dscommon.h"
#include "dstools_version.h"

#warning VERIFY the version string before each distinct build submission that changes the dsconfigldap tool
const char *version = "1.2";
	
#pragma mark Prototypes

void usage( void );

void AddServer( char *inServerName, char *inConfigName, char *inComputerID, char *inUsername, char *inPassword, bool inSecureAuthOnly, bool bManInMiddle, bool bEncryptionOnly, bool bSignPackets, bool inForceBind, bool inSSL, bool inVerbose );
void RemoveServer( char *inServerName, char *inUsername, char *inPassword, bool inForceUnbind, bool inVerbose );

int GetSecurityLevel( CFDictionaryRef inDict );
int sendConfig( CFDictionaryRef configDict, CFMutableDictionaryRef *recvDict, int customCode );

bool preflightAuthorization( void );
bool doAuthorization( char *inUsername, char *inPassword );

#pragma mark Globals
#pragma mark -

//These 21 following defines are found in the DirectoryService source file CLDAPv3Configs.h and must match
#define kXMLServerKey				"Server"
#define kXMLIsSSLFlagKey			"SSL"
#define kXMLUserDefinedNameKey		"UI Name"
#define kXMLServerAccountKey		"Server Account"
#define kXMLServerPasswordKey		"Server Password"

// New Directory Binding functionality --------------
//

// kXMLBoundDirectoryKey => indicates the computer is bound to this directory.
//         This prevents them from changing:  server account, password, 
//         secure use, and port number.  It also means the config cannot  
//         be deleted, without unbinding.
#define kXMLBoundDirectoryKey			"Bound Directory"

// macosxodpolicy config Record flags
//
// These new flags are for determining config-record settings..
#define kXMLDirectoryBindingKey			"Directory Binding"

// Dictionary of keys
#define kXMLConfiguredSecurityKey		"Configured Security Level"
#define kXMLSupportedSecurityKey		"Supported Security Level"
#define kXMLLocalSecurityKey			"Local Security Level"

// Keys for above Dictionaries
#define kXMLSecurityBindingRequired		"Binding Required"
#define kXMLSecurityNoClearTextAuths	"No ClearText Authentications"
#define kXMLSecurityManInTheMiddle		"Man In The Middle"
#define kXMLSecurityPacketSigning		"Packet Signing"
#define kXMLSecurityPacketEncryption	"Packet Encryption"

// Corresponding bit flags for quick checks..
#define kSecNoSecurity				0
#define kSecCompBindingBit			1
#define kSecNoClearTextAuthBit		2
#define kSecManInMiddleBit			4
#define kSecPacketSignBit			8
#define kSecEncryptionBit			16

#define kSecurityMask				(kSecNoClearTextAuthBit | kSecManInMiddleBit | kSecPacketSignBit | kSecEncryptionBit)

//
// End New Directory Binding functionality ---------------

static AuthorizationRef		gAuthRef				= NULL;
static char					*kDirConfigAuthRight	= "system.services.directory.configure";
static char					*kNetConfigAuthRight	= "system.preferences";

#pragma mark -
#pragma mark Functions

int main(int argc, char *argv[])
{
    int				ch;
	bool			bAddServer		= false;
	bool			bRemoveServer   = false;
	bool			bForceBinding	= false;
	bool			bInteractivePwd = false;
	bool			bSSL			= false;
	bool			bVerbose		= false;
	bool			bSecureAuthOnly = false;
	bool			bDefaultUser	= false;
	bool			bManInMiddle	= false;
	bool			bEncryptionOnly	= false;
	bool			bSignPackets	= false;
	char		   *serverName		= nil;
	char		   *serverNameDupe	= nil;
	char		   *configName		= nil;
	char		   *computerID		= nil;
	char		   *userName		= nil;
	char		   *userPassword	= nil;
	char		   *localName		= nil;
	char		   *localPassword   = nil;
	
	if (argc < 2)
	{
		usage();
		exit(0);
	}
	
	if ( strcmp(argv[1], "-appleversion") == 0 )
        dsToolAppleVersionExit( argv[0] );
	
    while ((ch = getopt(argc, argv, "fvisxgema:r:n:c:u:p:l:q:h")) != -1)
	{
        switch (ch)
		{
        case 'f':
            bForceBinding = true;
            break;
        case 'v':
            bVerbose = true;
            break;
        case 'i':
            bInteractivePwd = true;
            break;
		case 's':
			bSecureAuthOnly = true;
			break;
		case 'm':
			bManInMiddle = true;
			break;
		case 'g':
			bSignPackets = true;
			break;
		case 'e':
			bEncryptionOnly = true;
			break;
		case 'x':
			bSSL = true;
			break;
        case 'a':
			bAddServer = true;
			if (serverName != nil)
			{
				serverNameDupe = serverName;
			}
            serverName = strdup(optarg);
            break;
        case 'r':
			bRemoveServer = true;
			if (serverName != nil)
			{
				serverNameDupe = serverName;
			}
            serverName = strdup(optarg);
            break;
        case 'n':
            configName = strdup(optarg);
            break;
        case 'c':
            computerID = strdup(optarg);
            break;
        case 'u':
            userName = strdup(optarg);
            break;
        case 'p':
            userPassword = strdup(optarg);
            break;
        case 'l':
            localName = strdup(optarg);
            break;
        case 'q':
            localPassword = strdup(optarg);
            break;
        case 'h':
        default:
			usage();
			exit(0);
        }
    }
	
	char *envStr = nil;
	envStr = getenv("USER");
	if ( (localName == nil) && (envStr != nil) )
	{
		bDefaultUser = true;
		localName = strdup(envStr);
	}
	envStr = nil;
	envStr = getenv("HOST");
	if ( bAddServer && (computerID == nil) && (envStr != nil) )
	{
		char *dotLocation = nil;
		computerID = strdup(envStr);
		//do not use anything past first dot ie. first token in fully qualified domain name
		if( (dotLocation = strcasestr( computerID, "." )) != NULL )
		{
			*dotLocation = '\0';
		}
	}
	envStr = nil;

	if (bVerbose)
	{
		fprintf( stdout,"dsconfigldap verbose mode\n");
		fprintf( stdout,"Options selected by user:\n");
		if (bForceBinding)
			fprintf( stdout,"Force (un)binding option selected\n");
		if (bInteractivePwd)
			fprintf( stdout,"Interactive password option selected\n");
		if (bSecureAuthOnly)
			fprintf( stdout,"Enforce Secure Authentication is enabled\n");
		if (bSSL)
			fprintf( stdout,"SSL was chosen\n");
		if (bAddServer)
			fprintf( stdout,"Add server option selected\n");
		if (bRemoveServer)
			fprintf( stdout,"Remove server option selected\n");
		if (serverName)
			fprintf( stdout,"Server name provided as <%s>\n", serverName);
		if (configName)
			fprintf( stdout,"LDAP Configuration name provided as <%s>\n", configName);
		if (computerID)
			fprintf( stdout,"Computer ID provided as <%s>\n", computerID);
		if (userName)
			fprintf( stdout,"Network username provided as <%s>\n", userName);
		if ( userPassword && !bInteractivePwd )
			fprintf( stdout,"Network user password provided as <%s>\n", userPassword);
		if (localName && !bDefaultUser)
			fprintf( stdout,"Local username provided as <%s>\n", localName);
		else if (localName)
			fprintf( stdout,"Local username determined to be <%s>\n", localName);
		else
			fprintf( stdout,"No Local username determined\n");
		if ( localPassword && !bInteractivePwd )
			fprintf( stdout,"Local user password provided as <%s>\n", localPassword);
		if( bManInMiddle )
			fprintf( stdout, "Enforce man-in-the-middle only policy if server supports it.\n" );
		if( bEncryptionOnly )
			fprintf( stdout, "Enforce packet encryption policy if server supports it.\n" );
		if( bSignPackets )
			fprintf( stdout, "Enforce packet signing policy if server supports it.\n" );
		fprintf( stdout,"\n");
	}
	
	if (bAddServer && bRemoveServer)
	{
		fprintf( stdout,"Can't add and remove at the same time.\n");
		if ( (serverName != nil) && (serverNameDupe != nil) )
			fprintf( stdout,"Two server names were given <%s> and <%s>.\n", serverName, serverNameDupe);
		usage();
	}
	else
	{
		if ( ( userName != nil) && ( ( (userPassword == nil) || bInteractivePwd ) ) )
		{
			userPassword = read_passphrase("Please enter network user password: ", 1);
			//do not verbose output this password value
		}
		
		if ( ( !bDefaultUser && ( (localPassword == nil) || bInteractivePwd ) ) || (bDefaultUser && bInteractivePwd) )
		{
			localPassword = read_passphrase("Please enter local user password: ", 1);
			//do not verbose output this password value
		}
		
		do
		{
			if( preflightAuthorization() == false )
			{
				if( doAuthorization(localName, localPassword) == false )
				{
					fprintf( stdout, "Unable to obtain auth rights to update DirectoryService LDAP configuration.\n" );
					bAddServer		= false;
					bRemoveServer   = false;
				}
			}
			
			if( bAddServer )
			{
				AddServer( serverName, configName, computerID, userName, userPassword, bSecureAuthOnly, bManInMiddle, bEncryptionOnly, bSignPackets, bForceBinding, bSSL, bVerbose );
			}

			if( bRemoveServer )
			{
				RemoveServer( serverName, userName, userPassword, bForceBinding, bVerbose );
			}
			
			break; // always leave the do-while
		} while(true);
		
	}//add or remove server correctly selected

	//cleanup variables
	//not really needed since we exit
	if (serverName)
		free(serverName);
	if (serverNameDupe)
		free(serverNameDupe);
	if (configName)
		free(configName);
	if (computerID)
		free(computerID);
	if (userName)
		free(userName);
	if (userPassword)
		free(userPassword);
	if (localName)
		free(localName);
	if (localPassword)
		free(localPassword);

	exit(0);
}

void usage( void )
{
	fprintf( stdout,
			 "dsconfigldap:: Add or remove LDAP server configurations in Directory Services\n"
			 "Version %s\n"
			 "Usage: dsconfigldap -h\n"
			 "Usage: dsconfigldap [-fvixsgem] -a servername [-n configname] [-c computerid]\n"
			 "                    [-u username] [-p userpassword] [-l localusername]\n"
			 "                    [-q localuserpassword]\n"
			 "Usage: dsconfigldap [-fvi] -r servername [-u username] [-p password]\n"
			 "                    [-l localusername] [-q localuserpassword]\n"
			 "  -f                 force the add or remove (i.e. join/un-join the existing\n"
			 "                     record)\n"
			 "  -v                 log details\n"
			 "  -i                 interactive password entry\n"
			 "  -s                 enforce not using cleartext authentication via policy\n"
			 "  -e                 enforce use of encryption capabilities via policy\n"
			 "  -m                 enforce use of man-in-middle capabilities via policy\n"
			 "  -g                 enforce use of packet signing capabilities via policy\n"
			 "  -x                 SSL connection to LDAP server\n"
			 "  -h                 display usage statement\n"
			 "  -a servername      add config of servername\n"
			 "  -r servername      remove config of servername, unbind if necessary\n"
			 "  -n configname      name to give this new server config\n"
			 "  -c computerid      name to use if when binding to directory\n"
			 "  -u username        username of a privileged network user for binding\n"
			 "  -p password        password of a privileged network user for binding\n"
			 "  -l username        username of a local administrator\n"
			 "  -q password        password of a local administrator\n\n"
			 , version );
}

#pragma mark -
#pragma mark Support Routines

void AddServer( char *inServerName, char *inConfigName, char *inComputerID, char *inUsername, char *inPassword, bool inSecureAuthOnly, bool bManInMiddle, bool bEncryptionOnly, bool bSignPackets, bool inForceBind, bool inSSL, bool inVerbose )
{
	CFStringRef				cfServerName	= CFStringCreateWithCString( kCFAllocatorDefault, inServerName, kCFStringEncodingUTF8 );
	CFMutableDictionaryRef  xmlConfig		= CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	CFMutableDictionaryRef  cfResponse		= NULL;
	int						iError			= eDSNoErr;
	
	if (inVerbose) fprintf( stdout, "Step 1 - Server Information Discovery\n" );
	
	CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerKey), cfServerName );
	
	CFBooleanRef cfBool = kCFBooleanFalse;
	if (inSSL)
	{
		cfBool = kCFBooleanTrue;
		CFDictionarySetValue(xmlConfig, CFSTR( kXMLIsSSLFlagKey ), cfBool);						
	}
	
	iError = sendConfig( xmlConfig, &cfResponse, 201 );
	
	if( iError == eDSRecordAlreadyExists )
	{
		if (inVerbose) fprintf( stdout, "   Status:  Failed - Server already in configuration.\n\n" );
	}
	else if( iError == eDSBogusServer )
	{   
		// if we get bogusServer, it was not an LDAP Server
		if (inVerbose) fprintf( stdout, "   Status:  Failed - Server did not Respond.\n\n" );
	}
	else if( iError == eDSNoErr )
	{
		if (inVerbose) fprintf( stdout, "   Status:  Success - Server Responded.\n\n" );
		
		// Let's rotate our answer back to the sender...
		CFRelease( xmlConfig );
		xmlConfig = cfResponse;
		cfResponse = NULL;
		
		if (inVerbose) fprintf( stdout, "Step 2 - Validating Record/Attribute Mapping\n" );
		iError = sendConfig( xmlConfig, &cfResponse, 202 );
		
		if( iError == eDSInvalidNativeMapping )
		{
			if (inVerbose) fprintf( stdout, "   Status:  Failed - Invalid Record/Attribute Mappings\n\n" );
		}
		// well, we can continue to 203 next
		else if( iError == eDSNoErr )
		{
			if (inVerbose) fprintf( stdout, "   Status:  Success - Valid Record/Attribute Mapping\n\n" );
			
			CFRelease( xmlConfig );
			xmlConfig = cfResponse;
			cfResponse = NULL;
			
			if (inVerbose) fprintf( stdout, "Step 3 - Detecting Required Security Levels and Binding requirements\n" );
			iError = sendConfig( xmlConfig, &cfResponse, 203 );
			
			if( iError == eDSNoErr )
			{
				if (inVerbose) fprintf( stdout, "   Status:  Success\n\n" );
				
				if( cfResponse )
				{
					CFMutableDictionaryRef  cfConfiguredSec = NULL;
					CFMutableDictionaryRef	cfLocalSecurity = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
					
					CFDictionarySetValue( cfResponse, CFSTR(kXMLLocalSecurityKey), cfLocalSecurity );
					
					CFRelease( cfLocalSecurity );
					
					cfConfiguredSec = (CFMutableDictionaryRef) CFDictionaryGetValue(cfResponse, CFSTR("Configured Security Level"));
					
					int iConfigSecurity = GetSecurityLevel( cfConfiguredSec );
					int iSecurityLevel  = GetSecurityLevel( (CFDictionaryRef) CFDictionaryGetValue(cfResponse, CFSTR("Supported Security Level")) );
					
					if( (iSecurityLevel & kSecNoClearTextAuthBit) != 0 )
					{
						if (inVerbose) fprintf( stdout, "   WARNING:  No Security Levels configured by Administrator!\n\n" );
						if (inVerbose) fprintf( stdout, "      Your LDAP server supports Secure authentication.\n\n" );
						
						if ( inSecureAuthOnly )
						{
							if (inVerbose) fprintf( stdout, "   Enforcing no cleartext password security policy.\n\n" );
							CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityNoClearTextAuths), kCFBooleanTrue );
						}
					}
					else if( inSecureAuthOnly )
					{
						fprintf( stdout, "   Unable to enforce no cleartext password security policy!\n" );
					}					
					
					if( ((iConfigSecurity & iSecurityLevel) & kSecurityMask) != (iConfigSecurity & kSecurityMask) )
					{
						if (inVerbose) fprintf( stdout, "The LDAP server you selected does not comply with Configured Security Policy.\n\n" );
						if (inVerbose) fprintf( stdout, "Add new Server cancelled\n" );
					}
					else
					{
						CFBooleanRef cfBinding = (CFBooleanRef) CFDictionaryGetValue( cfResponse, CFSTR("Directory Binding") );
						bool bBinding   = false;
						if( cfBinding )
						{
							bBinding = CFBooleanGetValue( cfBinding );
						}
						
						if( bBinding )
						{
							bool	bDoBind = false;
							
							if( (iConfigSecurity & kSecCompBindingBit) == 0 ) 
							{
								if (inVerbose) fprintf( stdout, "   Directory Binding is ENABLED but OPTIONAL.\n\n" );
								bDoBind = inForceBind;
								if (inForceBind && inVerbose)
								{
									fprintf( stdout, "   Directory Binding is being Forced as requested.\n" );
								}
							}
							else
							{
								if (inVerbose) fprintf( stdout, "   Directory Binding is ENABLED and REQUIRED.\n\n" );
                                
								bDoBind = true;
							}
							
							if( bDoBind && inUsername && inPassword )
							{
								CFRelease( xmlConfig );
								xmlConfig = cfResponse;
								cfResponse = NULL;
								
								CFStringRef cfComputerName = CFStringCreateWithCString( kCFAllocatorDefault, inComputerID, kCFStringEncodingUTF8 );
								CFDictionarySetValue( xmlConfig, CFSTR(kXMLUserDefinedNameKey), cfComputerName );
								CFRelease( cfComputerName );								
								
								CFStringRef cfUsername = CFStringCreateWithCString( kCFAllocatorDefault, inUsername, kCFStringEncodingUTF8 );
								CFStringRef cfPassword = CFStringCreateWithCString( kCFAllocatorDefault, inPassword, kCFStringEncodingUTF8 );
								
								CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerAccountKey), cfUsername );
								CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerPasswordKey), cfPassword );
								
								CFRelease( cfUsername );
								CFRelease( cfPassword );
								
								if (inVerbose) fprintf( stdout, "Step 4 - Attempting to bind computer as %s\n", inComputerID );
								
								iError = sendConfig( xmlConfig, &cfResponse, 204 );
								
								if( iError == eDSNoErr )
								{
									if (inVerbose) fprintf( stdout, "   Status:  Success.\n\n" );
								}
								else if( iError == eDSAuthFailed )
								{
									if (inVerbose) fprintf( stdout, "   Status:  Failed - Invalid credentials.\n\n" );
								}
								else if( iError == eDSRecordAlreadyExists )
								{
									if (inVerbose) fprintf( stdout, "   Status:  Failed - Computer Already Exists.\n\n" );
									
									if( inForceBind )
									{
										if (inVerbose) fprintf( stdout, "      WARNING:  If this record is in use, it will disable the other computer!\n\n" );
										iError = sendConfig( xmlConfig, &cfResponse, 205 ); //Join code call
										if( iError == eDSNoErr )
										{
											if (inVerbose) fprintf( stdout, "   Status:  Success.\n\n" );
										}
										else
										{
											if (inVerbose) fprintf( stdout, "   Status:  Failed to overwrite.\n\n" );
										}
									}
								}
								else
								{
									if (inVerbose) fprintf( stdout, "   Status:  Failed Error Code %d\n\n", iError );
								}
							}
                            else if( bDoBind ) // bind but no username/password, no need to check, was checked above
                            {
                                fprintf( stderr, "   Status:  Failed, no network credentials supplied\n\n" );
                                iError = eDSAuthFailed;
                            }
						}
						else
						{
							if (inVerbose) fprintf( stdout, "Step 4 - Directory Binding\n" );
							if (inVerbose) fprintf( stdout, "   Directory binding is not supported.\n\n" );
						}
						
						if( iError == eDSNoErr )
						{
							char	*pCurrentName = NULL;
							
							CFRelease( xmlConfig );
							xmlConfig = cfResponse;
							cfResponse = NULL;
							
							if (inVerbose) fprintf( stdout, "Step 5 - Adding server to configuration\n" );
							
							cfLocalSecurity = (CFMutableDictionaryRef) CFDictionaryGetValue( xmlConfig, CFSTR(kXMLLocalSecurityKey) );

							if( bManInMiddle && (iSecurityLevel & kSecManInMiddleBit) == kSecManInMiddleBit )
							{
								if (inVerbose) fprintf( stdout, "   Enforcing man-in-the-middle security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityManInTheMiddle), kCFBooleanTrue );
							}
							else if( bManInMiddle )
							{
								fprintf( stdout, "   Unable to enforce Man-in-the-middle security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityManInTheMiddle), kCFBooleanFalse );
							}
							
							if( bEncryptionOnly && (iSecurityLevel & kSecEncryptionBit) == kSecEncryptionBit )
							{
								if (inVerbose) fprintf( stdout, "   Enforcing encryption security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityPacketEncryption), kCFBooleanTrue );
							}
							else if( bEncryptionOnly )
							{
								fprintf( stdout, "   Unable to enforce encryption security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityPacketEncryption), kCFBooleanFalse );
							}
							
							if( bSignPackets && (iSecurityLevel & kSecPacketSignBit) == kSecPacketSignBit )
							{
								if (inVerbose) fprintf( stdout, "   Enforcing packet signing security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityPacketSigning), kCFBooleanTrue );
							}
							else if( bSignPackets )
							{
								fprintf( stdout, "   Unable to enforce packet signing security policy!\n" );
								CFDictionarySetValue( cfLocalSecurity, CFSTR(kXMLSecurityPacketSigning), kCFBooleanFalse );
							}
									
							CFStringRef cfComputerName = (CFStringRef) CFDictionaryGetValue( xmlConfig, CFSTR(kXMLUserDefinedNameKey) );
							
							if( cfComputerName )
							{
								CFIndex iLength = CFStringGetMaximumSizeForEncoding( CFStringGetLength(cfComputerName), kCFStringEncodingUTF8 ) + 1;
								pCurrentName = (char *) calloc( sizeof(char), iLength );
								CFStringGetCString( cfComputerName, pCurrentName, iLength, kCFStringEncodingUTF8 );
							}
							
							if( inConfigName == NULL )
							{
								cfComputerName = CFStringCreateWithCString( kCFAllocatorDefault, pCurrentName, kCFStringEncodingUTF8 );
							}
							else
							{
								cfComputerName = CFStringCreateWithCString( kCFAllocatorDefault, inConfigName, kCFStringEncodingUTF8 );
							}
							
							CFDictionarySetValue( xmlConfig, CFSTR(kXMLUserDefinedNameKey), cfComputerName );
							CFRelease( cfComputerName );
							
							if (pCurrentName != nil)
							{
								free(pCurrentName);
							}
							
							iError = sendConfig( xmlConfig, &cfResponse, 206 );
							if( iError == eDSNoErr )
							{
								if (inVerbose) fprintf( stdout, "   Status:  Success.\n\n" );
							}
							else
							{
								if (inVerbose) fprintf( stdout, "   Status:  Failure.\n\n" );
							}
						}
					}
				}
			}
		}
	}
	return;
}

void RemoveServer( char *inServerName, char *inUsername, char *inPassword, bool inForceUnbind, bool inVerbose )
{
	CFStringRef				cfServerName	= CFStringCreateWithCString( kCFAllocatorDefault, inServerName, kCFStringEncodingUTF8 );
	CFMutableDictionaryRef  cfResponse		= NULL;
	int						iCode			= 207;
	int						iError			= eDSNoErr;
	CFMutableDictionaryRef  xmlConfig		= CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	
	if (inVerbose) fprintf( stdout, "Attempting to remove server from configuration\n" );
	
	CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerKey), cfServerName );
	
	do
	{
		iError = sendConfig( xmlConfig, &cfResponse, iCode );
		
		// if we get bogusServer, it was not an LDAP Server
		if( iError == eDSBogusServer )
		{
			if (inVerbose) fprintf( stdout, "   Status:  Failed - Server does not exist in configuration.\n\n" );
			iError = eDSNoErr;
		}
		else if( iError == eDSOpenNodeFailed )
		{
			if (inVerbose) fprintf( stdout, "   Status:  Failed - Could not contact the LDAP server to unbind.\n\n" );
			if( inForceUnbind )
			{
				if (inVerbose) fprintf( stdout, "   Status:  Forcing unbind from server.\n\n" );
				iCode = 208;
			}
			else
			{
				iError = eDSNoErr;
			}
		}
		else if( iError == eDSRecordNotFound )
		{
			if (inVerbose) fprintf( stdout, "   Status:  Computer Record missing from Directory.  Force unbinding.\n\n" );
			iCode = 208;
		}
		else if( iError == eDSNoErr )
		{
			if ( (iCode == 207) || (iCode == 208) )
			{
				if (inVerbose) fprintf( stdout, "   Status:  Success - Unbound now if authenticated directory binding was in place.\n\n" );
				iError = eDSContinue;
				iCode = 209;
			}
			else if (iCode == 209)
			{
				if (inVerbose) fprintf( stdout, "   Status:  Success - Server removed from configuration.\n\n" );
			}
		}
		else if( iError == eDSPermissionError )
		{
			// we don't have privilege to unbind from the server, give user ability to force unbind?
			// Let's rotate our answer back to the sender...
			if (inVerbose) fprintf( stdout, "You do not have the privilege to unbind from this server.  Contact your network administrator.\n" );
			iError = eDSNoErr;
		}
		else if( iError == eDSAuthParameterError )
		{
            if( inUsername && inPassword )
            {
                if (inVerbose) fprintf( stdout, "   Status:  Computer is bound to the Directory\n" );

                CFStringRef cfUsername = CFStringCreateWithCString( kCFAllocatorDefault, inUsername, kCFStringEncodingUTF8 );
                CFStringRef cfPassword = CFStringCreateWithCString( kCFAllocatorDefault, inPassword, kCFStringEncodingUTF8 );
                
                CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerAccountKey), cfUsername );
                CFDictionarySetValue( xmlConfig, CFSTR(kXMLServerPasswordKey), cfPassword );
                
                CFRelease( cfUsername );
                CFRelease( cfPassword );
                
                if (inVerbose) fprintf( stdout, "      Attempting to unbind from server with credentials\n" );
                
                iError = sendConfig( xmlConfig, &cfResponse, iCode );
                
                if( iError == eDSNoErr )
                {
                    if (inVerbose) fprintf( stdout, "   Status:  Success.\n\n" );
                    iError = eDSContinue; // set to continue so we remove the config too
                    iCode = 209;
                }
                else if( iError == eDSAuthFailed )
                {
                    if (inVerbose) fprintf( stdout, "   Status:  Failed - Invalid credentials.\n\n" );
                    iError = eDSNoErr;
                }
            }
            else
            {
                fprintf( stderr, "   Status:  Failed - Computer is bound, network credentials not supplied\n\n" );
                break;
            }
		}
	} while (iError != eDSNoErr);
	return;	
}

#pragma mark -
#pragma mark Utility Routines

int GetSecurityLevel( CFDictionaryRef inDict )
{
	CFBooleanRef	cfBoolean;
	int				siSecurityLevel = kSecNoSecurity;
	
	if( inDict != NULL ) {
		if( CFDictionaryGetValueIfPresent(inDict, CFSTR(kXMLSecurityBindingRequired), (const void **) &cfBoolean) && CFBooleanGetValue(cfBoolean) ) {
			siSecurityLevel |= kSecCompBindingBit;
		}

		if( CFDictionaryGetValueIfPresent(inDict, CFSTR(kXMLSecurityNoClearTextAuths), (const void **) &cfBoolean) && CFBooleanGetValue(cfBoolean) ) {
			siSecurityLevel |= kSecNoClearTextAuthBit;
		}
		
		if( CFDictionaryGetValueIfPresent(inDict, CFSTR(kXMLSecurityManInTheMiddle), (const void **) &cfBoolean) && CFBooleanGetValue(cfBoolean) ) {
			siSecurityLevel |= kSecManInMiddleBit;
		}
		
		if( CFDictionaryGetValueIfPresent(inDict, CFSTR(kXMLSecurityPacketSigning), (const void **) &cfBoolean) && CFBooleanGetValue(cfBoolean) ) {
			siSecurityLevel |= kSecPacketSignBit;
		}
		
		if( CFDictionaryGetValueIfPresent(inDict, CFSTR(kXMLSecurityPacketEncryption), (const void **) &cfBoolean) && CFBooleanGetValue(cfBoolean) ) {
			siSecurityLevel |= kSecEncryptionBit;
		}
	}
	
	return siSecurityLevel;
}

int sendConfig( CFDictionaryRef configDict, CFMutableDictionaryRef *recvDict, int customCode )
{
	tDirNodeReference			nodeRef				= nil;
	tDirReference				dsRef				= nil;
	tDataList				   *dataList			= nil;
	tDataBuffer				   *sendBuff			= nil;
	tDataBuffer				   *recvBuff			= nil;
	CFDataRef					xmlData				= nil;
	int							status				= eDSNoErr;
	AuthorizationExternalForm	authRightsExtForm;
	
	try
	{
		if( geteuid() == 0 ) {
			memset( &authRightsExtForm, 0, sizeof(authRightsExtForm) );
		} else if( (status = AuthorizationMakeExternalForm(gAuthRef, &authRightsExtForm)) != noErr ) {
			throw( status );
		}
		
		if( (status = dsOpenDirService(&dsRef)) != eDSNoErr ) {
			throw( status );
		}
		
		recvBuff = dsDataBufferAllocate( dsRef, 128000 );
		dataList = dsDataListAllocate( dsRef );
		
		status = dsBuildListFromStringsAlloc( dsRef, dataList, "LDAPv3", nil );
		if (status != eDSNoErr) {
			throw( status );
		}
		
		status = dsOpenDirNode( dsRef, dataList, &nodeRef );
		if (status != eDSNoErr) {
			throw( status );
		}
		
		xmlData = (CFDataRef) CFPropertyListCreateXMLData( nil, configDict );
		
		CFIndex iLength = CFDataGetLength(xmlData);
		
		sendBuff = dsDataBufferAllocate( dsRef, sizeof(authRightsExtForm) + iLength + 1 );
		
		memcpy( sendBuff->fBufferData, &authRightsExtForm, sizeof(authRightsExtForm) );
		
		CFDataGetBytes( xmlData, CFRangeMake(0, iLength), (UInt8*)(sendBuff->fBufferData+sizeof(authRightsExtForm)) );
		
		sendBuff->fBufferLength = iLength + sizeof(authRightsExtForm);
		
		while( (status = dsDoPlugInCustomCall( nodeRef, customCode, sendBuff, recvBuff )) == eDSBufferTooSmall ) {
			int newLength = recvBuff->fBufferSize + 16384;	// let's add 16k at a time
			dsDataBufferDeAllocate( dsRef, recvBuff );
			recvBuff = dsDataBufferAllocate( dsRef, newLength );
		}
		
		if( recvBuff->fBufferLength ) {
			CFDataRef cfRecvData = CFDataCreate( kCFAllocatorDefault, (UInt8*)recvBuff->fBufferData, recvBuff->fBufferLength );
			CFMutableDictionaryRef cfDict = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData( kCFAllocatorDefault, cfRecvData, kCFPropertyListMutableContainersAndLeaves, NULL);
			*recvDict = cfDict;
			CFRelease( cfRecvData );
		}
	}
	catch( int error )
	{
		status = error;
	}
	
	if( sendBuff ) {
		dsDataBufferDeAllocate( dsRef, sendBuff );
	}
	
	if( recvBuff ) {
		dsDataBufferDeAllocate( dsRef, recvBuff );
	}
	
	if( dataList ) {
		dsDataListDeallocate( dsRef, dataList );
	}
	
	if( nodeRef ) {
		dsCloseDirNode( nodeRef );
	}
	
	if( dsRef ) {
		dsCloseDirService( dsRef );
	}
	
	return status;
}

#pragma mark -
#pragma mark Security Authorization Support Calls

bool preflightAuthorization( void )
{
    AuthorizationRights		rights;
    AuthorizationFlags		flags;
	AuthorizationRights    *authorizedRights;
    OSStatus				err			= noErr;
	bool					authorized  = false;
	
	// if we are root we don't have to authorize
	if( geteuid() == 0 )
	{
		return true;
	}
	
	AuthorizationItem rightsItems[] = { {kDirConfigAuthRight, 0, 0, 0}, {kNetConfigAuthRight, 0, 0, 0} };
	int rightCount = sizeof(rightsItems) / sizeof(AuthorizationItem);
	
    if( gAuthRef == NULL )
	{
        rights.count	= 0;
        rights.items	= NULL;
		
        flags = kAuthorizationFlagDefaults;
		
        err = AuthorizationCreate( &rights, kAuthorizationEmptyEnvironment, flags, &gAuthRef );
    }
	
    rights.count	= rightCount;
    rights.items	= rightsItems;
	
    flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize;
	
    err = AuthorizationCopyRights( gAuthRef, &rights, kAuthorizationEmptyEnvironment, flags, &authorizedRights );
	
    authorized = (err==errAuthorizationSuccess);
    if( authorized )
	{
        AuthorizationFreeItemSet( authorizedRights );
	} else
	{
		gAuthRef = NULL;
	}
    return authorized;
}

bool doAuthorization( char *inUsername, char *inPassword )
{
    AuthorizationRights		rights;
    AuthorizationRights    *authorizedRights;
    AuthorizationFlags		flags;
    OSStatus				err				= errAuthorizationDenied;
	bool					authorized		= false;
	AuthorizationItem		rightsItems[]   = { {kDirConfigAuthRight, 0, 0, 0}, {kNetConfigAuthRight, 0, 0, 0} };
	int						rightCount		= sizeof(rightsItems) / sizeof(AuthorizationItem);
	
	if ( (inUsername == nil) || (inPassword == nil) )
	{
		return (false);
	}

    if( gAuthRef == NULL )
	{
		rights.count= 0;
		rights.items = NULL;
		flags = kAuthorizationFlagDefaults;
		
		AuthorizationItem			params[]	= { {"username", strlen(inUsername), (void*)inUsername, 0},
													{"password", strlen(inPassword), (void*)inPassword, 0} };
		AuthorizationEnvironment	environment = { sizeof(params)/ sizeof(*params), params };
		
		err = AuthorizationCreate( &rights, kAuthorizationEmptyEnvironment, flags, &gAuthRef );
		
		if( err == noErr )
		{
			rights.count = rightCount;
			rights.items = rightsItems;
			
			flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize;
			
			err = AuthorizationCopyRights( gAuthRef, &rights, &environment, flags, &authorizedRights );
		}
    }
	
    if( err == errAuthorizationSuccess )
	{
		authorized = true;
        AuthorizationFreeItemSet( authorizedRights );
	}
    return authorized;
}
