]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/credential/wincred/git-credential-wincred.c
Merge branch 'ds/remove-idx-before-pack'
[thirdparty/git.git] / contrib / credential / wincred / git-credential-wincred.c
CommitLineData
a6253da0
EFL
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>
818b4f82 9#include <wincred.h>
a6253da0
EFL
10
11/* common helpers */
12
8b2d219a
KB
13#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
14
48ca53ca 15__attribute__((format (printf, 1, 2)))
a6253da0
EFL
16static void die(const char *err, ...)
17{
18 char msg[4096];
19 va_list params;
20 va_start(params, err);
21 vsnprintf(msg, sizeof(msg), err, params);
22 fprintf(stderr, "%s\n", msg);
23 va_end(params);
24 exit(1);
25}
26
27static void *xmalloc(size_t size)
28{
29 void *ret = malloc(size);
30 if (!ret && !size)
31 ret = malloc(1);
32 if (!ret)
33 die("Out of memory");
34 return ret;
35}
36
488d9d52
H
37static WCHAR *wusername, *password, *protocol, *host, *path, target[1024],
38 *password_expiry_utc;
a6253da0 39
8b2d219a 40static void write_item(const char *what, LPCWSTR wbuf, int wlen)
a6253da0
EFL
41{
42 char *buf;
601e1e78
JB
43
44 if (!wbuf || !wlen) {
45 printf("%s=\n", what);
46 return;
47 }
48
8b2d219a 49 int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
a6253da0
EFL
50 FALSE);
51 buf = xmalloc(len);
52
8b2d219a 53 if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
a6253da0
EFL
54 die("WideCharToMultiByte failed!");
55
56 printf("%s=", what);
8b2d219a 57 fwrite(buf, 1, len, stdout);
a6253da0
EFL
58 putchar('\n');
59 free(buf);
60}
61
8b2d219a
KB
62/*
63 * Match an (optional) expected string and a delimiter in the target string,
64 * consuming the matched text by updating the target pointer.
65 */
13d261e5
AV
66
67static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
68{
69 LPCWSTR res = NULL, pos;
70 for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
71 res = pos;
72 return res;
73}
74
75static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
a6253da0 76{
8b2d219a
KB
77 LPCWSTR delim_pos, start = *ptarget;
78 int len;
79
80 /* find start of delimiter (or end-of-string if delim is empty) */
81 if (*delim)
13d261e5 82 delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
8b2d219a
KB
83 else
84 delim_pos = start + wcslen(start);
85
86 /*
87 * match text up to delimiter, or end of string (e.g. the '/' after
88 * host is optional if not followed by a path)
89 */
90 if (delim_pos)
91 len = delim_pos - start;
92 else
93 len = wcslen(start);
94
95 /* update ptarget if we either found a delimiter or need a match */
96 if (delim_pos || want)
97 *ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
98
99 return !want || (!wcsncmp(want, start, len) && !want[len]);
a6253da0
EFL
100}
101
13d261e5
AV
102static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
103{
104 return match_part_with_last(ptarget, want, delim, 0);
105}
106
107static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
108{
109 return match_part_with_last(ptarget, want, delim, 1);
110}
111
a6253da0
EFL
112static int match_cred(const CREDENTIALW *cred)
113{
8b2d219a 114 LPCWSTR target = cred->TargetName;
601e1e78 115 if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
8b2d219a
KB
116 return 0;
117
118 return match_part(&target, L"git", L":") &&
119 match_part(&target, protocol, L"://") &&
13d261e5 120 match_part_last(&target, wusername, L"@") &&
8b2d219a
KB
121 match_part(&target, host, L"/") &&
122 match_part(&target, path, L"");
a6253da0
EFL
123}
124
125static void get_credential(void)
126{
8b2d219a 127 CREDENTIALW **creds;
a6253da0
EFL
128 DWORD num_creds;
129 int i;
488d9d52 130 CREDENTIAL_ATTRIBUTEW *attr;
a6253da0
EFL
131
132 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
133 return;
134
135 /* search for the first credential that matches username */
136 for (i = 0; i < num_creds; ++i)
137 if (match_cred(creds[i])) {
8b2d219a 138 write_item("username", creds[i]->UserName,
601e1e78 139 creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
8b2d219a
KB
140 write_item("password",
141 (LPCWSTR)creds[i]->CredentialBlob,
142 creds[i]->CredentialBlobSize / sizeof(WCHAR));
488d9d52
H
143 for (int j = 0; j < creds[i]->AttributeCount; j++) {
144 attr = creds[i]->Attributes + j;
145 if (!wcscmp(attr->Keyword, L"git_password_expiry_utc")) {
146 write_item("password_expiry_utc", (LPCWSTR)attr->Value,
147 attr->ValueSize / sizeof(WCHAR));
148 break;
149 }
150 }
a6253da0
EFL
151 break;
152 }
a6253da0
EFL
153
154 CredFree(creds);
a6253da0
EFL
155}
156
157static void store_credential(void)
158{
159 CREDENTIALW cred;
488d9d52 160 CREDENTIAL_ATTRIBUTEW expiry_attr;
a6253da0
EFL
161
162 if (!wusername || !password)
163 return;
164
a6253da0
EFL
165 cred.Flags = 0;
166 cred.Type = CRED_TYPE_GENERIC;
167 cred.TargetName = target;
168 cred.Comment = L"saved by git-credential-wincred";
8b2d219a
KB
169 cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
170 cred.CredentialBlob = (LPVOID)password;
a6253da0 171 cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
8b2d219a
KB
172 cred.AttributeCount = 0;
173 cred.Attributes = NULL;
488d9d52
H
174 if (password_expiry_utc != NULL) {
175 expiry_attr.Keyword = L"git_password_expiry_utc";
176 expiry_attr.Value = (LPVOID)password_expiry_utc;
177 expiry_attr.ValueSize = (wcslen(password_expiry_utc)) * sizeof(WCHAR);
178 expiry_attr.Flags = 0;
179 cred.Attributes = &expiry_attr;
180 cred.AttributeCount = 1;
181 }
a6253da0
EFL
182 cred.TargetAlias = NULL;
183 cred.UserName = wusername;
184
a6253da0
EFL
185 if (!CredWriteW(&cred, 0))
186 die("CredWrite failed");
187}
188
189static void erase_credential(void)
190{
191 CREDENTIALW **creds;
192 DWORD num_creds;
193 int i;
194
195 if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
196 return;
197
198 for (i = 0; i < num_creds; ++i) {
199 if (match_cred(creds[i]))
200 CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
201 }
202
203 CredFree(creds);
204}
205
206static WCHAR *utf8_to_utf16_dup(const char *str)
207{
208 int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
209 WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
210 MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
211 return wstr;
212}
213
0a3a972c
TB
214#define KB (1024)
215
a6253da0
EFL
216static void read_credential(void)
217{
0a3a972c
TB
218 size_t alloc = 100 * KB;
219 char *buf = calloc(alloc, sizeof(*buf));
a6253da0 220
0a3a972c 221 while (fgets(buf, alloc, stdin)) {
a6253da0 222 char *v;
0a3a972c
TB
223 size_t len = strlen(buf);
224 int ends_in_newline = 0;
3b12f46a 225 /* strip trailing CR / LF */
0a3a972c
TB
226 if (len && buf[len - 1] == '\n') {
227 buf[--len] = 0;
228 ends_in_newline = 1;
229 }
230 if (len && buf[len - 1] == '\r')
3b12f46a 231 buf[--len] = 0;
a6253da0 232
0a3a972c
TB
233 if (!ends_in_newline)
234 die("bad input: %s", buf);
235
3b12f46a 236 if (!*buf)
a6253da0 237 break;
a6253da0
EFL
238
239 v = strchr(buf, '=');
240 if (!v)
241 die("bad input: %s", buf);
242 *v++ = '\0';
243
244 if (!strcmp(buf, "protocol"))
8b2d219a 245 protocol = utf8_to_utf16_dup(v);
a6253da0 246 else if (!strcmp(buf, "host"))
8b2d219a 247 host = utf8_to_utf16_dup(v);
a6253da0 248 else if (!strcmp(buf, "path"))
8b2d219a 249 path = utf8_to_utf16_dup(v);
a6253da0 250 else if (!strcmp(buf, "username")) {
a6253da0
EFL
251 wusername = utf8_to_utf16_dup(v);
252 } else if (!strcmp(buf, "password"))
253 password = utf8_to_utf16_dup(v);
488d9d52
H
254 else if (!strcmp(buf, "password_expiry_utc"))
255 password_expiry_utc = utf8_to_utf16_dup(v);
d6958049
MJC
256 /*
257 * Ignore other lines; we don't know what they mean, but
258 * this future-proofs us when later versions of git do
259 * learn new lines, and the helpers are updated to match.
260 */
a6253da0 261 }
0a3a972c
TB
262
263 free(buf);
a6253da0
EFL
264}
265
266int main(int argc, char *argv[])
267{
268 const char *usage =
c358ed75 269 "usage: git credential-wincred <get|store|erase>\n";
a6253da0
EFL
270
271 if (!argv[1])
488d9d52 272 die("%s", usage);
a6253da0
EFL
273
274 /* git use binary pipes to avoid CRLF-issues */
275 _setmode(_fileno(stdin), _O_BINARY);
276 _setmode(_fileno(stdout), _O_BINARY);
277
278 read_credential();
279
a6253da0
EFL
280 if (!protocol || !(host || path))
281 return 0;
282
283 /* prepare 'target', the unique key for the credential */
8b2d219a
KB
284 wcscpy(target, L"git:");
285 wcsncat(target, protocol, ARRAY_SIZE(target));
286 wcsncat(target, L"://", ARRAY_SIZE(target));
287 if (wusername) {
288 wcsncat(target, wusername, ARRAY_SIZE(target));
289 wcsncat(target, L"@", ARRAY_SIZE(target));
a6253da0
EFL
290 }
291 if (host)
8b2d219a 292 wcsncat(target, host, ARRAY_SIZE(target));
a6253da0 293 if (path) {
8b2d219a
KB
294 wcsncat(target, L"/", ARRAY_SIZE(target));
295 wcsncat(target, path, ARRAY_SIZE(target));
a6253da0
EFL
296 }
297
a6253da0
EFL
298 if (!strcmp(argv[1], "get"))
299 get_credential();
300 else if (!strcmp(argv[1], "store"))
301 store_credential();
302 else if (!strcmp(argv[1], "erase"))
303 erase_credential();
304 /* otherwise, ignore unknown action */
305 return 0;
306}