//
//  $Id$
//
//  SPKeychain.m
//  sequel-pro
//
//  Created by lorenz textor (lorenz@textor.ch) on Wed Dec 25 2002.
//  Copyright (c) 2002-2003 Lorenz Textor. All rights reserved.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//  More info at <http://code.google.com/p/sequel-pro/>

#import "SPKeychain.h"

#import <CoreFoundation/CoreFoundation.h>
#import <Security/Security.h>

@implementation SPKeychain

/**
 * Add the supplied password to the user's Keychain using the supplied name and account.
 */
- (void)addPassword:(NSString *)password forName:(NSString *)name account:(NSString *)account
{
	[self addPassword:password forName:name account:account withLabel:name];
}

/**
 * Add the supplied password to the user's Keychain using the supplied name, account, and label.
 */
- (void)addPassword:(NSString *)password forName:(NSString *)name account:(NSString *)account withLabel:(NSString *)label;
{
	OSStatus status;
	SecTrustedApplicationRef sequelProRef, sequelProHelperRef;
	SecAccessRef passwordAccessRef;
	SecKeychainAttribute attributes[4];
	SecKeychainAttributeList attList;
	
	// Check if password already exists before adding
	if (![self passwordExistsForName:name account:account]) {

		// Create a trusted access list with two items - ourselves and the SSH pass app.
		NSString *helperPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"SequelProTunnelAssistant"];
		if ((SecTrustedApplicationCreateFromPath(NULL, &sequelProRef) == noErr) &&
			(SecTrustedApplicationCreateFromPath([helperPath UTF8String], &sequelProHelperRef) == noErr)) {

			NSArray *trustedApps = [NSArray arrayWithObjects:(id)sequelProRef, (id)sequelProHelperRef, nil];
			status = SecAccessCreate((CFStringRef)name, (CFArrayRef)trustedApps, &passwordAccessRef);
			if (status != noErr) {
				NSLog(@"Error (%i) while trying to create access list for name: %@ account: %@", status, name, account);
				passwordAccessRef = NULL;
			}
		}
		
		// Set up the item attributes
		attributes[0].tag = kSecGenericItemAttr;
		attributes[0].data = "application password";
		attributes[0].length = 20;
		attributes[1].tag = kSecLabelItemAttr;
		attributes[1].data = (unichar *)[label UTF8String];
		attributes[1].length = strlen([label UTF8String]);
		attributes[2].tag = kSecAccountItemAttr;
		attributes[2].data = (unichar *)[account UTF8String];
		attributes[2].length = strlen([account UTF8String]);
		attributes[3].tag = kSecServiceItemAttr;
		attributes[3].data = (unichar *)[name UTF8String];
		attributes[3].length = strlen([name UTF8String]);
		attList.count = 4;
		attList.attr = attributes;

		// Create the keychain item
		status = SecKeychainItemCreateFromContent(
			kSecGenericPasswordItemClass,			// Generic password type
			&attList,								// The attribute list created for the keychain item
			strlen([password UTF8String]),			// Length of password
			[password UTF8String],					// Password data
			NULL,									// Default keychain
			passwordAccessRef,						// Access list for this keychain
			NULL);									// The item reference

		if (passwordAccessRef) CFRelease(passwordAccessRef);
		if (status != noErr) {
			NSLog(@"Error (%i) while trying to add password for name: %@ account: %@", status, name, account);
		}
	}
}

/**
 * Get a password from the user's Keychain for the supplied name and account.
 */
- (NSString *)getPasswordForName:(NSString *)name account:(NSString *)account
{
	OSStatus status;
	
	void *passwordData;
	UInt32 passwordLength;
	SecKeychainItemRef itemRef;
	NSString *password = @"";
	
	status = SecKeychainFindGenericPassword(
											NULL,						// default keychain
											strlen([name UTF8String]),		// length of service name (bytes)
											[name UTF8String],				// service name
											strlen([account UTF8String]),	// length of account name (bytes)
											[account UTF8String],			// account name
											&passwordLength,			// length of password
											&passwordData,				// pointer to password data
											&itemRef					// the item reference
											);
	
	if (status == noErr) {
		password = [NSString stringWithCString:passwordData length:passwordLength];
		
		// Free the data allocated by SecKeychainFindGenericPassword:
		SecKeychainItemFreeContent(
									NULL,           // No attribute data to release
									passwordData    // Release data
									);
	}

	return password;
}

