Merge branch 'lp/maint-diff-three-dash-with-graph'
[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, "https"))
131                                 protocol = kSecProtocolTypeHTTPS;
132                         else if (!strcmp(v, "http"))
133                                 protocol = kSecProtocolTypeHTTP;
134                         else /* we don't yet handle other protocols */
135                                 exit(0);
136                 }
137                 else if (!strcmp(buf, "host")) {
138                         char *colon = strchr(v, ':');
139                         if (colon) {
140                                 *colon++ = '\0';
141                                 port = atoi(colon);
142                         }
143                         host = xstrdup(v);
144                 }
145                 else if (!strcmp(buf, "path"))
146                         path = xstrdup(v);
147                 else if (!strcmp(buf, "username"))
148                         username = xstrdup(v);
149                 else if (!strcmp(buf, "password"))
150                         password = xstrdup(v);
151         }
152 }
153
154 int main(int argc, const char **argv)
155 {
156         const char *usage =
157                 "Usage: git credential-osxkeychain <get|store|erase>";
158
159         if (!argv[1])
160                 die(usage);
161
162         read_credential();
163
164         if (!strcmp(argv[1], "get"))
165                 find_internet_password();
166         else if (!strcmp(argv[1], "store"))
167                 add_internet_password();
168         else if (!strcmp(argv[1], "erase"))
169                 delete_internet_password();
170         /* otherwise, ignore unknown action */
171
172         return 0;
173 }