keychain_find.c   [plain text]


//
//
//
//



#include <CoreFoundation/CoreFoundation.h>

#include <Security/SecItem.h>

#include <SecurityTool/tool_errors.h>
#include <SecurityTool/readline.h>

#include <utilities/SecCFWrappers.h>

#include "SecurityCommands.h"

//
// Craptastic hacks.

typedef uint32_t SecProtocolType;
typedef uint32_t SecAuthenticationType;

/* Parse a string of the form attr=value,attr=value,attr=value */
static void
keychain_query_parse_string(CFMutableDictionaryRef q, CFStringRef s) {
    bool inkey = true;
    bool escaped = false;
    CFStringRef key = NULL;
    CFMutableStringRef str = CFStringCreateMutable(0, 0);
    CFRange rng = { .location = 0, .length = CFStringGetLength(s) };
    CFCharacterSetRef cs_key = CFCharacterSetCreateWithCharactersInString(0, CFSTR("=\\"));
    CFCharacterSetRef cs_value = CFCharacterSetCreateWithCharactersInString(0, CFSTR(",\\"));
    while (rng.length) {
        CFRange r;
        CFStringRef sub;
        bool complete = false;
        if (escaped) {
            r.location = rng.location;
            r.length = 1;
            sub = CFStringCreateWithSubstring(0, s, r);
            escaped = false;
        } else if (CFStringFindCharacterFromSet(s, inkey ? cs_key : cs_value, rng, 0, &r)) {
            if (CFStringGetCharacterAtIndex(s, r.location) == '\\') {
                escaped = true;
            } else {
                complete = true;
            }
            CFIndex next = r.location + 1;
            r.length = r.location - rng.location;
            r.location = rng.location;
            sub = CFStringCreateWithSubstring(0, s, r);
            rng.length -= next - rng.location;
            rng.location = next;
        } else {
            sub = CFStringCreateWithSubstring(0, s, rng);
            rng.location += rng.length;
            rng.length = 0;
            complete = true;
        }
        CFStringAppend(str, sub);
        CFRelease(sub);
        if (complete) {
            CFStringRef value = CFStringCreateCopy(0, str);
            CFStringReplaceAll(str, CFSTR(""));
            if (inkey) {
                key = value;
            } else {
                CFDictionarySetValue(q, key, value);
                CFReleaseNull(value);
                CFReleaseNull(key);
            }
            inkey = !inkey;
        }
    }
    if (key) {
        /* Dangeling key value is true?. */
        CFDictionarySetValue(q, key, kCFBooleanTrue);
        CFRelease(key);
    }
    
    CFRelease(str);
    CFReleaseSafe(cs_key);
    CFReleaseSafe(cs_value);
}

static void
keychain_query_parse_cstring(CFMutableDictionaryRef q, const char *query) {
    CFStringRef s;
    s = CFStringCreateWithCStringNoCopy(0, query, kCFStringEncodingUTF8, kCFAllocatorNull);
    keychain_query_parse_string(q, s);
    CFRelease(s);
}

static CFMutableDictionaryRef
keychain_create_query_from_string(const char *query) {
    CFMutableDictionaryRef q;

    q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    keychain_query_parse_cstring(q, query);
    return q;
}

static void add_key(const void *key, const void *value, void *context) {
    CFArrayAppendValue(context, key);
}

static void display_item(const void *v_item, void *context) {
    CFDictionaryRef item = (CFDictionaryRef)v_item;
    CFIndex dict_count, key_ix, key_count;
    CFMutableArrayRef keys = NULL;
    CFIndex maxWidth = 10; /* Maybe precompute this or grab from context? */

    dict_count = CFDictionaryGetCount(item);
    keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count,
        &kCFTypeArrayCallBacks);
    CFDictionaryApplyFunction(item, add_key, keys);
    key_count = CFArrayGetCount(keys);
    CFArraySortValues(keys, CFRangeMake(0, key_count),
        (CFComparatorFunction)CFStringCompare, 0);

    for (key_ix = 0; key_ix < key_count; ++key_ix) {
        CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix);
        CFTypeRef value = CFDictionaryGetValue(item, key);
        CFMutableStringRef line = CFStringCreateMutable(NULL, 0);

        CFStringAppend(line, key);
        CFIndex jx;
        for (jx = CFStringGetLength(key);
            jx < maxWidth; ++jx) {
            CFStringAppend(line, CFSTR(" "));
        }
        CFStringAppend(line, CFSTR(" : "));
        if (CFStringGetTypeID() == CFGetTypeID(value)) {
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFNumberGetTypeID() == CFGetTypeID(value)) {
            CFNumberRef v_n = (CFNumberRef)value;
            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_n);
        } else if (CFDateGetTypeID() == CFGetTypeID(value)) {
            CFDateRef v_d = (CFDateRef)value;
            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_d);
        } else if (CFDataGetTypeID() == CFGetTypeID(value)) {
            CFDataRef v_d = (CFDataRef)value;
            CFStringRef v_s = CFStringCreateFromExternalRepresentation(
                kCFAllocatorDefault, v_d, kCFStringEncodingUTF8);
            if (v_s) {
                CFStringAppend(line, CFSTR("/"));
                CFStringAppend(line, v_s);
                CFStringAppend(line, CFSTR("/ "));
                CFRelease(v_s);
            }
            const uint8_t *bytes = CFDataGetBytePtr(v_d);
            CFIndex len = CFDataGetLength(v_d);
            for (jx = 0; jx < len; ++jx) {
                CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]);
            }
        } else {
            CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
        }

        CFStringWriteToFileWithNewline(line, stdout);

		CFRelease(line);
    }
    CFRelease(keys);
    
    CFStringWriteToFileWithNewline(CFSTR("===="), stdout);

    //CFShow(item);
}


