contrib: add win32 credential-helper

Since the Windows port of Git expects binary pipes, we need to make sure the helper-end also sets up binary pipes. Side-step CRLF-issue in test to make it pass. Signed-off-by: Erik Faye-Lund <kusmabite@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Erik Faye-Lund committed Jul 27, 2012 at 19:10 UTC a6253da0f30c5bee1ecb9bea313881dc8e50efda
3 files changed +375
contrib/credential/wincred/Makefile new
+14
@@ -0,0 +1,14 @@
1 +all: git-credential-wincred.exe
2 +
3 +CC = gcc
4 +RM = rm -f
5 +CFLAGS = -O2 -Wall
6 +
7 +-include ../../../config.mak.autogen
8 +-include ../../../config.mak
9 +
10 +git-credential-wincred.exe : git-credential-wincred.c
11 + $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
12 +
13 +clean:
14 + $(RM) git-credential-wincred.exe
contrib/credential/wincred/git-credential-wincred.c new
+357
@@ -0,0 +1,357 @@
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 +}
t/lib-credential.sh
+4
@@ -18,6 +18,10 @@ check() {
18 cat stderr &&
19 false
20 fi &&
21 + if test_have_prereq MINGW
22 + then
23 + dos2unix -q stderr
24 + fi &&
25 test_cmp expect-stdout stdout &&
26 test_cmp expect-stderr stderr
27 }