Merge branch 'jn/warn-on-inaccessible-loosen'
[git] / contrib / credential / wincred / git-credential-wincred.c
1 /*
2  * A git credential helper that interface with Windows' Credential Manager
3  *
4  */
5 #include <windows.h>
6 #include <stdio.h>
7 #include <io.h>
8 #include <fcntl.h>
9
10 /* common helpers */
11
12 static void die(const char *err, ...)
13 {
14         char msg[4096];
15         va_list params;
16         va_start(params, err);
17         vsnprintf(msg, sizeof(msg), err, params);
18         fprintf(stderr, "%s\n", msg);
19         va_end(params);
20         exit(1);
21 }
22
23 static void *xmalloc(size_t size)
24 {
25         void *ret = malloc(size);
26         if (!ret && !size)
27                 ret = malloc(1);
28         if (!ret)
29                  die("Out of memory");
30         return ret;
31 }
32
33 static char *xstrdup(const char *str)
34 {
35         char *ret = strdup(str);
36         if (!ret)
37                 die("Out of memory");
38         return ret;
39 }
40
41 /* MinGW doesn't have wincred.h, so we need to define stuff */
42
43 typedef struct _CREDENTIAL_ATTRIBUTEW {
44         LPWSTR Keyword;
45         DWORD  Flags;
46         DWORD  ValueSize;
47         LPBYTE Value;
48 } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
49
50 typedef struct _CREDENTIALW {
51         DWORD                  Flags;
52         DWORD                  Type;
53         LPWSTR                 TargetName;
54         LPWSTR                 Comment;
55         FILETIME               LastWritten;
56         DWORD                  CredentialBlobSize;
57         LPBYTE                 CredentialBlob;
58         DWORD                  Persist;
59         DWORD                  AttributeCount;
60         PCREDENTIAL_ATTRIBUTEW Attributes;
61         LPWSTR                 TargetAlias;
62         LPWSTR                 UserName;
63 } CREDENTIALW, *PCREDENTIALW;
64
65 #define CRED_TYPE_GENERIC 1
66 #define CRED_PERSIST_LOCAL_MACHINE 2
67 #define CRED_MAX_ATTRIBUTES 64
68
69 typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
70 typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
71     LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
72 typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
73     PCREDENTIALW **);
74 typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
75     PBYTE, DWORD *);
76 typedef VOID (WINAPI *CredFreeT)(PVOID);
77 typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
78
79 static HMODULE advapi, credui;
80 static CredWriteWT CredWriteW;
81 static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
82 static CredEnumerateWT CredEnumerateW;
83 static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
84 static CredFreeT CredFree;
85 static CredDeleteWT CredDeleteW;
86
87 static void load_cred_funcs(void)
88 {
89         /* load DLLs */
90         advapi = LoadLibrary("advapi32.dll");
91         credui = LoadLibrary("credui.dll");
92         if (!advapi || !credui)
93                 die("failed to load DLLs");
94
95         /* get function pointers */
96         CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
97         CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
98             GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
99         CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
100             "CredEnumerateW");
101         CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
102             GetProcAddress(credui, "CredPackAuthenticationBufferW");
103         CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
104         CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
105         if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
106             !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
107             !CredDeleteW)
108                 die("failed to load functions");
109 }
110
111 static char target_buf[1024];
112 static char *protocol, *host, *path, *username;
113 static WCHAR *wusername, *password, *target;
114
115 static void write_item(const char *what, WCHAR *wbuf)
116 {
117         char *buf;
118         int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
119             FALSE);
120         buf = xmalloc(len);
121
122         if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
123                 die("WideCharToMultiByte failed!");
124
125         printf("%s=", what);
126         fwrite(buf, 1, len - 1, stdout);
127         putchar('\n');
128         free(buf);
129 }
130
131 static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
132     const char *want)
133 {
134         int i;
135         if (!want)
136                 return 1;
137
138         for (i = 0; i < cred->AttributeCount; ++i)
139                 if (!wcscmp(cred->Attributes[i].Keyword, keyword))
140                         return !strcmp((const char *)cred->Attributes[i].Value,
141                             want);
142
143         return 0; /* not found */
144 }
145
146 static int match_cred(const CREDENTIALW *cred)
147 {
148         return (!wusername || !wcscmp(wusername, cred->UserName)) &&
149             match_attr(cred, L"git_protocol", protocol) &&
150             match_attr(cred, L"git_host", host) &&
151             match_attr(cred, L"git_path", path);
152 }
153
154 static void get_credential(void)
155 {
156         WCHAR *user_buf, *pass_buf;
157         DWORD user_buf_size = 0, pass_buf_size = 0;
158         CREDENTIALW **creds, *cred = NULL;
159         DWORD num_creds;
160         int i;
161
162         if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
163                 return;
164
165         /* search for the first credential that matches username */
166         for (i = 0; i < num_creds; ++i)
167                 if (match_cred(creds[i])) {
168                         cred = creds[i];
169                         break;
170                 }
171         if (!cred)
172                 return;
173
174         CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
175             cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
176             NULL, &pass_buf_size);
177
178         user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
179         pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
180
181         if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
182             cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
183             pass_buf, &pass_buf_size))
184                 die("CredUnPackAuthenticationBuffer failed");
185
186         CredFree(creds);
187
188         /* zero-terminate (sizes include zero-termination) */
189         user_buf[user_buf_size - 1] = L'\0';
190         pass_buf[pass_buf_size - 1] = L'\0';
191
192         write_item("username", user_buf);
193         write_item("password", pass_buf);
194
195         free(user_buf);
196         free(pass_buf);
197 }
198
199 static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
200     const char *value)
201 {
202         attr->Keyword = (LPWSTR)keyword;
203         attr->Flags = 0;
204         attr->ValueSize = strlen(value) + 1; /* store zero-termination */
205         attr->Value = (LPBYTE)value;
206 }
207
208 static void store_credential(void)
209 {
210         CREDENTIALW cred;
211         BYTE *auth_buf;
212         DWORD auth_buf_size = 0;
213         CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
214
215         if (!wusername || !password)
216                 return;
217
218         /* query buffer size */
219         CredPackAuthenticationBufferW(0, wusername, password,
220             NULL, &auth_buf_size);
221
222         auth_buf = xmalloc(auth_buf_size);
223
224         if (!CredPackAuthenticationBufferW(0, wusername, password,
225             auth_buf, &auth_buf_size))
226                 die("CredPackAuthenticationBuffer failed");
227
228         cred.Flags = 0;
229         cred.Type = CRED_TYPE_GENERIC;
230         cred.TargetName = target;
231         cred.Comment = L"saved by git-credential-wincred";
232         cred.CredentialBlobSize = auth_buf_size;
233         cred.CredentialBlob = auth_buf;
234         cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
235         cred.AttributeCount = 1;
236         cred.Attributes = attrs;
237         cred.TargetAlias = NULL;
238         cred.UserName = wusername;
239
240         write_attr(attrs, L"git_protocol", protocol);
241
242         if (host) {
243                 write_attr(attrs + cred.AttributeCount, L"git_host", host);
244                 cred.AttributeCount++;
245         }
246
247         if (path) {
248                 write_attr(attrs + cred.AttributeCount, L"git_path", path);
249                 cred.AttributeCount++;
250         }
251
252         if (!CredWriteW(&cred, 0))
253                 die("CredWrite failed");
254 }
255
256 static void erase_credential(void)
257 {
258         CREDENTIALW **creds;
259         DWORD num_creds;
260         int i;
261
262         if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
263                 return;
264
265         for (i = 0; i < num_creds; ++i) {
266                 if (match_cred(creds[i]))
267                         CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
268         }
269
270         CredFree(creds);
271 }
272
273 static WCHAR *utf8_to_utf16_dup(const char *str)
274 {
275         int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
276         WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
277         MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
278         return wstr;
279 }
280
281 static void read_credential(void)
282 {
283         char buf[1024];
284
285         while (fgets(buf, sizeof(buf), stdin)) {
286                 char *v;
287
288                 if (!strcmp(buf, "\n"))
289                         break;
290                 buf[strlen(buf)-1] = '\0';
291
292                 v = strchr(buf, '=');
293                 if (!v)
294                         die("bad input: %s", buf);
295                 *v++ = '\0';
296
297                 if (!strcmp(buf, "protocol"))
298                         protocol = xstrdup(v);
299                 else if (!strcmp(buf, "host"))
300                         host = xstrdup(v);
301                 else if (!strcmp(buf, "path"))
302                         path = xstrdup(v);
303                 else if (!strcmp(buf, "username")) {
304                         username = xstrdup(v);
305                         wusername = utf8_to_utf16_dup(v);
306                 } else if (!strcmp(buf, "password"))
307                         password = utf8_to_utf16_dup(v);
308                 else
309                         die("unrecognized input");
310         }
311 }
312
313 int main(int argc, char *argv[])
314 {
315         const char *usage =
316             "Usage: git credential-wincred <get|store|erase>\n";
317
318         if (!argv[1])
319                 die(usage);
320
321         /* git use binary pipes to avoid CRLF-issues */
322         _setmode(_fileno(stdin), _O_BINARY);
323         _setmode(_fileno(stdout), _O_BINARY);
324
325         read_credential();
326
327         load_cred_funcs();
328
329         if (!protocol || !(host || path))
330                 return 0;
331
332         /* prepare 'target', the unique key for the credential */
333         strncat(target_buf, "git:", sizeof(target_buf));
334         strncat(target_buf, protocol, sizeof(target_buf));
335         strncat(target_buf, "://", sizeof(target_buf));
336         if (username) {
337                 strncat(target_buf, username, sizeof(target_buf));
338                 strncat(target_buf, "@", sizeof(target_buf));
339         }
340         if (host)
341                 strncat(target_buf, host, sizeof(target_buf));
342         if (path) {
343                 strncat(target_buf, "/", sizeof(target_buf));
344                 strncat(target_buf, path, sizeof(target_buf));
345         }
346
347         target = utf8_to_utf16_dup(target_buf);
348
349         if (!strcmp(argv[1], "get"))
350                 get_credential();
351         else if (!strcmp(argv[1], "store"))
352                 store_credential();
353         else if (!strcmp(argv[1], "erase"))
354                 erase_credential();
355         /* otherwise, ignore unknown action */
356         return 0;
357 }