2 * A git credential helper that interface with Windows' Credential Manager
12 static void die(const char *err, ...)
16 va_start(params, err);
17 vsnprintf(msg, sizeof(msg), err, params);
18 fprintf(stderr, "%s\n", msg);
23 static void *xmalloc(size_t size)
25 void *ret = malloc(size);
33 static char *xstrdup(const char *str)
35 char *ret = strdup(str);
41 /* MinGW doesn't have wincred.h, so we need to define stuff */
43 typedef struct _CREDENTIAL_ATTRIBUTEW {
48 } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
50 typedef struct _CREDENTIALW {
56 DWORD CredentialBlobSize;
57 LPBYTE CredentialBlob;
60 PCREDENTIAL_ATTRIBUTEW Attributes;
63 } CREDENTIALW, *PCREDENTIALW;
65 #define CRED_TYPE_GENERIC 1
66 #define CRED_PERSIST_LOCAL_MACHINE 2
67 #define CRED_MAX_ATTRIBUTES 64
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 *,
74 typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
76 typedef VOID (WINAPI *CredFreeT)(PVOID);
77 typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
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;
87 static void load_cred_funcs(void)
90 advapi = LoadLibrary("advapi32.dll");
91 credui = LoadLibrary("credui.dll");
92 if (!advapi || !credui)
93 die("failed to load DLLs");
95 /* get function pointers */
96 CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
97 CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
98 GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
99 CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
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 ||
108 die("failed to load functions");
111 static char target_buf[1024];
112 static char *protocol, *host, *path, *username;
113 static WCHAR *wusername, *password, *target;
115 static void write_item(const char *what, WCHAR *wbuf)
118 int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
122 if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
123 die("WideCharToMultiByte failed!");
126 fwrite(buf, 1, len - 1, stdout);
131 static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
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,
143 return 0; /* not found */
146 static int match_cred(const CREDENTIALW *cred)
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);
154 static void get_credential(void)
156 WCHAR *user_buf, *pass_buf;
157 DWORD user_buf_size = 0, pass_buf_size = 0;
158 CREDENTIALW **creds, *cred = NULL;
162 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
165 /* search for the first credential that matches username */
166 for (i = 0; i < num_creds; ++i)
167 if (match_cred(creds[i])) {
174 CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
175 cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
176 NULL, &pass_buf_size);
178 user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
179 pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
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");
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';
192 write_item("username", user_buf);
193 write_item("password", pass_buf);
199 static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
202 attr->Keyword = (LPWSTR)keyword;
204 attr->ValueSize = strlen(value) + 1; /* store zero-termination */
205 attr->Value = (LPBYTE)value;
208 static void store_credential(void)
212 DWORD auth_buf_size = 0;
213 CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
215 if (!wusername || !password)
218 /* query buffer size */
219 CredPackAuthenticationBufferW(0, wusername, password,
220 NULL, &auth_buf_size);
222 auth_buf = xmalloc(auth_buf_size);
224 if (!CredPackAuthenticationBufferW(0, wusername, password,
225 auth_buf, &auth_buf_size))
226 die("CredPackAuthenticationBuffer failed");
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;
240 write_attr(attrs, L"git_protocol", protocol);
243 write_attr(attrs + cred.AttributeCount, L"git_host", host);
244 cred.AttributeCount++;
248 write_attr(attrs + cred.AttributeCount, L"git_path", path);
249 cred.AttributeCount++;
252 if (!CredWriteW(&cred, 0))
253 die("CredWrite failed");
256 static void erase_credential(void)
262 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
265 for (i = 0; i < num_creds; ++i) {
266 if (match_cred(creds[i]))
267 CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
273 static WCHAR *utf8_to_utf16_dup(const char *str)
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);
281 static void read_credential(void)
285 while (fgets(buf, sizeof(buf), stdin)) {
288 if (!strcmp(buf, "\n"))
290 buf[strlen(buf)-1] = '\0';
292 v = strchr(buf, '=');
294 die("bad input: %s", buf);
297 if (!strcmp(buf, "protocol"))
298 protocol = xstrdup(v);
299 else if (!strcmp(buf, "host"))
301 else if (!strcmp(buf, "path"))
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);
309 die("unrecognized input");
313 int main(int argc, char *argv[])
316 "Usage: git credential-wincred <get|store|erase>\n";
321 /* git use binary pipes to avoid CRLF-issues */
322 _setmode(_fileno(stdin), _O_BINARY);
323 _setmode(_fileno(stdout), _O_BINARY);
329 if (!protocol || !(host || path))
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));
337 strncat(target_buf, username, sizeof(target_buf));
338 strncat(target_buf, "@", sizeof(target_buf));
341 strncat(target_buf, host, sizeof(target_buf));
343 strncat(target_buf, "/", sizeof(target_buf));
344 strncat(target_buf, path, sizeof(target_buf));
347 target = utf8_to_utf16_dup(target_buf);
349 if (!strcmp(argv[1], "get"))
351 else if (!strcmp(argv[1], "store"))
353 else if (!strcmp(argv[1], "erase"))
355 /* otherwise, ignore unknown action */