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
12static 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
23static 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
33static 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
43typedef struct _CREDENTIAL_ATTRIBUTEW {
44 LPWSTR Keyword;
45 DWORD Flags;
46 DWORD ValueSize;
47 LPBYTE Value;
48} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
49
50typedef 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
69typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
70typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
71 LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
72typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
73 PCREDENTIALW **);
74typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
75 PBYTE, DWORD *);
76typedef VOID (WINAPI *CredFreeT)(PVOID);
77typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
78
79static HMODULE advapi, credui;
80static CredWriteWT CredWriteW;
81static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
82static CredEnumerateWT CredEnumerateW;
83static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
84static CredFreeT CredFree;
85static CredDeleteWT CredDeleteW;
86
87static 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
111static char target_buf[1024];
112static char *protocol, *host, *path, *username;
113static WCHAR *wusername, *password, *target;
114
115static 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
131static 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
146static 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
154static 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
199static 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
208static 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
256static 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
273static 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
281static 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
313int 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}