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
}