Merge branch 'mm/detach-at-HEAD-reflog'
[git] / contrib / credential / osxkeychain / git-credential-osxkeychain.c
1 #include <stdio.h>
2 #include <string.h>
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 static 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
24 static 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
43 static 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
50 static 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
66 static 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
82 static 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
100 static 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
113 static 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
164 int 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 }