static void display_results(CFTypeRef results) {
    if (CFGetTypeID(results) == CFArrayGetTypeID()) {
        CFArrayRef r_a = (CFArrayRef)results;
        CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
            display_item, NULL);
    } else if (CFGetTypeID(results) == CFArrayGetTypeID()) {
        display_item(results, NULL);
    } else {
        fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
        CFShow(results);
    }
}

static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
    OSStatus result;
    if (do_delete) {
        result = SecItemDelete(query);
        if (result) {
            sec_perror("SecItemDelete", result);
        }
    } else {
        CFTypeRef results = NULL;
        result = SecItemCopyMatching(query, &results);
        if (result) {
            sec_perror("SecItemCopyMatching", result);
        } else {
            display_results(results);
        }
        CFReleaseSafe(results);
    }
    return result;
}

static int
do_keychain_find_or_delete_internet_password(Boolean do_delete,
	const char *serverName, const char *securityDomain,
	const char *accountName, const char *path, UInt16 port,
	SecProtocolType protocol, SecAuthenticationType authenticationType,
	Boolean get_password)
 {
	OSStatus result;
    CFDictionaryRef query = NULL;
    const void *keys[11], *values[11];
    CFIndex ix = 0;

    keys[ix] = kSecClass;
    values[ix++] = kSecClassInternetPassword;
	if (serverName) {
		keys[ix] = kSecAttrServer;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
	if (securityDomain) {
		keys[ix] = kSecAttrSecurityDomain;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
	if (accountName) {
		keys[ix] = kSecAttrAccount;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
	if (path) {
		keys[ix] = kSecAttrPath;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
	if (port != 0) {
		keys[ix] = kSecAttrPort;
		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
	}
	if (protocol != 0) {
		/* Protocol is a 4 char code, perhaps we should use a string rep
		   instead. */
		keys[ix] = kSecAttrProtocol;
		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
	}
	if (authenticationType != 0) {
		keys[ix] = kSecAttrAuthenticationType;
		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
			&authenticationType);
	}
    if (get_password) {
        /* Only ask for the data if so required. */
		keys[ix] = kSecReturnData;
		values[ix++] = kCFBooleanTrue;
    }
    keys[ix] = kSecReturnAttributes;
    values[ix++] = kCFBooleanTrue;
	if (!do_delete) {
		/* If we aren't deleting ask for all items. */
		keys[ix] = kSecMatchLimit;
		values[ix++] = kSecMatchLimitAll;
	}

    query = CFDictionaryCreate(NULL, keys, values, ix,
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    result = do_find_or_delete(query, do_delete);
    CFReleaseSafe(query);

	return result;
}

static int
parse_fourcharcode(const char *name, uint32_t *code)
{
	/* @@@ Check for errors. */
	char *p = (char *)code;
	strncpy(p, name, 4);
	return 0;
}

static int
keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
{
	char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
    UInt16 port = 0;
    SecProtocolType protocol = 0;
    SecAuthenticationType authenticationType = 0;
	int ch, result = 0;
	Boolean get_password = FALSE;

	while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
	{
		switch  (ch)
		{
        case 'a':
            accountName = optarg;
            break;
        case 'd':
            securityDomain = optarg;
			break;
		case 'g':
            if (do_delete)
                return 2;
			get_password = TRUE;
			break;
        case 'p':
            path = optarg;
            break;
        case 'P':
            port = atoi(optarg);
            break;
        case 'r':
			result = parse_fourcharcode(optarg, &protocol);
			if (result)
				goto loser;
			break;
		case 's':
			serverName = optarg;
			break;
        case 't':
			result = parse_fourcharcode(optarg, &authenticationType);
			if (result)
				goto loser;
			break;
        case '?':
		default:
			return 2; /* @@@ Return 2 triggers usage message. */
		}
	}
  
	argc -= optind;
	argv += optind;

	result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
		accountName, path, port, protocol,authenticationType, get_password);


loser:

	return result;
}

int
keychain_find_internet_password(int argc, char * const *argv) {
    return keychain_find_or_delete_internet_password(0, argc, argv);
}

int
keychain_delete_internet_password(int argc, char * const *argv) {
    return keychain_find_or_delete_internet_password(1, argc, argv);
}

static int
do_keychain_find_or_delete_generic_password(Boolean do_delete,
	const char *serviceName, const char *accountName,
	Boolean get_password)
 {
	OSStatus result;
    CFDictionaryRef query = NULL;
    const void *keys[6], *values[6];
    CFIndex ix = 0;

    keys[ix] = kSecClass;
    values[ix++] = kSecClassGenericPassword;
	if (serviceName) {
		keys[ix] = kSecAttrService;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
	if (accountName) {
		keys[ix] = kSecAttrAccount;
		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
			kCFStringEncodingUTF8, kCFAllocatorNull);
	}
    if (get_password) {
        /* Only ask for the data if so required. */
		keys[ix] = kSecReturnData;
		values[ix++] = kCFBooleanTrue;
    }
    keys[ix] = kSecReturnAttributes;
    values[ix++] = kCFBooleanTrue;
	if (!do_delete) {
		/* If we aren't deleting ask for all items. */
		keys[ix] = kSecMatchLimit;
		values[ix++] = kSecMatchLimitAll;
	}

    query = CFDictionaryCreate(NULL, keys, values, ix,
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    result = do_find_or_delete(query, do_delete);

	CFReleaseSafe(query);

	return result;
}

int keychain_item(int argc, char * const *argv) {
	int ch, result = 0;
    CFMutableDictionaryRef query, update = NULL;
	bool get_password = false;
    bool do_delete = false;
    bool do_add = false;
    bool verbose = false;
    int limit = 0;

    query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	while ((ch = getopt(argc, argv, "ad:Df:gq:u:vl:")) != -1)
	{
		switch  (ch)
		{
            case 'a':
                do_add = true;
                break;
            case 'D':
                do_delete = true;
                break;
            case 'd':
            {
                CFStringRef data = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
                if (data) {
                    CFDictionarySetValue(update ? update : query, kSecValueData, data);
                    CFRelease(data);
                } else {
                    result = 1;
                    goto out;
                }
                break;
            }
            case 'f':
            {
                CFDataRef data = copyFileContents(optarg);
                CFDictionarySetValue(update ? update : query, kSecValueData, data);
                CFRelease(data);
                break;
            }
            case 'g':
                get_password = true;
                break;
            case 'q':
                keychain_query_parse_cstring(query, optarg);
                break;
            case 'u':
                if (!update)
                    update = keychain_create_query_from_string(optarg);
                else
                    keychain_query_parse_cstring(update, optarg);
                break;
            case 'v':
                verbose = true;
                break;
            case 'l':
                limit = atoi(optarg);
                break;
            case '?':
            default:
                /* Return 2 triggers usage message. */
                result = 2;
                goto out;
		}
	}

    if (((do_add || do_delete) && (get_password || update)) || !query) {
        result = 2;
        goto out;
    }

	argc -= optind;
	argv += optind;

    int ix;
    for (ix = 0; ix < argc; ++ix) {
        keychain_query_parse_cstring(query, argv[ix]);
    }

    if (!update && !do_add && !do_delete) {
        CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
        if(limit) {
            CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
            CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
            CFReleaseSafe(cfLimit);
        } else {
            CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
        }
        if (get_password)
            CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
    }

    if (verbose)
        CFShow(query);

    OSStatus error;
    if (do_add) {
        error = SecItemAdd(query, NULL);
        if (error) {
            sec_perror("SecItemAdd", error);
            result = 1;
        }
    } else if (update) {
        error = SecItemUpdate(query, update);
        if (error) {
            sec_perror("SecItemUpdate", error);
            result = 1;
        }
    } else if (do_delete) {
        error = SecItemDelete(query);
        if (error) {
            sec_perror("SecItemDelete", error);
            result = 1;
        }
    } else {
        do_find_or_delete(query, do_delete);
    }

out:
    CFReleaseSafe(query);
    CFReleaseSafe(update);
	return result;
}

static int
keychain_find_or_delete_generic_password(Boolean do_delete,
	int argc, char * const *argv)
{
	char *serviceName = NULL, *accountName = NULL;
	int ch, result = 0;
	Boolean get_password = FALSE;

	while ((ch = getopt(argc, argv, "a:s:g")) != -1)
	{
		switch  (ch)
		{
        case 'a':
            accountName = optarg;
            break;
        case 'g':
            if (do_delete)
                return 2;
			get_password = TRUE;
			break;
		case 's':
			serviceName = optarg;
			break;
        case '?':
		default:
			return 2; /* @@@ Return 2 triggers usage message. */
		}
	}
  
	argc -= optind;
	argv += optind;

	result = do_keychain_find_or_delete_generic_password(do_delete,
		serviceName, accountName, get_password);

	return result;
}

int
keychain_find_generic_password(int argc, char * const *argv) {
    return keychain_find_or_delete_generic_password(0, argc, argv);
}

int
keychain_delete_generic_password(int argc, char * const *argv) {
    return keychain_find_or_delete_generic_password(1, argc, argv);
}