archive: handle commits with an empty tree
[git/jnareb-git.git] / contrib / credential / wincred / git-credential-wincred.c
blobcbaec5f24b9c2b4a1fb1b25af57a826a053da037
1 /*
2 * A git credential helper that interface with Windows' Credential Manager
4 */
5 #include <windows.h>
6 #include <stdio.h>
7 #include <io.h>
8 #include <fcntl.h>
10 /* common helpers */
12 static void die(const char *err, ...)
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);
23 static void *xmalloc(size_t size)
25 void *ret = malloc(size);
26 if (!ret && !size)
27 ret = malloc(1);
28 if (!ret)
29 die("Out of memory");
30 return ret;
33 static char *xstrdup(const char *str)
35 char *ret = strdup(str);
36 if (!ret)
37 die("Out of memory");
38 return ret;
41 /* MinGW doesn't have wincred.h, so we need to define stuff */
43 typedef struct _CREDENTIAL_ATTRIBUTEW {
44 LPWSTR Keyword;
45 DWORD Flags;
46 DWORD ValueSize;
47 LPBYTE Value;
48 } CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
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;
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 *,
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);
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)
89 /* load DLLs */
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,
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");
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)
117 char *buf;
118 int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
119 FALSE);
120 buf = xmalloc(len);
122 if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
123 die("WideCharToMultiByte failed!");
125 printf("%s=", what);
126 fwrite(buf, 1, len - 1, stdout);
127 putchar('\n');
128 free(buf);
131 static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
132 const char *want)
134 int i;
135 if (!want)
136 return 1;
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);
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;
159 DWORD num_creds;
160 int i;
162 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
163 return;
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;
171 if (!cred)
172 return;
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");
186 CredFree(creds);
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);
195 free(user_buf);
196 free(pass_buf);
199 static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
200 const char *value)
202 attr->Keyword = (LPWSTR)keyword;
203 attr->Flags = 0;
204 attr->ValueSize = strlen(value) + 1; /* store zero-termination */
205 attr->Value = (LPBYTE)value;
208 static void store_credential(void)
210 CREDENTIALW cred;
211 BYTE *auth_buf;
212 DWORD auth_buf_size = 0;
213 CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
215 if (!wusername || !password)
216 return;
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");
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;
240 write_attr(attrs, L"git_protocol", protocol);
242 if (host) {
243 write_attr(attrs + cred.AttributeCount, L"git_host", host);
244 cred.AttributeCount++;
247 if (path) {
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)
258 CREDENTIALW **creds;
259 DWORD num_creds;
260 int i;
262 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
263 return;
265 for (i = 0; i < num_creds; ++i) {
266 if (match_cred(creds[i]))
267 CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
270 CredFree(creds);
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);
278 return wstr;
281 static void read_credential(void)
283 char buf[1024];
285 while (fgets(buf, sizeof(buf), stdin)) {
286 char *v;
288 if (!strcmp(buf, "\n"))
289 break;
290 buf[strlen(buf)-1] = '\0';
292 v = strchr(buf, '=');
293 if (!v)
294 die("bad input: %s", buf);
295 *v++ = '\0';
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");
313 int main(int argc, char *argv[])
315 const char *usage =
316 "Usage: git credential-wincred <get|store|erase>\n";
318 if (!argv[1])
319 die(usage);
321 /* git use binary pipes to avoid CRLF-issues */
322 _setmode(_fileno(stdin), _O_BINARY);
323 _setmode(_fileno(stdout), _O_BINARY);
325 read_credential();
327 load_cred_funcs();
329 if (!protocol || !(host || path))
330 return 0;
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));
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));
347 target = utf8_to_utf16_dup(target_buf);
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;