1#include <stdio.h>
2#include <string.h>
3#include <stdlib.h>
4#include <Security/Security.h>
5
6static SecProtocolType protocol;
7static char *host;
8static char *path;
9static char *username;
10static char *password;
11static UInt16 port;
12
13static void die(const char *err, ...)
14{
15 char msg[4096];
16 va_list params;
17 va_start(params, err);
18 vsnprintf(msg, sizeof(msg), err, params);
19 fprintf(stderr, "%s\n", msg);
20 va_end(params);
21 exit(1);
22}
23
24static void *xstrdup(const char *s1)
25{
26 void *ret = strdup(s1);
27 if (!ret)
28 die("Out of memory");
29 return ret;
30}
31
32#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
33#define KEYCHAIN_ARGS \
34 NULL, /* default keychain */ \
35 KEYCHAIN_ITEM(host), \
36 0, NULL, /* account domain */ \
37 KEYCHAIN_ITEM(username), \
38 KEYCHAIN_ITEM(path), \
39 port, \
40 protocol, \
41 kSecAuthenticationTypeDefault
42
43static void write_item(const char *what, const char *buf, int len)
44{
45 printf("%s=", what);
46 fwrite(buf, 1, len, stdout);
47 putchar('\n');
48}
49
50static void find_username_in_item(SecKeychainItemRef item)
51{
52 SecKeychainAttributeList list;
53 SecKeychainAttribute attr;
54
55 list.count = 1;
56 list.attr = &attr;
57 attr.tag = kSecAccountItemAttr;
58
59 if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
60 return;
61
62 write_item("username", attr.data, attr.length);
63 SecKeychainItemFreeContent(&list, NULL);
64}
65
66static void find_internet_password(void)
67{
68 void *buf;
69 UInt32 len;
70 SecKeychainItemRef item;
71
72 if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
73 return;
74
75 write_item("password", buf, len);
76 if (!username)
77 find_username_in_item(item);
78
79 SecKeychainItemFreeContent(NULL, buf);
80}
81
82static void delete_internet_password(void)
83{
84 SecKeychainItemRef item;
85
86 /*
87 * Require at least a protocol and host for removal, which is what git
88 * will give us; if you want to do something more fancy, use the
89 * Keychain manager.
90 */
91 if (!protocol || !host)
92 return;
93
94 if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
95 return;
96
97 SecKeychainItemDelete(item);
98}
99
100static void add_internet_password(void)
101{
102 /* Only store complete credentials */
103 if (!protocol || !host || !username || !password)
104 return;
105
106 if (SecKeychainAddInternetPassword(
107 KEYCHAIN_ARGS,
108 KEYCHAIN_ITEM(password),
109 NULL))
110 return;
111}
112
113static void read_credential(void)
114{
115 char buf[1024];
116
117 while (fgets(buf, sizeof(buf), stdin)) {
118 char *v;
119
120 if (!strcmp(buf, "\n"))
121 break;
122 buf[strlen(buf)-1] = '\0';
123
124 v = strchr(buf, '=');
125 if (!v)
126 die("bad input: %s", buf);
127 *v++ = '\0';
128
129 if (!strcmp(buf, "protocol")) {
130 if (!strcmp(v, "imap"))
131 protocol = kSecProtocolTypeIMAP;
132 else if (!strcmp(v, "imaps"))
133 protocol = kSecProtocolTypeIMAPS;
134 else if (!strcmp(v, "ftp"))
135 protocol = kSecProtocolTypeFTP;
136 else if (!strcmp(v, "ftps"))
137 protocol = kSecProtocolTypeFTPS;
138 else if (!strcmp(v, "https"))
139 protocol = kSecProtocolTypeHTTPS;
140 else if (!strcmp(v, "http"))
141 protocol = kSecProtocolTypeHTTP;
142 else if (!strcmp(v, "smtp"))
143 protocol = kSecProtocolTypeSMTP;
144 else /* we don't yet handle other protocols */
145 exit(0);
146 }
147 else if (!strcmp(buf, "host")) {
148 char *colon = strchr(v, ':');
149 if (colon) {
150 *colon++ = '\0';
151 port = atoi(colon);
152 }
153 host = xstrdup(v);
154 }
155 else if (!strcmp(buf, "path"))
156 path = xstrdup(v);
157 else if (!strcmp(buf, "username"))
158 username = xstrdup(v);
159 else if (!strcmp(buf, "password"))
160 password = xstrdup(v);
161 }
162}
163
164int main(int argc, const char **argv)
165{
166 const char *usage =
167 "usage: git credential-osxkeychain <get|store|erase>";
168
169 if (!argv[1])
170 die(usage);
171
172 read_credential();
173
174 if (!strcmp(argv[1], "get"))
175 find_internet_password();
176 else if (!strcmp(argv[1], "store"))
177 add_internet_password();
178 else if (!strcmp(argv[1], "erase"))
179 delete_internet_password();
180 /* otherwise, ignore unknown action */
181
182 return 0;
183}