1//
2//
3//
4//
5
6
7
8#include <CoreFoundation/CoreFoundation.h>
9
10#include <Security/SecItem.h>
11
12#include <SecurityTool/tool_errors.h>
13#include <SecurityTool/readline.h>
14
15#include <utilities/SecCFWrappers.h>
16
17#include "SecurityCommands.h"
18
19//
20// Craptastic hacks.
21
22typedef uint32_t SecProtocolType;
23typedef uint32_t SecAuthenticationType;
24
25/* Parse a string of the form attr=value,attr=value,attr=value */
26static void
27keychain_query_parse_string(CFMutableDictionaryRef q, CFStringRef s) {
28    bool inkey = true;
29    bool escaped = false;
30    CFStringRef key = NULL;
31    CFMutableStringRef str = CFStringCreateMutable(0, 0);
32    CFRange rng = { .location = 0, .length = CFStringGetLength(s) };
33    CFCharacterSetRef cs_key = CFCharacterSetCreateWithCharactersInString(0, CFSTR("=\\"));
34    CFCharacterSetRef cs_value = CFCharacterSetCreateWithCharactersInString(0, CFSTR(",\\"));
35    while (rng.length) {
36        CFRange r;
37        CFStringRef sub;
38        bool complete = false;
39        if (escaped) {
40            r.location = rng.location;
41            r.length = 1;
42            sub = CFStringCreateWithSubstring(0, s, r);
43            escaped = false;
44        } else if (CFStringFindCharacterFromSet(s, inkey ? cs_key : cs_value, rng, 0, &r)) {
45            if (CFStringGetCharacterAtIndex(s, r.location) == '\\') {
46                escaped = true;
47            } else {
48                complete = true;
49            }
50            CFIndex next = r.location + 1;
51            r.length = r.location - rng.location;
52            r.location = rng.location;
53            sub = CFStringCreateWithSubstring(0, s, r);
54            rng.length -= next - rng.location;
55            rng.location = next;
56        } else {
57            sub = CFStringCreateWithSubstring(0, s, rng);
58            rng.location += rng.length;
59            rng.length = 0;
60            complete = true;
61        }
62        CFStringAppend(str, sub);
63        CFRelease(sub);
64        if (complete) {
65            CFStringRef value = CFStringCreateCopy(0, str);
66            CFStringReplaceAll(str, CFSTR(""));
67            if (inkey) {
68                key = value;
69            } else {
70                CFDictionarySetValue(q, key, value);
71                CFReleaseNull(value);
72                CFReleaseNull(key);
73            }
74            inkey = !inkey;
75        }
76    }
77    if (key) {
78        /* Dangeling key value is true?. */
79        CFDictionarySetValue(q, key, kCFBooleanTrue);
80        CFRelease(key);
81    }
82
83    CFRelease(str);
84    CFReleaseSafe(cs_key);
85    CFReleaseSafe(cs_value);
86}
87
88static void
89keychain_query_parse_cstring(CFMutableDictionaryRef q, const char *query) {
90    CFStringRef s;
91    s = CFStringCreateWithCStringNoCopy(0, query, kCFStringEncodingUTF8, kCFAllocatorNull);
92    keychain_query_parse_string(q, s);
93    CFRelease(s);
94}
95
96static CFMutableDictionaryRef
97keychain_create_query_from_string(const char *query) {
98    CFMutableDictionaryRef q;
99
100    q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
101    keychain_query_parse_cstring(q, query);
102    return q;
103}
104
105static void add_key(const void *key, const void *value, void *context) {
106    CFArrayAppendValue(context, key);
107}
108
109static void display_item(const void *v_item, void *context) {
110    CFDictionaryRef item = (CFDictionaryRef)v_item;
111    CFIndex dict_count, key_ix, key_count;
112    CFMutableArrayRef keys = NULL;
113    CFIndex maxWidth = 10; /* Maybe precompute this or grab from context? */
114
115    dict_count = CFDictionaryGetCount(item);
116    keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count,
117        &kCFTypeArrayCallBacks);
118    CFDictionaryApplyFunction(item, add_key, keys);
119    key_count = CFArrayGetCount(keys);
120    CFArraySortValues(keys, CFRangeMake(0, key_count),
121        (CFComparatorFunction)CFStringCompare, 0);
122
123    for (key_ix = 0; key_ix < key_count; ++key_ix) {
124        CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix);
125        CFTypeRef value = CFDictionaryGetValue(item, key);
126        CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
127
128        CFStringAppend(line, key);
129        CFIndex jx;
130        for (jx = CFStringGetLength(key);
131            jx < maxWidth; ++jx) {
132            CFStringAppend(line, CFSTR(" "));
133        }
134        CFStringAppend(line, CFSTR(" : "));
135        if (CFStringGetTypeID() == CFGetTypeID(value)) {
136            CFStringAppend(line, (CFStringRef)value);
137        } else if (CFNumberGetTypeID() == CFGetTypeID(value)) {
138            CFNumberRef v_n = (CFNumberRef)value;
139            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_n);
140        } else if (CFDateGetTypeID() == CFGetTypeID(value)) {
141            CFDateRef v_d = (CFDateRef)value;
142            CFStringAppendFormat(line, NULL, CFSTR("%@"), v_d);
143        } else if (CFDataGetTypeID() == CFGetTypeID(value)) {
144            CFDataRef v_d = (CFDataRef)value;
145            CFStringRef v_s = CFStringCreateFromExternalRepresentation(
146                kCFAllocatorDefault, v_d, kCFStringEncodingUTF8);
147            if (v_s) {
148                CFStringAppend(line, CFSTR("/"));
149                CFStringAppend(line, v_s);
150                CFStringAppend(line, CFSTR("/ "));
151                CFRelease(v_s);
152            }
153            const uint8_t *bytes = CFDataGetBytePtr(v_d);
154            CFIndex len = CFDataGetLength(v_d);
155            for (jx = 0; jx < len; ++jx) {
156                CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]);
157            }
158        } else {
159            CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
160        }
161
162        CFStringWriteToFileWithNewline(line, stdout);
163
164		CFRelease(line);
165    }
166    CFRelease(keys);
167
168    CFStringWriteToFileWithNewline(CFSTR("===="), stdout);
169
170    //CFShow(item);
171}
172
173
174static void display_results(CFTypeRef results) {
175    if (CFGetTypeID(results) == CFArrayGetTypeID()) {
176        CFArrayRef r_a = (CFArrayRef)results;
177        CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
178            display_item, NULL);
179    } else if (CFGetTypeID(results) == CFArrayGetTypeID()) {
180        display_item(results, NULL);
181    } else {
182        fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
183        CFShow(results);
184    }
185}
186
187static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
188    OSStatus result;
189    if (do_delete) {
190        result = SecItemDelete(query);
191        if (result) {
192            sec_perror("SecItemDelete", result);
193        }
194    } else {
195        CFTypeRef results = NULL;
196        result = SecItemCopyMatching(query, &results);
197        if (result) {
198            sec_perror("SecItemCopyMatching", result);
199        } else {
200            display_results(results);
201        }
202        CFReleaseSafe(results);
203    }
204    return result;
205}
206
207static int
208do_keychain_find_or_delete_internet_password(Boolean do_delete,
209	const char *serverName, const char *securityDomain,
210	const char *accountName, const char *path, UInt16 port,
211	SecProtocolType protocol, SecAuthenticationType authenticationType,
212	Boolean get_password)
213 {
214	OSStatus result;
215    CFDictionaryRef query = NULL;
216    const void *keys[11], *values[11];
217    CFIndex ix = 0;
218
219    keys[ix] = kSecClass;
220    values[ix++] = kSecClassInternetPassword;
221	if (serverName) {
222		keys[ix] = kSecAttrServer;
223		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
224			kCFStringEncodingUTF8, kCFAllocatorNull);
225	}
226	if (securityDomain) {
227		keys[ix] = kSecAttrSecurityDomain;
228		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
229			kCFStringEncodingUTF8, kCFAllocatorNull);
230	}
231	if (accountName) {
232		keys[ix] = kSecAttrAccount;
233		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
234			kCFStringEncodingUTF8, kCFAllocatorNull);
235	}
236	if (path) {
237		keys[ix] = kSecAttrPath;
238		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
239			kCFStringEncodingUTF8, kCFAllocatorNull);
240	}
241	if (port != 0) {
242		keys[ix] = kSecAttrPort;
243		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
244	}
245	if (protocol != 0) {
246		/* Protocol is a 4 char code, perhaps we should use a string rep
247		   instead. */
248		keys[ix] = kSecAttrProtocol;
249		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
250	}
251	if (authenticationType != 0) {
252		keys[ix] = kSecAttrAuthenticationType;
253		values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
254			&authenticationType);
255	}
256    if (get_password) {
257        /* Only ask for the data if so required. */
258		keys[ix] = kSecReturnData;
259		values[ix++] = kCFBooleanTrue;
260    }
261    keys[ix] = kSecReturnAttributes;
262    values[ix++] = kCFBooleanTrue;
263	if (!do_delete) {
264		/* If we aren't deleting ask for all items. */
265		keys[ix] = kSecMatchLimit;
266		values[ix++] = kSecMatchLimitAll;
267	}
268
269    query = CFDictionaryCreate(NULL, keys, values, ix,
270        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
271    result = do_find_or_delete(query, do_delete);
272    CFReleaseSafe(query);
273
274	return result;
275}
276
277static int
278parse_fourcharcode(const char *name, uint32_t *code)
279{
280	/* @@@ Check for errors. */
281	char *p = (char *)code;
282	strncpy(p, name, 4);
283	return 0;
284}
285
286static int
287keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
288{
289	char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
290    UInt16 port = 0;
291    SecProtocolType protocol = 0;
292    SecAuthenticationType authenticationType = 0;
293	int ch, result = 0;
294	Boolean get_password = FALSE;
295
296	while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
297	{
298		switch  (ch)
299		{
300        case 'a':
301            accountName = optarg;
302            break;
303        case 'd':
304            securityDomain = optarg;
305			break;
306		case 'g':
307            if (do_delete)
308                return 2;
309			get_password = TRUE;
310			break;
311        case 'p':
312            path = optarg;
313            break;
314        case 'P':
315            port = atoi(optarg);
316            break;
317        case 'r':
318			result = parse_fourcharcode(optarg, &protocol);
319			if (result)
320				goto loser;
321			break;
322		case 's':
323			serverName = optarg;
324			break;
325        case 't':
326			result = parse_fourcharcode(optarg, &authenticationType);
327			if (result)
328				goto loser;
329			break;
330        case '?':
331		default:
332			return 2; /* @@@ Return 2 triggers usage message. */
333		}
334	}
335
336	argc -= optind;
337	argv += optind;
338
339	result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
340		accountName, path, port, protocol,authenticationType, get_password);
341
342
343loser:
344
345	return result;
346}
347
348int
349keychain_find_internet_password(int argc, char * const *argv) {
350    return keychain_find_or_delete_internet_password(0, argc, argv);
351}
352
353int
354keychain_delete_internet_password(int argc, char * const *argv) {
355    return keychain_find_or_delete_internet_password(1, argc, argv);
356}
357
358static int
359do_keychain_find_or_delete_generic_password(Boolean do_delete,
360	const char *serviceName, const char *accountName,
361	Boolean get_password)
362 {
363	OSStatus result;
364    CFDictionaryRef query = NULL;
365    const void *keys[6], *values[6];
366    CFIndex ix = 0;
367
368    keys[ix] = kSecClass;
369    values[ix++] = kSecClassGenericPassword;
370	if (serviceName) {
371		keys[ix] = kSecAttrService;
372		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
373			kCFStringEncodingUTF8, kCFAllocatorNull);
374	}
375	if (accountName) {
376		keys[ix] = kSecAttrAccount;
377		values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
378			kCFStringEncodingUTF8, kCFAllocatorNull);
379	}
380    if (get_password) {
381        /* Only ask for the data if so required. */
382		keys[ix] = kSecReturnData;
383		values[ix++] = kCFBooleanTrue;
384    }
385    keys[ix] = kSecReturnAttributes;
386    values[ix++] = kCFBooleanTrue;
387	if (!do_delete) {
388		/* If we aren't deleting ask for all items. */
389		keys[ix] = kSecMatchLimit;
390		values[ix++] = kSecMatchLimitAll;
391	}
392
393    query = CFDictionaryCreate(NULL, keys, values, ix,
394        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
395
396    result = do_find_or_delete(query, do_delete);
397
398	CFReleaseSafe(query);
399
400	return result;
401}
402
403int keychain_item(int argc, char * const *argv) {
404	int ch, result = 0;
405    CFMutableDictionaryRef query, update = NULL;
406	bool get_password = false;
407    bool do_delete = false;
408    bool do_add = false;
409    bool verbose = false;
410    int limit = 0;
411
412    query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
413	while ((ch = getopt(argc, argv, "ad:Df:gq:u:vl:")) != -1)
414	{
415		switch  (ch)
416		{
417            case 'a':
418                do_add = true;
419                break;
420            case 'D':
421                do_delete = true;
422                break;
423            case 'd':
424            {
425                CFStringRef data = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
426                if (data) {
427                    CFDictionarySetValue(update ? update : query, kSecValueData, data);
428                    CFRelease(data);
429                } else {
430                    result = 1;
431                    goto out;
432                }
433                break;
434            }
435            case 'f':
436            {
437                CFDataRef data = copyFileContents(optarg);
438                CFDictionarySetValue(update ? update : query, kSecValueData, data);
439                CFRelease(data);
440                break;
441            }
442            case 'g':
443                get_password = true;
444                break;
445            case 'q':
446                keychain_query_parse_cstring(query, optarg);
447                break;
448            case 'u':
449                if (!update)
450                    update = keychain_create_query_from_string(optarg);
451                else
452                    keychain_query_parse_cstring(update, optarg);
453                break;
454            case 'v':
455                verbose = true;
456                break;
457            case 'l':
458                limit = atoi(optarg);
459                break;
460            case '?':
461            default:
462                /* Return 2 triggers usage message. */
463                result = 2;
464                goto out;
465		}
466	}
467
468    if (((do_add || do_delete) && (get_password || update)) || !query) {
469        result = 2;
470        goto out;
471    }
472
473	argc -= optind;
474	argv += optind;
475
476    int ix;
477    for (ix = 0; ix < argc; ++ix) {
478        keychain_query_parse_cstring(query, argv[ix]);
479    }
480
481    if (!update && !do_add && !do_delete) {
482        CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
483        if(limit) {
484            CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
485            CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
486            CFReleaseSafe(cfLimit);
487        } else {
488            CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
489        }
490        if (get_password)
491            CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
492    }
493
494    if (verbose)
495        CFShow(query);
496
497    OSStatus error;
498    if (do_add) {
499        error = SecItemAdd(query, NULL);
500        if (error) {
501            sec_perror("SecItemAdd", error);
502            result = 1;
503        }
504    } else if (update) {
505        error = SecItemUpdate(query, update);
506        if (error) {
507            sec_perror("SecItemUpdate", error);
508            result = 1;
509        }
510    } else if (do_delete) {
511        error = SecItemDelete(query);
512        if (error) {
513            sec_perror("SecItemDelete", error);
514            result = 1;
515        }
516    } else {
517        do_find_or_delete(query, do_delete);
518    }
519
520out:
521    CFReleaseSafe(query);
522    CFReleaseSafe(update);
523	return result;
524}
525
526static int
527keychain_find_or_delete_generic_password(Boolean do_delete,
528	int argc, char * const *argv)
529{
530	char *serviceName = NULL, *accountName = NULL;
531	int ch, result = 0;
532	Boolean get_password = FALSE;
533
534	while ((ch = getopt(argc, argv, "a:s:g")) != -1)
535	{
536		switch  (ch)
537		{
538        case 'a':
539            accountName = optarg;
540            break;
541        case 'g':
542            if (do_delete)
543                return 2;
544			get_password = TRUE;
545			break;
546		case 's':
547			serviceName = optarg;
548			break;
549        case '?':
550		default:
551			return 2; /* @@@ Return 2 triggers usage message. */
552		}
553	}
554
555	argc -= optind;
556	argv += optind;
557
558	result = do_keychain_find_or_delete_generic_password(do_delete,
559		serviceName, accountName, get_password);
560
561	return result;
562}
563
564int
565keychain_find_generic_password(int argc, char * const *argv) {
566    return keychain_find_or_delete_generic_password(0, argc, argv);
567}
568
569int
570keychain_delete_generic_password(int argc, char * const *argv) {
571    return keychain_find_or_delete_generic_password(1, argc, argv);
572}
573