/**
 * Delete a password from the user's Keychain for the supplied name and account.
 */
- (void)deletePasswordForName:(NSString *)name account:(NSString *)account
{
	OSStatus status;
	SecKeychainItemRef itemRef = nil;

	// Check if password already exists before deleting
	if ([self passwordExistsForName:name account:account]) {
		status = SecKeychainFindGenericPassword(
												NULL,						// default keychain
												strlen([name UTF8String]),		// length of service name
												[name UTF8String],				// service name
												strlen([account UTF8String]),	// length of account name
												[account UTF8String],			// account name
												nil,						// length of password
												nil,						// pointer to password data
												&itemRef					// the item reference
												);
		
		if (status == noErr) {
			status = SecKeychainItemDelete(itemRef);
			
			if (status != noErr) {
				NSLog(@"Error (%i) while trying to delete password for name: %@ account: %@", status, name, account);
			}
		}
		
		if (itemRef) CFRelease(itemRef);
	}
}

/**
 * Checks the user's Keychain to see if a password for the supplied name and account exists.
 */
- (BOOL)passwordExistsForName:(NSString *)name account:(NSString *)account
{
	SecKeychainItemRef item;
	SecKeychainSearchRef search = NULL;
    int numberOfItemsFound = 0;
	SecKeychainAttributeList list;
	SecKeychainAttribute attributes[2];
	
	attributes[0].tag    = kSecAccountItemAttr;
	attributes[0].data   = (void *)[account UTF8String];	// Account name
	attributes[0].length = strlen([account UTF8String]);	// Length of account name (bytes)
	
	attributes[1].tag    = kSecServiceItemAttr;
    attributes[1].data   = (void *)[name UTF8String];	// Service name
    attributes[1].length = strlen([name UTF8String]);	// Length of service name (bytes)
	
    list.count = 2;
    list.attr  = attributes;
	
    if (SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search) == noErr) {
		while (SecKeychainSearchCopyNext(search, &item) == noErr) {
			CFRelease(item);
			numberOfItemsFound++;
		}
	}
	
    if (search) CFRelease(search);
	
	return (numberOfItemsFound > 0);
}

/**
 * Retrieve the keychain item name for a supplied name and id.
 */
- (NSString *)nameForFavoriteName:(NSString *)theName id:(NSString *)theID
{
	NSString *keychainItemName;

	keychainItemName = [NSString stringWithFormat:@"Sequel Pro : %@ (%i)",
							theName,
							[theID intValue]];

	return keychainItemName;
}

/**
 * Retrieve the keychain item account for a supplied user, host, and database - which can be nil.
 */
- (NSString *)accountForUser:(NSString *)theUser host:(NSString *)theHost database:(NSString *)theDatabase
{
	NSString *keychainItemAccount;

	keychainItemAccount = [NSString stringWithFormat:@"%@@%@/%@",
								theUser?theUser:@"",
								theHost?theHost:@"",
								theDatabase?theDatabase:@""];

	return keychainItemAccount;
}

/**
 * Retrieve the keychain SSH item name for a supplied name and id.
 */
- (NSString *)nameForSSHForFavoriteName:(NSString *)theName id:(NSString *)theID
{
	NSString *sshKeychainItemName;

	sshKeychainItemName = [NSString stringWithFormat:@"Sequel Pro SSHTunnel : %@ (%i)",
							theName,
							[theID intValue]];

	return sshKeychainItemName;
}

/**
 * Retrieve the keychain SSH item account for a supplied SSH user and host - which can be nil.
 */
- (NSString *)accountForSSHUser:(NSString *)theSSHUser sshHost:(NSString *)theSSHHost
{
	NSString *sshKeychainItemAccount;

	sshKeychainItemAccount = [NSString stringWithFormat:@"%@@%@",
								theSSHUser?theSSHUser:@"",
								theSSHHost?theSSHHost:@""];

	return sshKeychainItemAccount;
}

@end