/*******************************************************************************
 * Copyright (c) 2008 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

#include "jni.h"
#include "keystoreNative.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <CoreServices/CoreServices.h>

/**
 * Implements the get password functionality.
 */ 
jstring getPassword(JNIEnv *env, jobject this, jstring serviceName, jstring accountName) {
	OSStatus status;
	UInt32 passwordLength = (UInt32) nil;
	void *passwordData = nil;
	const char *serviceNameUTF = (*env)->GetStringUTFChars(env, serviceName, NULL);
	const char *accountNameUTF = (*env)->GetStringUTFChars(env, accountName, NULL);

	status = SecKeychainFindGenericPassword (NULL,
			(*env)->GetStringUTFLength(env, serviceName), serviceNameUTF,
			(*env)->GetStringUTFLength(env, accountName), accountNameUTF,
			&passwordLength, &passwordData,
			NULL
	);
	
	// free the UTF strings
	(*env)->ReleaseStringUTFChars( env, serviceName, serviceNameUTF );
	(*env)->ReleaseStringUTFChars( env, accountName, accountNameUTF );

	// throw an exception if we have an error
	if (status != noErr) {
		(*env)->ExceptionClear(env);
		char buffer [60];
		sprintf(buffer, "Could not obtain password.  Result: %d", (int) status);
		(*env)->ThrowNew(env, (* env)->FindClass(env, "java/lang/SecurityException"), buffer);
	}

	// massage the string into a Java-friendly UTF-8 string
	char *truncatedPassword = (char *) malloc(passwordLength * sizeof(char) + 1);
	strncpy(truncatedPassword, passwordData, passwordLength * sizeof(char));
	truncatedPassword[passwordLength * sizeof(char)] = '\0';
	jstring result = (*env)->NewStringUTF(env, truncatedPassword);
	free(truncatedPassword);
	// free the returned password data
	SecKeychainItemFreeContent (NULL, passwordData);
	return result;
}

JNIEXPORT jstring JNICALL Java_org_eclipse_equinox_internal_security_osx_OSXProvider_getPassword(JNIEnv *env, jobject this, jstring serviceName, jstring accountName) {
	return getPassword(env, this, serviceName, accountName);
}

/**
 * Implements the set password functionality.
 */ 
void setPassword(JNIEnv *env, jobject this, jstring serviceName, jstring accountName, jstring password) {
	OSStatus status;
	const char *serviceNameUTF = (*env)->GetStringUTFChars(env, serviceName, NULL);
	const char *accountNameUTF = (*env)->GetStringUTFChars(env, accountName, NULL);
	const char *passwordUTF = (*env)->GetStringUTFChars(env, password, NULL);
	
	// attempt to add the password
	status = SecKeychainAddGenericPassword (NULL,
		(*env)->GetStringUTFLength(env, serviceName), serviceNameUTF,
		(*env)->GetStringUTFLength(env, accountName), accountNameUTF,
		(*env)->GetStringUTFLength(env, password), passwordUTF, 
		NULL);

	// it already exists, try to change it
	if (status == errSecDuplicateItem) {
		SecKeychainItemRef itemRef = (SecKeychainItemRef) nil;
	    
		// find the ItemRef corresponding to the item
		status = SecKeychainFindGenericPassword (NULL,
		(*env)->GetStringUTFLength(env, serviceName), serviceNameUTF,
		(*env)->GetStringUTFLength(env, accountName), accountNameUTF,
		NULL, NULL,
		&itemRef
		);
		
		// this should rarely happen - we're in this state because it exists.
		if (status != noErr) {
			// free the UTF strings
			(*env)->ReleaseStringUTFChars( env, serviceName, serviceNameUTF );
			(*env)->ReleaseStringUTFChars( env, accountName, accountNameUTF );
			(*env)->ReleaseStringUTFChars( env, password, passwordUTF );
			// release the pointer to the item
			// the following code craps out for some unknown reason when called from within Eclipse.  It works fine in a standalone app, however.
			//if (itemRef) CFRelease(itemRef);

			(*env)->ExceptionClear(env);
			char buffer [60];
			sprintf(buffer, "Could not obtain password.  Result: %d", (int) status);
			(*env)->ThrowNew(env, (* env)->FindClass(env, "java/lang/SecurityException"), buffer);
		}

		// modify the data based on the item we've gotten above
		status = SecKeychainItemModifyAttributesAndData (itemRef, NULL, (*env)->GetStringUTFLength(env, password), passwordUTF);

		// free the UTF strings
		(*env)->ReleaseStringUTFChars( env, serviceName, serviceNameUTF );
		(*env)->ReleaseStringUTFChars( env, accountName, accountNameUTF );
		(*env)->ReleaseStringUTFChars( env, password, passwordUTF );
		
		// release the pointer to the item
		// the following code craps out for some unknown reason when called from within Eclipse.  It works fine in a standalone app, however.
		//if (itemRef) CFRelease(itemRef);

		// throw an exception if it didnt work
		if (status != noErr) {
			(*env)->ExceptionClear(env);
			char buffer [60];
			sprintf(buffer, "Could change password.  Result: %d", (int) status);
			(*env)->ThrowNew(env, (* env)->FindClass(env, "java/lang/SecurityException"), buffer);
		}

	}
}

JNIEXPORT void JNICALL Java_org_eclipse_equinox_internal_security_osx_OSXProvider_setPassword(JNIEnv *env, jobject this, jstring serviceName, jstring accountName, jstring password) {
	setPassword(env, this, serviceName, accountName, password);
}

