Skip to content

Commit 9f152fc

Browse files
committed
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>
1 parent 0e18bef commit 9f152fc

File tree

3 files changed

+364
-0
lines changed

3 files changed

+364
-0
lines changed

contrib/credential/wincred/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
all: git-credential-wincred.exe
2+
3+
CC = gcc
4+
RM = rm -f
5+
CFLAGS = -O2 -Wall
6+
7+
git-credential-wincred.exe : git-credential-wincred.c
8+
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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 = 2;
236+
cred.Attributes = attrs;
237+
cred.TargetAlias = NULL;
238+
cred.UserName = wusername;
239+
240+
write_attr(attrs, L"git_protocol", protocol);
241+
write_attr(attrs + 1, L"git_host", host);
242+
243+
if (path) {
244+
write_attr(attrs + 2, L"git_path", path);
245+
cred.AttributeCount++;
246+
}
247+
248+
if (!CredWriteW(&cred, 0))
249+
die("CredWrite failed");
250+
}
251+
252+
static void erase_credential(void)
253+
{
254+
CREDENTIALW **creds;
255+
DWORD num_creds;
256+
int i;
257+
258+
if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
259+
return;
260+
261+
for (i = 0; i < num_creds; ++i) {
262+
if (match_cred(creds[i]))
263+
CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
264+
}
265+
266+
CredFree(creds);
267+
}
268+
269+
static WCHAR *utf8_to_utf16_dup(const char *str)
270+
{
271+
int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
272+
WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
273+
MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
274+
return wstr;
275+
}
276+
277+
static void read_credential(void)
278+
{
279+
char buf[1024];
280+
281+
while (fgets(buf, sizeof(buf), stdin)) {
282+
char *v;
283+
284+
if (!strcmp(buf, "\n"))
285+
break;
286+
buf[strlen(buf)-1] = '\0';
287+
288+
v = strchr(buf, '=');
289+
if (!v)
290+
die("bad input: %s", buf);
291+
*v++ = '\0';
292+
293+
if (!strcmp(buf, "protocol"))
294+
protocol = xstrdup(v);
295+
else if (!strcmp(buf, "host"))
296+
host = xstrdup(v);
297+
else if (!strcmp(buf, "path"))
298+
path = xstrdup(v);
299+
else if (!strcmp(buf, "username")) {
300+
username = xstrdup(v);
301+
wusername = utf8_to_utf16_dup(v);
302+
} else if (!strcmp(buf, "password"))
303+
password = utf8_to_utf16_dup(v);
304+
else
305+
die("unrecognized input");
306+
}
307+
}
308+
309+
int main(int argc, char *argv[])
310+
{
311+
const char *usage =
312+
"Usage: git credential-wincred <get|store|erase>\n";
313+
314+
if (!argv[1])
315+
die(usage);
316+
317+
/* git use binary pipes to avoid CRLF-issues */
318+
_setmode(_fileno(stdin), _O_BINARY);
319+
_setmode(_fileno(stdout), _O_BINARY);
320+
321+
read_credential();
322+
323+
load_cred_funcs();
324+
325+
if (!protocol || !host)
326+
return 0;
327+
328+
/* prepare 'target', the unique key for the credential */
329+
strncat(target_buf, "git:", sizeof(target_buf));
330+
strncat(target_buf, protocol, sizeof(target_buf));
331+
strncat(target_buf, "://", sizeof(target_buf));
332+
if (username) {
333+
strncat(target_buf, username, sizeof(target_buf));
334+
strncat(target_buf, "@", sizeof(target_buf));
335+
}
336+
strncat(target_buf, host, sizeof(target_buf));
337+
if (path) {
338+
strncat(target_buf, "/", sizeof(target_buf));
339+
strncat(target_buf, path, sizeof(target_buf));
340+
}
341+
342+
target = utf8_to_utf16_dup(target_buf);
343+
344+
if (!strcmp(argv[1], "get"))
345+
get_credential();
346+
else if (!strcmp(argv[1], "store"))
347+
store_credential();
348+
else if (!strcmp(argv[1], "erase"))
349+
erase_credential();
350+
/* otherwise, ignore unknown action */
351+
return 0;
352+
}

t/lib-credential.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ check() {
88
read_chunk >expect-stdout &&
99
read_chunk >expect-stderr &&
1010
test-credential "$@" <stdin >stdout 2>stderr &&
11+
if test_have_prereq MINGW
12+
then
13+
dos2unix -q stderr
14+
fi &&
1115
test_cmp expect-stdout stdout &&
1216
test_cmp expect-stderr stderr
1317
}

0 commit comments

Comments
 (0)