3
#include <stdlib.h>
4
#include <Security/Security.h>
5
6
-static SecProtocolType protocol;
7
-static char *host;
8
-static char *path;
9
-static char *username;
10
-static char *password;
11
-static UInt16 port;
12
-
13
-__attribute__((format (printf, 1, 2)))
6
+#define ENCODING kCFStringEncodingUTF8
7
+static CFStringRef protocol; /* Stores constant strings - not memory managed */
8
+static CFStringRef host;
9
+static CFStringRef path;
10
+static CFStringRef username;
11
+static CFDataRef password;
12
+static CFNumberRef port;
13
+
14
+static void clear_credential(void)
15
+{
16
+ if (host) {
17
+ CFRelease(host);
18
+ host = NULL;
19
+ }
20
+ if (path) {
21
+ CFRelease(path);
22
+ path = NULL;
23
+ }
24
+ if (username) {
25
+ CFRelease(username);
26
+ username = NULL;
27
+ }
28
+ if (password) {
29
+ CFRelease(password);
30
+ password = NULL;
31
+ }
32
+ if (port) {
33
+ CFRelease(port);
34
+ port = NULL;
35
+ }
36
+}
37
+
38
+__attribute__((format (printf, 1, 2), __noreturn__))
39
static void die(const char *err, ...)
40
{
41
char msg[4096];
44
vsnprintf(msg, sizeof(msg), err, params);
45
fprintf(stderr, "%s\n", msg);
46
va_end(params);
47
+ clear_credential();
48
exit(1);
49
}
50
25
-static void *xstrdup(const char *s1)
51
+static void *xmalloc(size_t len)
52
{
27
- void *ret = strdup(s1);
53
+ void *ret = malloc(len);
54
if (!ret)
55
die("Out of memory");
56
return ret;
57
}
58
33
-#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
34
-#define KEYCHAIN_ARGS \
35
- NULL, /* default keychain */ \
36
- KEYCHAIN_ITEM(host), \
37
- 0, NULL, /* account domain */ \
38
- KEYCHAIN_ITEM(username), \
39
- KEYCHAIN_ITEM(path), \
40
- port, \
41
- protocol, \
42
- kSecAuthenticationTypeDefault
43
-
44
-static void write_item(const char *what, const char *buf, int len)
59
+static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
60
+{
61
+ va_list args;
62
+ const void *key;
63
+ CFMutableDictionaryRef result;
64
+
65
+ result = CFDictionaryCreateMutable(allocator,
66
+ 0,
67
+ &kCFTypeDictionaryKeyCallBacks,
68
+ &kCFTypeDictionaryValueCallBacks);
69
+
70
+
71
+ va_start(args, allocator);
72
+ while ((key = va_arg(args, const void *)) != NULL) {
73
+ const void *value;
74
+ value = va_arg(args, const void *);
75
+ if (value)
76
+ CFDictionarySetValue(result, key, value);
77
+ }
78
+ va_end(args);
79
+
80
+ return result;
81
+}
82
+
83
+#define CREATE_SEC_ATTRIBUTES(...) \
84
+ create_dictionary(kCFAllocatorDefault, \
85
+ kSecClass, kSecClassInternetPassword, \
86
+ kSecAttrServer, host, \
87
+ kSecAttrAccount, username, \
88
+ kSecAttrPath, path, \
89
+ kSecAttrPort, port, \
90
+ kSecAttrProtocol, protocol, \
91
+ kSecAttrAuthenticationType, \
92
+ kSecAttrAuthenticationTypeDefault, \
93
+ __VA_ARGS__);
94
+
95
+static void write_item(const char *what, const char *buf, size_t len)
96
{
97
printf("%s=", what);
98
fwrite(buf, 1, len, stdout);
99
putchar('\n');
100
}
101
51
-static void find_username_in_item(SecKeychainItemRef item)
102
+static void find_username_in_item(CFDictionaryRef item)
103
{
53
- SecKeychainAttributeList list;
54
- SecKeychainAttribute attr;
104
+ CFStringRef account_ref;
105
+ char *username_buf;
106
+ CFIndex buffer_len;
107
56
- list.count = 1;
57
- list.attr = &attr;
58
- attr.tag = kSecAccountItemAttr;
108
+ account_ref = CFDictionaryGetValue(item, kSecAttrAccount);
109
+ if (!account_ref)
110
+ {
111
+ write_item("username", "", 0);
112
+ return;
113
+ }
114
60
- if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
115
+ username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
116
+ if (username_buf)
117
+ {
118
+ write_item("username", username_buf, strlen(username_buf));
119
return;
120
+ }
121
63
- write_item("username", attr.data, attr.length);
64
- SecKeychainItemFreeContent(&list, NULL);
122
+ /* If we can't get a CString pointer then
123
+ * we need to allocate our own buffer */
124
+ buffer_len = CFStringGetMaximumSizeForEncoding(
125
+ CFStringGetLength(account_ref), ENCODING) + 1;
126
+ username_buf = xmalloc(buffer_len);
127
+ if (CFStringGetCString(account_ref,
128
+ username_buf,
129
+ buffer_len,
130
+ ENCODING)) {
131
+ write_item("username", username_buf, buffer_len - 1);
132
+ }
133
+ free(username_buf);
134
}
135
67
-static void find_internet_password(void)
136
+static OSStatus find_internet_password(void)
137
{
69
- void *buf;
70
- UInt32 len;
71
- SecKeychainItemRef item;
138
+ CFDictionaryRef attrs;
139
+ CFDictionaryRef item;
140
+ CFDataRef data;
141
+ OSStatus result;
142
73
- if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
74
- return;
143
+ attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitOne,
144
+ kSecReturnAttributes, kCFBooleanTrue,
145
+ kSecReturnData, kCFBooleanTrue,
146
+ NULL);
147
+ result = SecItemCopyMatching(attrs, (CFTypeRef *)&item);
148
+ if (result) {
149
+ goto out;
150
+ }
151
76
- write_item("password", buf, len);
152
+ data = CFDictionaryGetValue(item, kSecValueData);
153
+
154
+ write_item("password",
155
+ (const char *)CFDataGetBytePtr(data),
156
+ CFDataGetLength(data));
157
if (!username)
158
find_username_in_item(item);
159
80
- SecKeychainItemFreeContent(NULL, buf);
160
+ CFRelease(item);
161
+
162
+out:
163
+ CFRelease(attrs);
164
+
165
+ /* We consider not found to not be an error */
166
+ if (result == errSecItemNotFound)
167
+ result = errSecSuccess;
168
+
169
+ return result;
170
}
171
83
-static void delete_internet_password(void)
172
+static OSStatus delete_internet_password(void)
173
{
85
- SecKeychainItemRef item;
174
+ CFDictionaryRef attrs;
175
+ OSStatus result;
176
177
/*
178
* Require at least a protocol and host for removal, which is what git
180
* Keychain manager.
181
*/
182
if (!protocol || !host)
93
- return;
183
+ return -1;
184
95
- if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
96
- return;
185
+ attrs = CREATE_SEC_ATTRIBUTES(NULL);
186
+ result = SecItemDelete(attrs);
187
+ CFRelease(attrs);
188
+
189
+ /* We consider not found to not be an error */
190
+ if (result == errSecItemNotFound)
191
+ result = errSecSuccess;
192
98
- SecKeychainItemDelete(item);
193
+ return result;
194
}
195
101
-static void add_internet_password(void)
196
+static OSStatus add_internet_password(void)
197
{
198
+ CFDictionaryRef attrs;
199
+ OSStatus result;
200
+
201
/* Only store complete credentials */
202
if (!protocol || !host || !username || !password)
105
- return;
203
+ return -1;
204
107
- if (SecKeychainAddInternetPassword(
108
- KEYCHAIN_ARGS,
109
- KEYCHAIN_ITEM(password),
110
- NULL))
111
- return;
205
+ attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, password,
206
+ NULL);
207
+
208
+ result = SecItemAdd(attrs, NULL);
209
+ if (result == errSecDuplicateItem) {
210
+ CFDictionaryRef query;
211
+ query = CREATE_SEC_ATTRIBUTES(NULL);
212
+ result = SecItemUpdate(query, attrs);
213
+ CFRelease(query);
214
+ }
215
+
216
+ CFRelease(attrs);
217
+
218
+ return result;
219
}
220
221
static void read_credential(void)
238
239
if (!strcmp(buf, "protocol")) {
240
if (!strcmp(v, "imap"))
134
- protocol = kSecProtocolTypeIMAP;
241
+ protocol = kSecAttrProtocolIMAP;
242
else if (!strcmp(v, "imaps"))
136
- protocol = kSecProtocolTypeIMAPS;
243
+ protocol = kSecAttrProtocolIMAPS;
244
else if (!strcmp(v, "ftp"))
138
- protocol = kSecProtocolTypeFTP;
245
+ protocol = kSecAttrProtocolFTP;
246
else if (!strcmp(v, "ftps"))
140
- protocol = kSecProtocolTypeFTPS;
247
+ protocol = kSecAttrProtocolFTPS;
248
else if (!strcmp(v, "https"))
142
- protocol = kSecProtocolTypeHTTPS;
249
+ protocol = kSecAttrProtocolHTTPS;
250
else if (!strcmp(v, "http"))
144
- protocol = kSecProtocolTypeHTTP;
251
+ protocol = kSecAttrProtocolHTTP;
252
else if (!strcmp(v, "smtp"))
146
- protocol = kSecProtocolTypeSMTP;
147
- else /* we don't yet handle other protocols */
253
+ protocol = kSecAttrProtocolSMTP;
254
+ else {
255
+ /* we don't yet handle other protocols */
256
+ clear_credential();
257
exit(0);
258
+ }
259
}
260
else if (!strcmp(buf, "host")) {
261
char *colon = strchr(v, ':');
262
if (colon) {
263
+ UInt16 port_i;
264
*colon++ = '\0';
154
- port = atoi(colon);
265
+ port_i = atoi(colon);
266
+ port = CFNumberCreate(kCFAllocatorDefault,
267
+ kCFNumberShortType,
268
+ &port_i);
269
}
156
- host = xstrdup(v);
270
+ host = CFStringCreateWithCString(kCFAllocatorDefault,
271
+ v,
272
+ ENCODING);
273
}
274
else if (!strcmp(buf, "path"))
159
- path = xstrdup(v);
275
+ path = CFStringCreateWithCString(kCFAllocatorDefault,
276
+ v,
277
+ ENCODING);
278
else if (!strcmp(buf, "username"))
161
- username = xstrdup(v);
279
+ username = CFStringCreateWithCString(
280
+ kCFAllocatorDefault,
281
+ v,
282
+ ENCODING);
283
else if (!strcmp(buf, "password"))
163
- password = xstrdup(v);
284
+ password = CFDataCreate(kCFAllocatorDefault,
285
+ (UInt8 *)v,
286
+ strlen(v));
287
/*
288
* Ignore other lines; we don't know what they mean, but
289
* this future-proofs us when later versions of git do
296
297
int main(int argc, const char **argv)
298
{
299
+ OSStatus result = 0;
300
const char *usage =
301
"usage: git credential-osxkeychain <get|store|erase>";
302
306
read_credential();
307
308
if (!strcmp(argv[1], "get"))
185
- find_internet_password();
309
+ result = find_internet_password();
310
else if (!strcmp(argv[1], "store"))
187
- add_internet_password();
311
+ result = add_internet_password();
312
else if (!strcmp(argv[1], "erase"))
189
- delete_internet_password();
313
+ result = delete_internet_password();
314
/* otherwise, ignore unknown action */
315
316
+ if (result)
317
+ die("failed to %s: %d", argv[1], (int)result);
318
+
319
+ clear_credential();
320
+
321
return 0;
322
}