Merge branch 'mt/parallel-checkout-with-padded-oidcpy'
[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 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
13
14 static void die(const char *err, ...)
15 {
16         char msg[4096];
17         va_list params;
18         va_start(params, err);
19         vsnprintf(msg, sizeof(msg), err, params);
20         fprintf(stderr, "%s\n", msg);
21         va_end(params);
22         exit(1);
23 }
24
25 static void *xmalloc(size_t size)
26 {
27         void *ret = malloc(size);
28         if (!ret && !size)
29                 ret = malloc(1);
30         if (!ret)
31                  die("Out of memory");
32         return ret;
33 }
34
35 /* MinGW doesn't have wincred.h, so we need to define stuff */
36
37 typedef struct _CREDENTIAL_ATTRIBUTEW {
38         LPWSTR Keyword;
39         DWORD  Flags;
40         DWORD  ValueSize;
41         LPBYTE Value;
42 } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
43
44 typedef struct _CREDENTIALW {
45         DWORD                  Flags;
46         DWORD                  Type;
47         LPWSTR                 TargetName;
48         LPWSTR                 Comment;
49         FILETIME               LastWritten;
50         DWORD                  CredentialBlobSize;
51         LPBYTE                 CredentialBlob;
52         DWORD                  Persist;
53         DWORD                  AttributeCount;
54         PCREDENTIAL_ATTRIBUTEW Attributes;
55         LPWSTR                 TargetAlias;
56         LPWSTR                 UserName;
57 } CREDENTIALW, *PCREDENTIALW;
58
59 #define CRED_TYPE_GENERIC 1
60 #define CRED_PERSIST_LOCAL_MACHINE 2
61 #define CRED_MAX_ATTRIBUTES 64
62
63 typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
64 typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
65     PCREDENTIALW **);
66 typedef VOID (WINAPI *CredFreeT)(PVOID);
67 typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
68
69 static HMODULE advapi;
70 static CredWriteWT CredWriteW;
71 static CredEnumerateWT CredEnumerateW;
72 static CredFreeT CredFree;
73 static CredDeleteWT CredDeleteW;
74
75 static void load_cred_funcs(void)
76 {
77         /* load DLLs */
78         advapi = LoadLibraryExA("advapi32.dll", NULL,
79                                 LOAD_LIBRARY_SEARCH_SYSTEM32);
80         if (!advapi)
81                 die("failed to load advapi32.dll");
82
83         /* get function pointers */
84         CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
85         CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
86             "CredEnumerateW");
87         CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
88         CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
89         if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
90                 die("failed to load functions");
91 }
92
93 static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
94
95 static void write_item(const char *what, LPCWSTR wbuf, int wlen)
96 {
97         char *buf;
98
99         if (!wbuf || !wlen) {
100                 printf("%s=\n", what);
101                 return;
102         }
103
104         int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
105             FALSE);
106         buf = xmalloc(len);
107
108         if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
109                 die("WideCharToMultiByte failed!");
110
111         printf("%s=", what);
112         fwrite(buf, 1, len, stdout);
113         putchar('\n');
114         free(buf);
115 }
116
117 /*
118  * Match an (optional) expected string and a delimiter in the target string,
119  * consuming the matched text by updating the target pointer.
120  */
121
122 static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
123 {
124         LPCWSTR res = NULL, pos;
125         for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
126                 res = pos;
127         return res;
128 }
129
130 static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
131 {
132         LPCWSTR delim_pos, start = *ptarget;
133         int len;
134
135         /* find start of delimiter (or end-of-string if delim is empty) */
136         if (*delim)
137                 delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
138         else
139                 delim_pos = start + wcslen(start);
140
141         /*
142          * match text up to delimiter, or end of string (e.g. the '/' after
143          * host is optional if not followed by a path)
144          */
145         if (delim_pos)
146                 len = delim_pos - start;
147         else
148                 len = wcslen(start);
149
150         /* update ptarget if we either found a delimiter or need a match */
151         if (delim_pos || want)
152                 *ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
153
154         return !want || (!wcsncmp(want, start, len) && !want[len]);
155 }
156
157 static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
158 {
159         return match_part_with_last(ptarget, want, delim, 0);
160 }
161
162 static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
163 {
164         return match_part_with_last(ptarget, want, delim, 1);
165 }
166
167 static int match_cred(const CREDENTIALW *cred)
168 {
169         LPCWSTR target = cred->TargetName;
170         if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
171                 return 0;
172
173         return match_part(&target, L"git", L":") &&
174                 match_part(&target, protocol, L"://") &&
175                 match_part_last(&target, wusername, L"@") &&
176                 match_part(&target, host, L"/") &&
177                 match_part(&target, path, L"");
178 }
179
180 static void get_credential(void)
181 {
182         CREDENTIALW **creds;
183         DWORD num_creds;
184         int i;
185
186         if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
187                 return;
188
189         /* search for the first credential that matches username */
190         for (i = 0; i < num_creds; ++i)
191                 if (match_cred(creds[i])) {
192                         write_item("username", creds[i]->UserName,
193                                 creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
194                         write_item("password",
195                                 (LPCWSTR)creds[i]->CredentialBlob,
196                                 creds[i]->CredentialBlobSize / sizeof(WCHAR));
197                         break;
198                 }
199
200         CredFree(creds);
201 }
202
203 static void store_credential(void)
204 {
205         CREDENTIALW cred;
206
207         if (!wusername || !password)
208                 return;
209
210         cred.Flags = 0;
211         cred.Type = CRED_TYPE_GENERIC;
212         cred.TargetName = target;
213         cred.Comment = L"saved by git-credential-wincred";
214         cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
215         cred.CredentialBlob = (LPVOID)password;
216         cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
217         cred.AttributeCount = 0;
218         cred.Attributes = NULL;
219         cred.TargetAlias = NULL;
220         cred.UserName = wusername;
221
222         if (!CredWriteW(&cred, 0))
223                 die("CredWrite failed");
224 }
225
226 static void erase_credential(void)
227 {
228         CREDENTIALW **creds;
229         DWORD num_creds;
230         int i;
231
232         if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
233                 return;
234
235         for (i = 0; i < num_creds; ++i) {
236                 if (match_cred(creds[i]))
237                         CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
238         }
239
240         CredFree(creds);
241 }
242
243 static WCHAR *utf8_to_utf16_dup(const char *str)
244 {
245         int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
246         WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
247         MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
248         return wstr;
249 }
250
251 static void read_credential(void)
252 {
253         char buf[1024];
254
255         while (fgets(buf, sizeof(buf), stdin)) {
256                 char *v;
257                 int len = strlen(buf);
258                 /* strip trailing CR / LF */
259                 while (len && strchr("\r\n", buf[len - 1]))
260                         buf[--len] = 0;
261
262                 if (!*buf)
263                         break;
264
265                 v = strchr(buf, '=');
266                 if (!v)
267                         die("bad input: %s", buf);
268                 *v++ = '\0';
269
270                 if (!strcmp(buf, "protocol"))
271                         protocol = utf8_to_utf16_dup(v);
272                 else if (!strcmp(buf, "host"))
273                         host = utf8_to_utf16_dup(v);
274                 else if (!strcmp(buf, "path"))
275                         path = utf8_to_utf16_dup(v);
276                 else if (!strcmp(buf, "username")) {
277                         wusername = utf8_to_utf16_dup(v);
278                 } else if (!strcmp(buf, "password"))
279                         password = utf8_to_utf16_dup(v);
280                 else
281                         die("unrecognized input");
282         }
283 }
284
285 int main(int argc, char *argv[])
286 {
287         const char *usage =
288             "usage: git credential-wincred <get|store|erase>\n";
289
290         if (!argv[1])
291                 die(usage);
292
293         /* git use binary pipes to avoid CRLF-issues */
294         _setmode(_fileno(stdin), _O_BINARY);
295         _setmode(_fileno(stdout), _O_BINARY);
296
297         read_credential();
298
299         load_cred_funcs();
300
301         if (!protocol || !(host || path))
302                 return 0;
303
304         /* prepare 'target', the unique key for the credential */
305         wcscpy(target, L"git:");
306         wcsncat(target, protocol, ARRAY_SIZE(target));
307         wcsncat(target, L"://", ARRAY_SIZE(target));
308         if (wusername) {
309                 wcsncat(target, wusername, ARRAY_SIZE(target));
310                 wcsncat(target, L"@", ARRAY_SIZE(target));
311         }
312         if (host)
313                 wcsncat(target, host, ARRAY_SIZE(target));
314         if (path) {
315                 wcsncat(target, L"/", ARRAY_SIZE(target));
316                 wcsncat(target, path, ARRAY_SIZE(target));
317         }
318
319         if (!strcmp(argv[1], "get"))
320                 get_credential();
321         else if (!strcmp(argv[1], "store"))
322                 store_credential();
323         else if (!strcmp(argv[1], "erase"))
324                 erase_credential();
325         /* otherwise, ignore unknown action */
326         return 0;
327 }